Documentation
¶
Overview ¶
Package errors is the project error taxonomy. It maps the DB layer's namespaced result codes (`err:domain:reason`, e.g. from result_error('err:document:notFound', …)) to Azugo HTTP error types and to safe client messages, so every service returns consistent HTTP status codes and never leaks internals.
Auth-specific mappings stay in go-authbyte; this package covers the general domain/data errors. Where Azugo lacks a matching error type (e.g. 409 Conflict), this package supplies one that implements Azugo's ResponseStatusCode and SafeError interfaces so ctx.Error(err) maps it automatically.
Index ¶
- Constants
- func FromResultCode(code string, safeMsg ...string) error
- func HTTP(domain, reason string, safeMsg ...string) error
- func Handler(source string, public bool) func(*azugo.Context, error) bool
- func RegisterReason(reason string, spec ReasonSpec)
- type Code
- type Coder
- type ConflictError
- type GoneError
- type Hop
- type InternalError
- type Problem
- type ProblemOption
- type PublicProblem
- type ReasonSpec
Constants ¶
const ContentTypeProblemJSON = "application/problem+json"
ContentTypeProblemJSON is the media type of the error envelope (RFC 9457).
const MaxChainHops = 8
MaxChainHops bounds the internal chain breadcrumb. A relay that would exceed it keeps the root hop (where the error originated) and the most recent hops, collapsing the elided middle into a single marker (see boundChain). The full path is always reconstructable from the logs by the trace id, so bounding costs nothing and keeps any single log line and internal response sane.
const Prefix = "err"
Prefix is the namespace every DB result code carries.
Variables ¶
This section is empty.
Functions ¶
func FromResultCode ¶
FromResultCode maps a DB namespaced result code to the Azugo HTTP error type the service should return. The optional safeMsg overrides the default client-safe message for the mapped error where the type carries one.
Unrecognized codes map to InternalError (HTTP 500) with a fixed safe message, so an unmapped DB error can never leak its internals to the client.
func HTTP ¶
HTTP returns the Azugo HTTP error for a reason within a domain. It is the programmatic counterpart of FromResultCode for services that classify errors without a DB result code.
func Handler ¶ added in v1.2.0
Handler returns the router error handler that renders every error — this service's own or one relayed from downstream — as a uniform RFC 9457 application/problem+json response. Installed once (see platform.Setup) it replaces both the hand-rolled per-service error bodies and the framework's default {"errors":[…]} shape, so the wire has exactly one error format.
source is stamped as Problem.Source (the service id) when an error does not already carry one. When public is true the response is projected to the public envelope — Source and Chain are dropped and Detail is withheld unless marked public-safe — which the single public-facing boundary service (the BFF) sets; internal services leave it false so service-to-service errors carry the full envelope for relay and logging.
It returns true (the response is fully written) for every non-nil error; nil falls through to the framework.
func RegisterReason ¶ added in v1.4.0
func RegisterReason(reason string, spec ReasonSpec)
RegisterReason extends the taxonomy with a reason beyond the built-in set, so NewProblem(code), FromResultCode(code), and HTTP(domain, reason) all derive status and title for a service-specific reason without WithStatus/WithTitle at every call site — all three flow through mapReason, so they agree by construction rather than by convention.
Call it once, before serving any request (e.g. from an init() or from App.init() before platform.Setup).
Reason matching is case-insensitive and separator-insensitive, same as the built-in set (see normalize). Registering a reason that collides with a built-in reason after normalization panics — the built-in taxonomy is not overridable, so err:domain:notFound keeps meaning the same thing everywhere. A spec.Status outside the HTTP range (100–599) also panics: a ReasonSpec must carry a real status, so a zero value can't silently render as "status": 0.
Registering the same custom reason twice does not panic; the last spec wins. Register each reason from a single site so two call sites can't drift to different specs for the same code — the drift RegisterReason exists to remove.
Types ¶
type Code ¶
type Code struct {
// Domain is the resource/area, e.g. "document", "envelope", "identity".
Domain string
// Reason is the specific failure, e.g. "notFound", "forbidden", "conflict".
Reason string
}
Code is a parsed DB result code of the form `err:domain:reason`.
func ParseCode ¶
ParseCode splits a namespaced result code (`err:domain:reason`) into its parts. ok is false if the value is not a recognized result code (missing the `err:` prefix). Extra colon-separated segments beyond domain are folded into the domain so codes like `err:document:slot:notFound` still resolve a reason.
type Coder ¶ added in v1.2.0
type Coder interface {
ErrorCode() string
}
Coder is implemented by an error that carries a stable err:domain:reason code. A service can attach a code to its own error type and the renderer will preserve it; a Problem is matched directly and need not implement this.
type ConflictError ¶
type ConflictError struct {
// Resource is the conflicting resource (used in the safe message).
Resource string
}
ConflictError reports a state conflict (HTTP 409). Azugo/core has no conflict type, so the taxonomy defines one here.
func (ConflictError) Error ¶
func (e ConflictError) Error() string
func (ConflictError) SafeError ¶
func (e ConflictError) SafeError() string
SafeError returns a client-safe message.
func (ConflictError) StatusCode ¶
func (ConflictError) StatusCode() int
StatusCode returns HTTP 409 Conflict.
type GoneError ¶
type GoneError struct {
Resource string
}
GoneError reports a resource that existed but is no longer available (HTTP 410), e.g. an expired signing envelope.
type Hop ¶ added in v1.2.0
type Hop struct {
Service string `json:"service,omitempty"`
Code string `json:"code,omitempty"`
Status int `json:"status,omitempty"`
// Elided is the number of middle hops omitted by bounding (see boundChain).
// Present only on the single marker hop; zero (omitted) on real hops.
Elided int `json:"elided,omitempty"`
}
Hop is one step in a Problem's chain: the service that handled the error and the code/status it saw or returned. An elided-middle marker carries only Elided (the count of omitted hops).
type InternalError ¶
type InternalError struct {
Err error
}
InternalError is the safe fallback for an unmapped or genuinely internal failure: it returns HTTP 500 with a fixed, non-leaking message while keeping the underlying error for server-side logging.
func (InternalError) Error ¶
func (e InternalError) Error() string
func (InternalError) SafeError ¶
func (InternalError) SafeError() string
SafeError returns a fixed message that never exposes internals.
func (InternalError) StatusCode ¶
func (InternalError) StatusCode() int
StatusCode returns HTTP 500 Internal Server Error.
func (InternalError) Unwrap ¶
func (e InternalError) Unwrap() error
Unwrap exposes the wrapped error for errors.Is/As and logging.
type Problem ¶ added in v1.2.0
type Problem struct {
// Type is an optional URI identifying the problem kind. Defaults to
// about:blank (omitted) until a documentation registry exists.
Type string `json:"type,omitempty"`
// Title is the stable, human-readable summary of the problem kind. It is
// the string a public client displays; it is carried from the origin, never
// authored at a relay.
Title string `json:"title"`
// Status mirrors the HTTP status line.
Status int `json:"status"`
// Detail is the occurrence-specific message. It is a SafeError (never a
// secret, PII, or internal name) but is internal-by-default: dropped at the
// public boundary unless the emitter marked it public-safe.
Detail string `json:"detail,omitempty"`
// Code is the stable machine identifier the caller branches on
// (err:domain:reason). The middle segment is the domain, not the service.
Code string `json:"code"`
// Source is the id of the service that originated the error. Internal only:
// stripped at the public boundary.
Source string `json:"source,omitempty"`
// TraceID is the thread that pulls every related log line. Safe to expose;
// it is the operator's jump-off point into the logs and traces.
TraceID string `json:"trace_id,omitempty"`
// Chain is the ordered breadcrumb of hops, root (origin) first. Internal
// only: stripped at the public boundary. Bounded by MaxChainHops.
Chain []Hop `json:"chain,omitempty"`
// contains filtered or unexported fields
}
Problem is the uniform error envelope every service returns (RFC 9457 Problem Details, house profile). It is produced for this service's own errors and preserved when relaying a downstream error, so the terminal code/source/trace id survive the whole trip back to the initiator.
It is a Go error: it implements the status, safe-message, and custom-body interfaces the web framework consults, so ctx.Error(problem) renders it as application/problem+json with the right HTTP status. The global renderer (see Handler) makes this the shape of every error response, produced or not.
func NewProblem ¶ added in v1.2.0
func NewProblem(code string, opts ...ProblemOption) *Problem
NewProblem builds a Problem for a stable code (err:domain:reason). The HTTP status and title are derived from the code's reason via the taxonomy — the single source of truth for status — and can be overridden with options.
func ParseProblem ¶ added in v1.2.0
ParseProblem decodes an application/problem+json body into a *Problem. ok is false when the body is not a problem document (no code, title, or status), so a caller can fall back to a generic envelope for a non-conforming upstream.
func Relay ¶ added in v1.2.0
Relay builds the error this service returns for a failed downstream call. It preserves the terminal code/source/trace id/title/detail (so the initiator still sees what actually failed) and appends this service to the chain, choosing outerStatus deliberately — never collapsing a parsed downstream error into a bare 502. down is the decoded downstream Problem; service is this service's id; outerStatus is the status this service returns (pass the downstream status to relay it unchanged, or a dependency status such as 424).
func (*Problem) Error ¶ added in v1.2.0
Error implements error. It is the internal (log-side) representation: the code and the most specific message available. It is never the client body — that is the JSON envelope.
func (*Problem) MarshalError ¶ added in v1.2.0
MarshalError implements azugo.ErrorMarshaler so a Problem renders its own application/problem+json body. It renders for JSON (and problem+json) negotiation and falls back (ok=false) for anything else (e.g. XML), letting the framework format it. The global Handler renders every error uniformly; this keeps a Problem self-describing where the Handler is not installed.
func (*Problem) Public ¶ added in v1.2.0
func (p *Problem) Public() *PublicProblem
Public returns the boundary projection: Source and Chain are dropped structurally, and Detail is included only if the emitter marked it public-safe.
func (*Problem) SafeError ¶ added in v1.2.0
SafeError implements azugo.SafeError: the client-safe message is the stable title, never the occurrence detail.
func (*Problem) StatusCode ¶ added in v1.2.0
StatusCode implements the framework's status interface so ctx.Error(problem) returns the right HTTP status.
type ProblemOption ¶ added in v1.2.0
type ProblemOption func(*Problem)
ProblemOption configures a Problem at construction.
func WithDetail ¶ added in v1.2.0
func WithDetail(detail string) ProblemOption
WithDetail sets the occurrence-specific, internal-by-default message. It must be a SafeError (no secrets/PII/internal names); it is dropped at the public boundary.
func WithPublicDetail ¶ added in v1.2.0
func WithPublicDetail(detail string) ProblemOption
WithPublicDetail sets a detail that is also safe to show a public client (it survives the boundary projection). Use only for messages deliberately meant for end users.
func WithSource ¶ added in v1.2.0
func WithSource(source string) ProblemOption
WithSource sets the originating service id. The renderer fills it from the app name when unset, so a handler rarely needs this.
func WithStatus ¶ added in v1.2.0
func WithStatus(status int) ProblemOption
WithStatus overrides the HTTP status derived from the code.
func WithTitle ¶ added in v1.2.0
func WithTitle(title string) ProblemOption
WithTitle overrides the stable title derived from the code.
func WithType ¶ added in v1.2.0
func WithType(typeURI string) ProblemOption
WithType sets the problem type URI.
type PublicProblem ¶ added in v1.2.0
type PublicProblem struct {
Type string `json:"type,omitempty"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Code string `json:"code"`
TraceID string `json:"trace_id,omitempty"`
}
PublicProblem is the boundary projection of a Problem: it structurally omits Source and Chain, so a public response cannot leak service topology no matter how it is built. The public boundary renders this shape (see Problem.Public); the strip is a property of the type, not a field a caller must remember to clear.
type ReasonSpec ¶ added in v1.4.0
ReasonSpec is a taxonomy entry: the HTTP status and stable title for a reason registered via RegisterReason.