Documentation
¶
Overview ¶
Package errs provides a single, transport-agnostic error type with stable codes, an HTTP (and later gRPC) status mapping, request validation helpers, and automatic redaction of secrets in user-facing messages.
An *Error carries a machine-readable Code, an optional Title, a human Message (the "detail"), and optional Args used for i18n. It implements the standard error interface and unwraps to any underlying cause, so it composes with errors.Is/errors.As. The JSON shape is small and stable ({"code","title","detail","fields"}) so an *Error can be returned directly to API clients.
Codes ¶
Code is a stable classification whose integer values are aligned with google.golang.org/grpc/codes, so gRPC mapping is an identity and HTTP mapping is a small lookup table. Each code has a snake_case String (e.g. "invalid_argument") used as the wire "code" and as the default i18n key, and an HTTPStatus. The canonical codes are OK, Canceled, Unknown, InvalidArgument, DeadlineExceeded, NotFound, AlreadyExists, PermissionDenied, ResourceExhausted, FailedPrecondition, Aborted, OutOfRange, Unimplemented, Internal, Unavailable, DataLoss, and Unauthenticated.
Creating errors ¶
New wraps an existing error, Newf formats a fresh message, and Wrapf wraps a cause behind a formatted prefix. The call site (function and file:line) is captured for logging. Chain WithTitle, WithMessageID, and WithArgs to enrich an error:
if w == nil {
return errs.Newf(errs.NotFound, "widget %d not found", id).
WithTitle("widget not found").
WithMessageID("widget.not_found").
WithArgs(map[string]any{"id": id})
}
// Wrap a low-level cause behind a stable code:
if err := db.QueryRow(...); err != nil {
return errs.Wrapf(errs.Internal, err, "load widget %d", id)
}
Normalizing and inspecting ¶
From normalizes any error into an *Error (an unrecognized error becomes Internal), and Is reports whether an error carries a given Code:
e := errs.From(err) // always an *errs.Error (or nil)
if errs.Is(err, errs.NotFound) { // code-aware check
...
}
Encoding (HTTP) ¶
Encode renders the error as JSON and returns (data, contentType, error), satisfying the web encoder contract. It runs the detail through Sanitize, so secrets never leak to clients. HTTPStatus gives the response status:
body, contentType, _ := e.Encode()
w.Header().Set("Content-Type", contentType)
w.WriteHeader(e.HTTPStatus())
w.Write(body)
Validation ¶
Check validates a struct via github.com/go-playground/validator tags and, on failure, returns an InvalidArgument *Error whose Fields list the offending inputs. NewFieldErrors builds the same shape from an explicit list:
type CreateReq struct {
Name string `validate:"required"`
Email string `validate:"required,email"`
}
if err := errs.Check(req); err != nil {
return err // 400 with per-field FieldError entries
}
Sanitization ¶
Sanitize redacts common secret patterns (password/token/secret/api_key in key=value and JSON forms) and truncates to MaxMessageLen on a UTF-8 boundary. Encode applies it automatically; call it directly for any other string bound for logs or responses.
i18n ¶
An *Error is localized by the i18n package using MessageID (or, when unset, CodeStr) as the lookup key and Args as template data; see that package's TranslateError.
Index ¶
Constants ¶
const MaxMessageLen = 1024
MaxMessageLen bounds the length of a sanitized message to keep logs and API responses tidy.
Variables ¶
This section is empty.
Functions ¶
func Check ¶
Check validates val using struct tags (github.com/go-playground/validator). On failure it returns an *Error with code InvalidArgument whose Fields list the offending fields. On success it returns nil.
Types ¶
type Code ¶
type Code uint32
Code is a stable, transport-agnostic error classification. Its integer values are intentionally aligned with google.golang.org/grpc/codes so that gRPC mapping is an identity and HTTP mapping is a small lookup table.
const ( OK Code = 0 Canceled Code = 1 Unknown Code = 2 InvalidArgument Code = 3 DeadlineExceeded Code = 4 NotFound Code = 5 AlreadyExists Code = 6 PermissionDenied Code = 7 ResourceExhausted Code = 8 FailedPrecondition Code = 9 Aborted Code = 10 OutOfRange Code = 11 Unimplemented Code = 12 Internal Code = 13 DataLoss Code = 15 Unauthenticated Code = 16 )
Canonical error codes, value-aligned with gRPC status codes.
func (Code) HTTPStatus ¶
HTTPStatus maps the code to an HTTP status code.
type Error ¶
type Error struct {
Code Code `json:"-"`
CodeStr string `json:"code"`
Title string `json:"title,omitempty"`
Message string `json:"detail"`
Fields []FieldError `json:"fields,omitempty"`
Args map[string]any `json:"-"`
// MessageID is an optional i18n message key. When set it overrides CodeStr as
// the lookup key the i18n package uses to localize Message; Args supply the
// template data. Left empty, translation falls back to CodeStr.
MessageID string `json:"-"`
// contains filtered or unexported fields
}
Error is the canonical application error.
The JSON shape is intentionally small and stable so it can be returned directly to API clients:
{"code":"not_found","title":"...","detail":"widget 42 not found","fields":[...]}
func From ¶
From normalizes any error into an *Error. If err already is (or wraps) an *Error it is returned as-is; otherwise it becomes an Internal error.
func New ¶
New creates an *Error with the given code, wrapping err. The message is taken from err. The call site (function and file:line) is captured for logging.
func NewFieldErrors ¶
func NewFieldErrors(msg string, fields ...FieldError) *Error
NewFieldErrors builds an InvalidArgument error from an explicit field list.
func (*Error) Encode ¶
Encode renders the error as JSON, redacting any secrets in the detail. It satisfies the web encoder contract: (data, contentType, error).
func (*Error) HTTPStatus ¶
HTTPStatus returns the HTTP status code for this error.
func (*Error) WithMessageID ¶
WithMessageID sets the i18n lookup key and returns the error for chaining.
type FieldError ¶
FieldError describes a single failed validation constraint on an input field.