Ctrl K
ComunidadGuías…

response.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package aastro

import (
	"net/http"

	"github.com/goccy/go-json"
)

// ProblemDetails is the RFC 9457 ("Problem Details for HTTP APIs") body used
// for every gateway-authored response that carries no upstream data of its
// own - rate limiting, payload limits, plugin failures, and upstream/gateway
// failures that never produced anything worth returning as data.
//
// A response that *does* carry data (a full or partial aggregation success)
// is never wrapped: the body is the aggregated payload itself, exactly as a
// client of the upstream(s) directly would see it. See Router.buildResponse.
type ProblemDetails struct {
	// Type is always "about:blank" (RFC 9457 §4.2's own default for "no
	// further-specific type"): it is never a dereferencable URI on purpose.
	// A real one - even a stable, non-existent one under the gateway's own
	// docs domain - names the software fronting this upstreams to anyone
	// who receives an error, which is itself reconnaissance: it tells a
	// caller they're behind an aggregating gateway and invites probing for
	// what that implies about the backend topology. Errors carries the
	// actual discriminator instead - a closed, generic enum that says
	// nothing about what's behind the gateway.
	Type   string `json:"type"`
	Title  string `json:"title"`
	Status int    `json:"status"`
	Detail string `json:"detail,omitempty"`
	// Errors lists every distinct underlying ClientError - always at least
	// one. This is the one machine-readable discriminator (Type is constant,
	// see above); Title is for humans and may change wording over time.
	// More than one entry only for a multi-upstream flow where several
	// upstreams failed differently. This is a problem type extension member
	// (RFC 9457 §3.2), not a spec violation.
	Errors []ClientError `json:"errors"`
}

// problemTypeBlank is RFC 9457's own placeholder for "the HTTP status code
// is all the classification there is".
const problemTypeBlank = "about:blank"

type ClientError string

const (
	ClientErrRateLimitExceeded    ClientError = "RATE_LIMIT_EXCEEDED"
	ClientErrPayloadTooLarge      ClientError = "PAYLOAD_TOO_LARGE"
	ClientErrUpstreamBodyTooLarge ClientError = "UPSTREAM_BODY_TOO_LARGE"
	ClientErrUpstreamUnavailable  ClientError = "UPSTREAM_UNAVAILABLE"
	ClientErrUpstreamError        ClientError = "UPSTREAM_ERROR"
	ClientErrUpstreamClientError  ClientError = "UPSTREAM_CLIENT_ERROR"
	ClientErrUpstreamRedirect     ClientError = "UPSTREAM_REDIRECT"
	ClientErrUpstreamMalformed    ClientError = "UPSTREAM_MALFORMED"
	ClientErrInternal             ClientError = "INTERNAL"
	ClientErrAborted              ClientError = "ABORTED"
	ClientErrValueConflict        ClientError = "VALUE_CONFLICT"
	ClientErrUnauthorized         ClientError = "UNAUTHORIZED"
)

func (err ClientError) String() string {
	return string(err)
}

// problemTitle returns the RFC 9457 Title for this error code - a short,
// human-readable summary that does not change from one occurrence to the
// next (request-specific detail, when there is any, belongs in Detail).
func (err ClientError) problemTitle() string {
	if title, ok := problemTitles[err]; ok {
		return title
	}

	return string(err)
}

var problemTitles = map[ClientError]string{
	ClientErrRateLimitExceeded:    "Rate limit exceeded",
	ClientErrPayloadTooLarge:      "Request payload too large",
	ClientErrUpstreamBodyTooLarge: "Upstream response too large",
	ClientErrUpstreamUnavailable:  "Upstream unavailable",
	ClientErrUpstreamError:        "Upstream returned a server error",
	ClientErrUpstreamClientError:  "Upstream rejected the request",
	ClientErrUpstreamRedirect:     "Upstream returned a redirect",
	ClientErrUpstreamMalformed:    "Upstream response failed gateway policy",
	ClientErrInternal:             "Internal gateway error",
	ClientErrAborted:              "Request aborted before the upstream responded",
	ClientErrValueConflict:        "Conflicting values from upstreams",
	ClientErrUnauthorized:         "Unauthorized",
}

// WriteError writes a single-cause RFC 9457 Problem Details response:
// rate limiting, request-size limits, plugin failures, and any other
// gateway-side rejection that never reaches an upstream at all.
func WriteError(w http.ResponseWriter, code ClientError, status int) {
	w.Header().Set("Content-Type", "application/problem+json")
	w.WriteHeader(status)

	problem := ProblemDetails{
		Type:   problemTypeBlank,
		Title:  code.problemTitle(),
		Status: status,
		Errors: []ClientError{code},
	}

	if err := json.NewEncoder(w).Encode(problem); err != nil {
		// Fallback on error
		http.Error(w, http.StatusText(status), status)
	}
}