errors

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 10 Imported by: 0

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

View Source
const ContentTypeProblemJSON = "application/problem+json"

ContentTypeProblemJSON is the media type of the error envelope (RFC 9457).

View Source
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.

View Source
const Prefix = "err"

Prefix is the namespace every DB result code carries.

Variables

This section is empty.

Functions

func FromResultCode

func FromResultCode(code string, safeMsg ...string) error

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

func HTTP(domain, reason string, safeMsg ...string) error

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

func Handler(source string, public bool) func(*azugo.Context, error) bool

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

func ParseCode(code string) (Code, bool)

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.

func (GoneError) Error

func (e GoneError) Error() string

func (GoneError) SafeError

func (e GoneError) SafeError() string

SafeError returns a client-safe message.

func (GoneError) StatusCode

func (GoneError) StatusCode() int

StatusCode returns HTTP 410 Gone.

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

func ParseProblem(body []byte) (*Problem, bool)

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

func Relay(down *Problem, service string, outerStatus int) *Problem

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

func (p *Problem) Error() string

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

func (p *Problem) MarshalError(contentType string) (body []byte, ct string, ok bool)

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

func (p *Problem) SafeError() string

SafeError implements azugo.SafeError: the client-safe message is the stable title, never the occurrence detail.

func (*Problem) StatusCode added in v1.2.0

func (p *Problem) StatusCode() int

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

type ReasonSpec struct {
	Status int
	Title  string
}

ReasonSpec is a taxonomy entry: the HTTP status and stable title for a reason registered via RegisterReason.

Jump to

Keyboard shortcuts

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