Documentation
¶
Overview ¶
Package httperror defines structured API errors that carry an HTTP status, a machine-readable code, a gRPC status code and the request correlation ID, and that know how to serialise themselves as a JSON HTTP response.
Error and ManyError implement error, marshal.WriteHTTPResponse and GRPCStatus(), so a single value can be returned from a handler, written with marshal.WriteJSON, or returned from a gRPC method:
if id == "" {
return httperror.InvalidParam("id is required").WithContext(ctx)
}
item, err := db.Get(ctx, id)
if err != nil {
return httperror.WrapWithCtx(ctx, err, "failed to load item %s", id) // maps to 404/408/500
}
The JSON wire format is {"code": "...", "message": "...", "request_id": "..."}; ManyError adds an "errors" map keyed by field or item.
Code* constants list the well-known codes. HTTPStatusFromRPC, NewGrpc, NewFromPb and Status convert between gRPC codes and HTTP statuses using the Google API error mapping: Unauthenticated is 401 (Unauthorized) and PermissionDenied is 403 (Forbidden). GRPCStatus attaches the Code as a status detail, so request_too_large, sent as ResourceExhausted, converts back to 413 rather than 429.
WriteHTTPResponse never modifies the error it writes, so Error values may be built once and shared across requests.
Index ¶
- Constants
- func CorrelationID(err error) string
- func GRPCCode(err error) codes.Code
- func GRPCMessage(err error) string
- func HTTPStatusFromRPC(c codes.Code) int
- func IsInvalidModel(err error) bool
- func IsInvalidRequestError(err error, errStrings ...string) bool
- func IsNotFound(err error, errStrings ...string) bool
- func IsTimeout(err error, errStrings ...string) bool
- func Status(err error) int
- type Error
- func AccountNotFound(msgFormat string, vals ...any) *Error
- func BadNonce(msgFormat string, vals ...any) *Error
- func Conflict(msgFormat string, vals ...any) *Error
- func ContentLengthRequired() *Error
- func FailedToReadRequestBody(msgFormat string, vals ...any) *Error
- func Forbidden(msgFormat string, vals ...any) *Error
- func FromOAuth(code, descr string) *Error
- func InvalidContentType(msgFormat string, vals ...any) *Error
- func InvalidJSON(msgFormat string, vals ...any) *Error
- func InvalidParam(msgFormat string, vals ...any) *Error
- func InvalidRequest(msgFormat string, vals ...any) *Error
- func Malformed(msgFormat string, vals ...any) *Error
- func New(status int, code string, msgFormat string, vals ...any) *Error
- func NewFromCtx(ctx context.Context, status int, code string, msgFormat string, vals ...any) *Error
- func NewFromPb(err error) *Error
- func NewGrpc(code codes.Code, msgFormat string, vals ...any) *Error
- func NewGrpcFromCtx(ctx context.Context, code codes.Code, msgFormat string, vals ...any) *Error
- func NotFound(msgFormat string, vals ...any) *Error
- func NotReady(msgFormat string, vals ...any) *Error
- func RateLimitExceeded(msgFormat string, vals ...any) *Error
- func RequestTooLarge(msgFormat string, vals ...any) *Error
- func Timeout(msgFormat string, vals ...any) *Error
- func TooEarly(msgFormat string, vals ...any) *Error
- func Unauthorized(msgFormat string, vals ...any) *Error
- func Unexpected(msgFormat string, vals ...any) *Error
- func Wrap(err error, msgAndArgs ...any) *Error
- func WrapWithCtx(ctx context.Context, err error, msgAndArgs ...any) *Error
- func (e *Error) Cause() error
- func (e *Error) CorrelationID() string
- func (e *Error) Error() string
- func (e *Error) GRPCStatus() *status.Status
- func (e *Error) Is(target error) bool
- func (e *Error) Unwrap() error
- func (e *Error) WithCause(err error) *Error
- func (e *Error) WithContext(ctx context.Context) *Error
- func (e *Error) WriteHTTPResponse(w http.ResponseWriter, r *http.Request)
- type ManyError
- func (m *ManyError) Add(key string, err error) *ManyError
- func (m *ManyError) Cause() error
- func (m *ManyError) CorrelationID() string
- func (m *ManyError) Error() string
- func (m *ManyError) GRPCStatus() *status.Status
- func (m *ManyError) HasErrors() bool
- func (m *ManyError) WithCause(err error) *ManyError
- func (m *ManyError) WriteHTTPResponse(w http.ResponseWriter, r *http.Request)
Constants ¶
const ( // CodeAccountNotFound when requested account not found CodeAccountNotFound = "account_not_found" // CodeBadNonce is returned for bad nonce. CodeBadNonce = "bad_nonce" // CodeConflict is returned whith 409 CONFLICT response code. CodeConflict = "conflict" // CodeConnection is returned when connection failed. CodeConnection = "connection" // CodeContentLengthRequired is returned when request does not specify ContentLength. CodeContentLengthRequired = "content_length_required" // CodeFailedToReadRequestBody is returned when there's an error reading the HTTP body of the request. CodeFailedToReadRequestBody = "request_body" // CodeForbidden is returned when the client is not authorized to access the resource indicated. CodeForbidden = "forbidden" // CodeInvalidContentType is returned when request specifies invalid Content-Type. CodeInvalidContentType = "invalid_content_type" // CodeInvalidJSON is returned when we were unable to parse a client supplied JSON Payload. CodeInvalidJSON = "invalid_json" // CodeInvalidParam is returned where a URL parameter, or other type of generalized parameters value is invalid. CodeInvalidParam = "invalid_parameter" // CodeInvalidRequest is returned when the request validation failed. CodeInvalidRequest = "invalid_request" // CodeMalformed is returned when the request was malformed. CodeMalformed = "malformed" // CodeNotFound is returned when the requested URL doesn't exist. CodeNotFound = "not_found" // CodeNotReady is returned when the service is not ready to serve CodeNotReady = "not_ready" // CodeRateLimitExceeded is returned when the client has exceeded their request allotment. CodeRateLimitExceeded = "rate_limit_exceeded" // CodeRequestFailed is returned when an outbound request failed. CodeRequestFailed = "request_failed" // CodeRequestTooLarge is returned when the client provided payload is larger than allowed for the particular resource. CodeRequestTooLarge = "request_too_large" // CodeTimeout is returned when request timed out. CodeTimeout = "timeout" // CodeTooEarly is returned when the client makes requests too early. CodeTooEarly = "too_early" CodeUnauthorized = "unauthorized" // CodeUnexpected is returned when something went wrong. CodeUnexpected = "unexpected" )
Variables ¶
This section is empty.
Functions ¶
func CorrelationID ¶
CorrelationID extracts the correlation ID from an *Error or from the detail attached by Error.GRPCStatus to a gRPC status error; "" if none.
func GRPCCode ¶
GRPCCode returns the status code of a gRPC error, or codes.Internal for errors that carry no status.
func GRPCMessage ¶
GRPCMessage returns the status message of a gRPC error, or err.Error() for other errors.
func HTTPStatusFromRPC ¶
HTTPStatusFromRPC maps a gRPC status code to an HTTP status following the Google API error model; unknown codes map to 0.
func IsInvalidModel ¶
IsInvalidModel reports whether the error text contains "invalid model".
func IsInvalidRequestError ¶
IsInvalidRequestError reports whether err (or any of errStrings) looks like a client error, by substring match on "invalid", "Invalid", "bad" or "400".
func IsNotFound ¶ added in v0.36.378
IsNotFound reports whether err is an xdb not-found error, or whether err (or any of errStrings) contains "not found", "Not Found" or "404".
func IsTimeout ¶
IsTimeout reports whether err is context.DeadlineExceeded or context.Canceled, or whether err (or any of errStrings) contains a timeout/deadline/cancel substring.
Types ¶
type Error ¶
type Error struct {
// HTTPStatus contains the HTTP status code that should be used for this error
HTTPStatus int `json:"-"`
// RPCStatus is the gRPC status code returned by GRPCStatus; derived from
// Code for the well-known codes, codes.OK (0) for unknown codes.
RPCStatus codes.Code `json:"-"`
// Code identifies the particular error condition [for programatic consumers]
Code string `json:"code"`
// RequestID identifies the request ID
RequestID string `json:"request_id,omitempty"`
// Message is an textual description of the error
Message string `json:"message"`
// contains filtered or unexported fields
}
Error represents a single error from API.
func AccountNotFound ¶
AccountNotFound returns Error instance with AccountNotFound code
func ContentLengthRequired ¶
func ContentLengthRequired() *Error
ContentLengthRequired returns Error instance with ContentLengthRequired code
func FailedToReadRequestBody ¶
FailedToReadRequestBody returns Error instance with FailedToReadRequestBody code
func FromOAuth ¶
FromOAuth converts an OAuth 2.0 error response (RFC 6749 "error" and "error_description") into an *Error whose Code is the OAuth error code and whose HTTP status is 400, 401, 500 or 503 depending on the code.
func InvalidContentType ¶
InvalidContentType returns Error instance with InvalidContentType code
func InvalidJSON ¶
InvalidJSON returns Error instance with InvalidJSON code
func InvalidParam ¶
InvalidParam returns Error instance with InvalidParam code
func InvalidRequest ¶
InvalidRequest returns Error instance with InvalidRequest code
func New ¶
New returns an Error with the given HTTP status and code, formatting the message with fmt.Sprintf. RPCStatus is looked up from code; use one of the Code* constants for a meaningful gRPC mapping.
func NewFromCtx ¶
NewFromCtx is New plus WithContext: the RequestID is taken from the correlation ID in ctx.
func NewFromPb ¶
NewFromPb converts an error received from a gRPC call into an *Error: an *Error is returned as is, a status error is mapped by code (keeping the correlation ID detail, see CorrelationID, and restoring HTTP 413 request_too_large from the code detail), anything else becomes a 500 unexpected error with the original as cause.
func NewGrpc ¶
NewGrpc returns an *Error for the gRPC code, with HTTPStatus and Code derived from it via HTTPStatusFromRPC.
func NewGrpcFromCtx ¶
NewGrpcFromCtx returns an *Error for the gRPC code with the HTTP status and Code derived from it, and RequestID taken from ctx.
func RateLimitExceeded ¶
RateLimitExceeded returns Error instance with RateLimitExceeded code
func RequestTooLarge ¶
RequestTooLarge returns an HTTP 413 error with the request_too_large code.
func Unauthorized ¶
Unauthorized returns Error instance with Unauthorized code
func Unexpected ¶
Unexpected returns Error instance with Unexpected code
func Wrap ¶
Wrap converts any error into an *Error. An *Error or *ManyError found via errors.As is preserved (returned as is when msgAndArgs is empty); a gRPC status error is mapped through HTTPStatusFromRPC; otherwise the error text is classified with IsNotFound (404), IsInvalidRequestError (400), IsTimeout (408) or Unexpected (500). msgAndArgs optionally override the message: a single string, or a format string followed by arguments.
func WrapWithCtx ¶
WrapWithCtx is Wrap followed by WithContext(ctx).
func (*Error) CorrelationID ¶
CorrelationID implements the Correlation interface, and returns request ID
func (*Error) GRPCStatus ¶
GRPCStatus returns the gRPC status for the error (RPCStatus and Message), attaching the Code and RequestID as details so NewFromPb and CorrelationID can recover them on the client side. It makes *Error usable as a gRPC handler return value.
func (*Error) Is ¶
Is reports whether target is an *Error with identical Code and Message, for use with errors.Is.
func (*Error) Unwrap ¶
Unwrap returns the error one level below the cause when the cause itself wraps an error, otherwise the cause. This lets errors.Is/As reach through both Error and a cockroachdb/pkg errors wrapper.
func (*Error) WithCause ¶
WithCause records the underlying error (returned by Cause/Unwrap and logged by marshal.WriteJSON). It mutates and returns the receiver.
func (*Error) WithContext ¶
WithContext records ctx on the error and copies its correlation ID into RequestID. It mutates and returns the receiver.
func (*Error) WriteHTTPResponse ¶
func (e *Error) WriteHTTPResponse(w http.ResponseWriter, r *http.Request)
WriteHTTPResponse writes the error as an application/json body with HTTPStatus, pretty-printed when the URL has a "pp" query parameter. If RequestID is empty the body carries the request's correlation ID instead. The receiver is not modified, so an Error may be shared across requests.
type ManyError ¶
type ManyError struct {
// HTTPStatus contains the HTTP status code that should be used for this error
HTTPStatus int `json:"-"`
RPCStatus codes.Code `json:"-"`
// Code identifies the particular error condition [for programatic consumers]
Code string `json:"code,omitempty"`
// RequestID identifies the request ID
RequestID string `json:"request_id,omitempty"`
// Message is an textual description of the error
Message string `json:"message,omitempty"`
// Errors holds the individual errors keyed by the caller-chosen key
// passed to Add (for example a field or item name).
Errors map[string]*Error `json:"errors,omitempty"`
// contains filtered or unexported fields
}
ManyError identifies many errors from API.
func NewMany ¶
NewMany returns a ManyError with the given HTTP status and code and an empty Errors map; add nested errors with Add.
func (*ManyError) Add ¶
Add records err under key (replacing any previous entry) and returns the receiver. Non-*Error values are wrapped as CodeUnexpected. The first error added becomes the Cause. Add is safe for concurrent use and allocates a new ManyError when called on a nil receiver.
func (*ManyError) CorrelationID ¶
CorrelationID implements the Correlation interface, and returns request ID
func (*ManyError) Error ¶
Error returns "code: message" (prefixed with the request ID when set) or, when Code is empty, the nested errors joined by ";".
func (*ManyError) GRPCStatus ¶
GRPCStatus returns the gRPC status derived from Code and Message, with the Code and RequestID details that Error.GRPCStatus attaches, so a *ManyError can be returned from gRPC handlers.
func (*ManyError) WriteHTTPResponse ¶
func (m *ManyError) WriteHTTPResponse(w http.ResponseWriter, r *http.Request)
WriteHTTPResponse writes the error and its nested Errors as an application/json body with HTTPStatus; see Error.WriteHTTPResponse. The receiver is not modified: when RequestID is empty the body carries the request's correlation ID. The nested errors are snapshotted under the Add lock, which is released before anything is written, so a slow client never blocks other users of a shared ManyError.