httperror

package
v1.0.442 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 16 Imported by: 8

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

View Source
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 is for unauthorized access.
	CodeUnauthorized = "unauthorized"
	// CodeUnexpected is returned when something went wrong.
	CodeUnexpected = "unexpected"
)

Variables

This section is empty.

Functions

func CorrelationID

func CorrelationID(err error) string

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

func GRPCCode(err error) codes.Code

GRPCCode returns the status code of a gRPC error, or codes.Internal for errors that carry no status.

func GRPCMessage

func GRPCMessage(err error) string

GRPCMessage returns the status message of a gRPC error, or err.Error() for other errors.

func HTTPStatusFromRPC

func HTTPStatusFromRPC(c codes.Code) int

HTTPStatusFromRPC maps a gRPC status code to an HTTP status following the Google API error model; unknown codes map to 0.

func IsInvalidModel

func IsInvalidModel(err error) bool

IsInvalidModel reports whether the error text contains "invalid model".

func IsInvalidRequestError

func IsInvalidRequestError(err error, errStrings ...string) bool

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

func IsNotFound(err error, errStrings ...string) bool

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

func IsTimeout(err error, errStrings ...string) bool

IsTimeout reports whether err is context.DeadlineExceeded or context.Canceled, or whether err (or any of errStrings) contains a timeout/deadline/cancel substring.

func Status

func Status(err error) int

Status returns the HTTP status for err: 200 for nil, HTTPStatus for a direct *Error or *ManyError, otherwise the gRPC status code of err mapped with HTTPStatusFromRPC (500 for non-status errors), or 413 when the status carries the request_too_large code detail (see NewFromPb).

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

func AccountNotFound(msgFormat string, vals ...any) *Error

AccountNotFound returns Error instance with AccountNotFound code

func BadNonce

func BadNonce(msgFormat string, vals ...any) *Error

BadNonce returns Error instance with BadNonce code

func Conflict

func Conflict(msgFormat string, vals ...any) *Error

Conflict returns Error instance with Conflict code

func ContentLengthRequired

func ContentLengthRequired() *Error

ContentLengthRequired returns Error instance with ContentLengthRequired code

func FailedToReadRequestBody

func FailedToReadRequestBody(msgFormat string, vals ...any) *Error

FailedToReadRequestBody returns Error instance with FailedToReadRequestBody code

func Forbidden

func Forbidden(msgFormat string, vals ...any) *Error

Forbidden returns Error instance with Forbidden code

func FromOAuth

func FromOAuth(code, descr string) *Error

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

func InvalidContentType(msgFormat string, vals ...any) *Error

InvalidContentType returns Error instance with InvalidContentType code

func InvalidJSON

func InvalidJSON(msgFormat string, vals ...any) *Error

InvalidJSON returns Error instance with InvalidJSON code

func InvalidParam

func InvalidParam(msgFormat string, vals ...any) *Error

InvalidParam returns Error instance with InvalidParam code

func InvalidRequest

func InvalidRequest(msgFormat string, vals ...any) *Error

InvalidRequest returns Error instance with InvalidRequest code

func Malformed

func Malformed(msgFormat string, vals ...any) *Error

Malformed returns Error instance with Malformed code

func New

func New(status int, code string, msgFormat string, vals ...any) *Error

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

func NewFromCtx(ctx context.Context, status int, code string, msgFormat string, vals ...any) *Error

NewFromCtx is New plus WithContext: the RequestID is taken from the correlation ID in ctx.

func NewFromPb

func NewFromPb(err error) *Error

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

func NewGrpc(code codes.Code, msgFormat string, vals ...any) *Error

NewGrpc returns an *Error for the gRPC code, with HTTPStatus and Code derived from it via HTTPStatusFromRPC.

func NewGrpcFromCtx

func NewGrpcFromCtx(ctx context.Context, code codes.Code, msgFormat string, vals ...any) *Error

NewGrpcFromCtx returns an *Error for the gRPC code with the HTTP status and Code derived from it, and RequestID taken from ctx.

func NotFound

func NotFound(msgFormat string, vals ...any) *Error

NotFound returns Error instance with NotFound code

func NotReady

func NotReady(msgFormat string, vals ...any) *Error

NotReady returns Error instance with NotReady code

func RateLimitExceeded

func RateLimitExceeded(msgFormat string, vals ...any) *Error

RateLimitExceeded returns Error instance with RateLimitExceeded code

func RequestTooLarge

func RequestTooLarge(msgFormat string, vals ...any) *Error

RequestTooLarge returns an HTTP 413 error with the request_too_large code.

func Timeout

func Timeout(msgFormat string, vals ...any) *Error

Timeout returns Error instance with RequestTimeout code

func TooEarly

func TooEarly(msgFormat string, vals ...any) *Error

TooEarly returns Error instance with TooEarly code

func Unauthorized

func Unauthorized(msgFormat string, vals ...any) *Error

Unauthorized returns Error instance with Unauthorized code

func Unexpected

func Unexpected(msgFormat string, vals ...any) *Error

Unexpected returns Error instance with Unexpected code

func Wrap

func Wrap(err error, msgAndArgs ...any) *Error

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

func WrapWithCtx(ctx context.Context, err error, msgAndArgs ...any) *Error

WrapWithCtx is Wrap followed by WithContext(ctx).

func (*Error) Cause

func (e *Error) Cause() error

Cause returns the underlying error set with WithCause, or nil.

func (*Error) CorrelationID

func (e *Error) CorrelationID() string

CorrelationID implements the Correlation interface, and returns request ID

func (*Error) Error

func (e *Error) Error() string

Error implements the standard error interface

func (*Error) GRPCStatus

func (e *Error) GRPCStatus() *status.Status

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

func (e *Error) Is(target error) bool

Is reports whether target is an *Error with identical Code and Message, for use with errors.Is.

func (*Error) Unwrap

func (e *Error) Unwrap() error

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

func (e *Error) WithCause(err error) *Error

WithCause records the underlying error (returned by Cause/Unwrap and logged by marshal.WriteJSON). It mutates and returns the receiver.

func (*Error) WithContext

func (e *Error) WithContext(ctx context.Context) *Error

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

func NewMany(status int, code string, msgFormat string, vals ...any) *ManyError

NewMany returns a ManyError with the given HTTP status and code and an empty Errors map; add nested errors with Add.

func (*ManyError) Add

func (m *ManyError) Add(key string, err error) *ManyError

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) Cause

func (m *ManyError) Cause() error

Cause returns original error

func (*ManyError) CorrelationID

func (m *ManyError) CorrelationID() string

CorrelationID implements the Correlation interface, and returns request ID

func (*ManyError) Error

func (m *ManyError) Error() string

Error returns "code: message" (prefixed with the request ID when set) or, when Code is empty, the nested errors joined by ";".

func (*ManyError) GRPCStatus

func (m *ManyError) GRPCStatus() *status.Status

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) HasErrors

func (m *ManyError) HasErrors() bool

HasErrors check if ManyError has any nested error associated with it.

func (*ManyError) WithCause

func (m *ManyError) WithCause(err error) *ManyError

WithCause adds the cause error

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL