errcode

package
v0.0.0-...-85b4cba Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package errcode provides structured error codes for the GoCell framework. All errors exposed across package boundaries must use this package instead of bare errors.New. Error codes follow the ERR_{MODULE}_{REASON} convention.

Package errcode provides structured error codes for the GoCell framework. All errors exposed across package boundaries must use this package instead of bare errors.New.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsDomainNotFound

func IsDomainNotFound(err error, codes ...Code) bool

IsDomainNotFound reports whether err is a domain-layer not-found condition whose code is in the caller-supplied whitelist. Both conditions must hold:

  1. err must be an *Error with Category == CategoryDomain
  2. err.Code must appear in codes

This two-gated check prevents infra errors from ever matching, regardless of which code they carry — the dual-channel invariant from k8s IsNotFound.

Callers pass Code constants directly; no string(...) conversion is needed.

func IsExpected4xx

func IsExpected4xx(err error) bool

IsExpected4xx reports whether err maps to an HTTP 400-499 response code. These are expected client-side / business rejection conditions that callers should log at Warn level; true infrastructure failures should be Error.

Returns false for nil and for unclassified / plain errors (which are infra).

func IsInfraError

func IsInfraError(err error) bool

IsInfraError reports whether err represents an infrastructure failure.

Fail-closed semantics: any error that is not definitively classified as a domain / validation / auth error is treated as infra. This prevents infra outages from silently propagating into domain-not-found branches.

Returns false only for nil. Returns true for:

  • context.Canceled / context.DeadlineExceeded
  • driver.ErrBadConn / sql.ErrConnDone
  • *Error with Category == CategoryInfra or CategoryUnspecified
  • any unrecognised plain error (fail-closed)

Stdlib sentinel coverage is intentionally narrow (context.* / sql.Err* / driver.ErrBadConn). Adapters that return wrapped plain errors are covered by the fail-closed fallback: CategoryUnspecified → treated as infra. New adapters that return wrapped custom error types should construct them with NewInfra (or WrapInfra) so the category is explicit rather than relying on the fallback; no change to classify.go is required.

func IsTransient

func IsTransient(err error) bool

IsTransient reports whether err, or any error in its Unwrap chain, carries the ErrKeyProviderTransient code. It is the canonical predicate for routing KeyProvider failures in EventBus handlers:

if errcode.IsTransient(err) {
    return outbox.HandleResult{Disposition: outbox.DispositionRequeue, Err: err}
}
return outbox.HandleResult{Disposition: outbox.DispositionReject, Err: err}

Transient conditions map to Vault HTTP 503 / 429 / 408 / 499 (sealed, rate-limited, request timeout). All other KeyProvider errors are permanent (400 / 403 / 404) and must be routed to DispositionReject → DLX.

Returns false for nil. Uses errors.As so it correctly traverses chains produced by fmt.Errorf("…: %w", err).

ref: aws/aws-encryption-sdk-python src/aws_encryption_sdk/exceptions.py

Types

type Category

type Category int

Category classifies the origin of an error for log-level routing and dual-channel triage (infra vs domain). The zero value CategoryUnspecified is treated as infra (fail-closed) by all classifiers.

ref: k8s apimachinery pkg/api/errors — IsNotFound dual-channel pattern (infra errors must never map to domain not-found)

const (
	// CategoryUnspecified is the zero value. Classifiers treat it as infra
	// (fail-closed) to prevent leaking infra faults into domain branches.
	CategoryUnspecified Category = iota

	// CategoryDomain signals a well-known business-layer condition
	// (resource not found, conflict, validation failure).
	CategoryDomain

	// CategoryInfra signals an infrastructure failure (DB down, network
	// timeout, bad connection). Must never be mapped to domain not-found.
	CategoryInfra

	// CategoryValidation signals a caller input validation failure (400-class).
	CategoryValidation

	// CategoryAuth signals an authentication / authorisation failure (401/403).
	CategoryAuth
)

type Code

type Code string

Code is a typed error code string.

const (
	ErrMetadataInvalid        Code = "ERR_METADATA_INVALID"
	ErrMetadataNotFound       Code = "ERR_METADATA_NOT_FOUND"
	ErrCellNotFound           Code = "ERR_CELL_NOT_FOUND"
	ErrSliceNotFound          Code = "ERR_SLICE_NOT_FOUND"
	ErrContractNotFound       Code = "ERR_CONTRACT_NOT_FOUND"
	ErrAssemblyNotFound       Code = "ERR_ASSEMBLY_NOT_FOUND"
	ErrLifecycleInvalid       Code = "ERR_LIFECYCLE_INVALID"
	ErrDependencyCycle        Code = "ERR_DEPENDENCY_CYCLE"
	ErrValidationFailed       Code = "ERR_VALIDATION_FAILED"
	ErrReferenceBroken        Code = "ERR_REFERENCE_BROKEN"
	ErrInternal               Code = "ERR_INTERNAL"
	ErrAuthUnauthorized       Code = "ERR_AUTH_UNAUTHORIZED"
	ErrAuthForbidden          Code = "ERR_AUTH_FORBIDDEN"
	ErrRateLimited            Code = "ERR_RATE_LIMITED"
	ErrCSRFOriginDenied       Code = "ERR_CSRF_ORIGIN_DENIED"
	ErrBodyTooLarge           Code = "ERR_BODY_TOO_LARGE"
	ErrJourneyNotFound        Code = "ERR_JOURNEY_NOT_FOUND"
	ErrTestExecution          Code = "ERR_TEST_EXECUTION"
	ErrCheckRefInvalid        Code = "ERR_CHECKREF_INVALID"
	ErrZeroTestMatch          Code = "ERR_ZERO_TEST_MATCH"
	ErrBusClosed              Code = "ERR_BUS_CLOSED"
	ErrCellMissingOutbox      Code = "ERR_CELL_MISSING_OUTBOX"
	ErrCellMissingCodec       Code = "ERR_CELL_MISSING_CODEC"
	ErrCellMissingTokenIssuer Code = "ERR_CELL_MISSING_TOKEN_ISSUER"
	ErrCellInvalidConfig      Code = "ERR_CELL_INVALID_CONFIG"
	ErrSessionNotFound        Code = "ERR_SESSION_NOT_FOUND"
	ErrSessionConflict        Code = "ERR_SESSION_CONFLICT"
	ErrOrderNotFound          Code = "ERR_ORDER_NOT_FOUND"
	ErrDeviceNotFound         Code = "ERR_DEVICE_NOT_FOUND"
	ErrCommandNotFound        Code = "ERR_COMMAND_NOT_FOUND"
	ErrAdapterPGNoTx          Code = "ERR_ADAPTER_PG_NO_TX"
	ErrAuthKeyInvalid         Code = "ERR_AUTH_KEY_INVALID"
	// ErrAuthVerifierConfig signals a JWT verifier construction error — e.g.
	// required configuration (WithExpectedAudiences) was not provided.
	// Distinct from ErrAuthKeyInvalid (key material) so operators can route
	// verifier misconfiguration separately from cryptographic key failures.
	ErrAuthVerifierConfig Code = "ERR_AUTH_VERIFIER_CONFIG"
	ErrAuthTokenInvalid   Code = "ERR_AUTH_TOKEN_INVALID"
	ErrAuthTokenExpired   Code = "ERR_AUTH_TOKEN_EXPIRED"
	// ErrAuthInvalidTokenIntent signals that a JWT's token_use claim (and/or
	// its JOSE typ header) does not match the expected intent for the current
	// request scope — e.g., a refresh token presented at a business endpoint,
	// or an access token presented at /auth/refresh. Middleware and slice
	// layers map this to a generic ERR_AUTH_UNAUTHORIZED / ERR_AUTH_REFRESH_FAILED
	// response to prevent token-type enumeration; the specific code is only
	// visible in logs.
	//
	// ref: RFC 8725 §2.8 / §3.11 (JWT token confusion threat model)
	// ref: AWS Cognito token_use claim, Keycloak typ header constants
	ErrAuthInvalidTokenIntent Code = "ERR_AUTH_INVALID_TOKEN_INTENT"

	// Access-core cell error codes.
	ErrAuthUserNotFound         Code = "ERR_AUTH_USER_NOT_FOUND"
	ErrAuthUserDuplicate        Code = "ERR_AUTH_USER_DUPLICATE"
	ErrAuthRoleNotFound         Code = "ERR_AUTH_ROLE_NOT_FOUND"
	ErrAuthRoleDuplicate        Code = "ERR_AUTH_ROLE_DUPLICATE"
	ErrAuthInvalidInput         Code = "ERR_AUTH_INVALID_INPUT"
	ErrAuthUserLocked           Code = "ERR_AUTH_USER_LOCKED"
	ErrAuthSessionInvalidInput  Code = "ERR_AUTH_SESSION_INVALID_INPUT"
	ErrAuthIdentityInvalidInput Code = "ERR_AUTH_IDENTITY_INVALID_INPUT"
	ErrAuthLoginInvalidInput    Code = "ERR_AUTH_LOGIN_INVALID_INPUT"
	ErrAuthLoginFailed          Code = "ERR_AUTH_LOGIN_FAILED"
	ErrAuthLogoutInvalidInput   Code = "ERR_AUTH_LOGOUT_INVALID_INPUT"
	ErrAuthRefreshInvalidInput  Code = "ERR_AUTH_REFRESH_INVALID_INPUT"
	ErrAuthRefreshFailed        Code = "ERR_AUTH_REFRESH_FAILED"
	// Deprecated: use ErrRefreshTokenReused (CategoryAuth) via runtime/auth/refresh.ErrTokenReused.
	// This code is retained only for sessionrefresh.service's current implementation;
	// the F2 refresh store PR will migrate callers and remove this constant.
	ErrAuthRefreshTokenReuse Code = "ERR_AUTH_REFRESH_TOKEN_REUSE"
	ErrAuthInvalidToken      Code = "ERR_AUTH_INVALID_TOKEN"
	ErrAuthRBACInvalidInput  Code = "ERR_AUTH_RBAC_INVALID_INPUT"
	ErrAuthKeyMissing        Code = "ERR_AUTH_KEY_MISSING"
	ErrAuthSelfDelete        Code = "ERR_AUTH_SELF_DELETE"
	// ErrAuthPasswordResetRequired signals that the authenticated subject must
	// change their password before accessing business endpoints. The middleware
	// enforces this when the JWT claim password_reset_required is true.
	// Only the exempt endpoints (POST /api/v1/access/users/{id}/password and
	// DELETE /api/v1/access/sessions/{id}) bypass this check.
	ErrAuthPasswordResetRequired Code = "ERR_AUTH_PASSWORD_RESET_REQUIRED"

	// Config-core cell error codes.
	ErrConfigNotFound            Code = "ERR_CONFIG_NOT_FOUND"
	ErrConfigDuplicate           Code = "ERR_CONFIG_DUPLICATE"
	ErrConfigInvalidInput        Code = "ERR_CONFIG_INVALID_INPUT"
	ErrConfigPublishInvalidInput Code = "ERR_CONFIG_PUBLISH_INVALID_INPUT"
	ErrConfigRepoNotFound        Code = "ERR_CONFIG_REPO_NOT_FOUND"
	ErrConfigRepoDuplicate       Code = "ERR_CONFIG_REPO_DUPLICATE"
	ErrConfigRepoQuery           Code = "ERR_CONFIG_REPO_QUERY"
	ErrFlagNotFound              Code = "ERR_FLAG_NOT_FOUND"
	ErrFlagDuplicate             Code = "ERR_FLAG_DUPLICATE"
	ErrFlagInvalidInput          Code = "ERR_FLAG_INVALID_INPUT"
	ErrFlagRepoQuery             Code = "ERR_FLAG_REPO_QUERY"

	// Audit-core cell error codes.
	ErrAuditRepoNotFound Code = "ERR_AUDIT_REPO_NOT_FOUND"
	ErrAuditRepoQuery    Code = "ERR_AUDIT_REPO_QUERY"
	ErrArchiveUpload     Code = "ERR_ARCHIVE_UPLOAD"
	ErrArchiveMarshal    Code = "ERR_ARCHIVE_MARSHAL"
	ErrNotImplemented    Code = "ERR_NOT_IMPLEMENTED"

	// Pagination / validation error codes.
	ErrCursorInvalid     Code = "ERR_CURSOR_INVALID"
	ErrPageSizeExceeded  Code = "ERR_PAGE_SIZE_EXCEEDED"
	ErrInvalidTimeFormat Code = "ERR_INVALID_TIME_FORMAT"

	// Resilience middleware error codes.
	ErrCircuitOpen Code = "ERR_CIRCUIT_OPEN"

	// Outbox relay health error codes.
	// ErrRelayBudgetExhausted signals that an outbox relay operation (poll /
	// reclaim / cleanup) has exceeded its consecutive-failure threshold, tripping
	// the failure budget and marking /readyz unhealthy.
	ErrRelayBudgetExhausted Code = "ERR_RELAY_BUDGET_EXHAUSTED"

	// Observability configuration error.
	// Raised by kernel / runtime observability constructors when a
	// required dependency (Provider, cellID) is missing or malformed.
	// Semantically an initialisation error — distinct from
	// ErrValidationFailed (user-input validation) so operators can route
	// the two through different dashboards.
	ErrObservabilityConfigInvalid Code = "ERR_OBSERVABILITY_CONFIG_INVALID"

	// WebSocket runtime error codes.
	ErrWSConnNotFound   Code = "ERR_WS_CONN_NOT_FOUND"
	ErrWSAlreadyStarted Code = "ERR_WS_ALREADY_STARTED"
	ErrWSAlreadyStopped Code = "ERR_WS_ALREADY_STOPPED"
	ErrWSHubStopping    Code = "ERR_WS_HUB_STOPPING"
	ErrWSHubNotRunning  Code = "ERR_WS_HUB_NOT_RUNNING"
	ErrWSMaxConns       Code = "ERR_WS_MAX_CONNS"

	// Outbox envelope error codes.
	// ErrEnvelopeSchema signals that an inbound wire message does not conform
	// to the expected envelope schema — unknown schemaVersion, missing required
	// fields, or corrupt JSON. Consumers must Reject (not retry) on this error.
	ErrEnvelopeSchema Code = "ERR_ENVELOPE_SCHEMA"

	// Bootstrap lifecycle error codes.
	// ErrBootstrapLifecycle signals that a lifecycle operation was called in an
	// invalid state — e.g. Append or Start called after the lifecycle has already
	// started. Distinct from ErrLifecycleInvalid (metadata validation) so
	// operators can route runtime lifecycle faults separately.
	ErrBootstrapLifecycle Code = "ERR_BOOTSTRAP_LIFECYCLE"

	// Refresh token store error codes (runtime/auth/refresh).
	// These are returned by refresh.Store implementations; callers use
	// errors.Is against the package-level sentinels in refresh/errors.go.
	//
	// ErrRefreshTokenNotFound / ErrRefreshTokenExpired / ErrRefreshTokenRevoked
	// are CategoryDomain — expected client-observable conditions.
	// ErrRefreshTokenReused is CategoryAuth — an OAuth2 RFC 6749 §10.4
	// attack signal that triggers cascade revocation.
	ErrRefreshTokenNotFound Code = "ERR_REFRESH_TOKEN_NOT_FOUND"
	ErrRefreshTokenExpired  Code = "ERR_REFRESH_TOKEN_EXPIRED"
	ErrRefreshTokenRevoked  Code = "ERR_REFRESH_TOKEN_REVOKED"
	ErrRefreshTokenReused   Code = "ERR_REFRESH_TOKEN_REUSED"

	// KeyProvider error codes.
	// ErrKeyProviderKeyNotFound signals that the requested key ID is not
	// present in the provider's keyring — e.g. a historical key that has been
	// purged. Callers must not fall back to plaintext; surface as a config error.
	// Permanent error — EventBus handlers should return DispositionReject.
	// Maps to Vault HTTP 404 (key or mount not found).
	ErrKeyProviderKeyNotFound Code = "ERR_KEY_PROVIDER_KEY_NOT_FOUND"
	// ErrKeyProviderAuthFailed signals that the Vault token has been revoked,
	// has insufficient permissions, or has expired (Vault HTTP 403 Forbidden).
	// Distinct from ErrKeyProviderKeyNotFound (404 — key absent) so operators
	// can route permission/token failures separately from missing-key failures.
	//
	// Use when:
	//   - Vault returns HTTP 403 on any transit read/encrypt/decrypt path.
	//   - Token revoked (revoke-accessor) or token lacks required capabilities.
	//   - Permission denied on transit/keys/{name} or transit/encrypt|decrypt.
	//
	// Permanent error — EventBus handlers should return DispositionReject.
	// Operators must rotate the Vault token (not retry the operation).
	ErrKeyProviderAuthFailed Code = "ERR_KEY_PROVIDER_AUTH_FAILED"
	// ErrKeyProviderEncryptFailed signals a KMS encrypt-side operation failure
	// (Vault Transit encrypt API error, malformed response, etc.). Distinct from
	// ErrKeyProviderDecryptFailed so callers and log aggregators can route
	// encrypt-side failures (usually transient / retriable) separately from
	// decrypt-side failures (usually permanent / data integrity signal).
	// Permanent error — EventBus handlers should return DispositionReject.
	ErrKeyProviderEncryptFailed Code = "ERR_KEY_PROVIDER_ENCRYPT_FAILED"
	// ErrKeyProviderDecryptFailed signals an AES-GCM authentication failure,
	// wrong key, or malformed ciphertext. Fail-closed: callers must surface
	// this as an error and never return raw ciphertext or empty string.
	// Permanent error — EventBus handlers should return DispositionReject.
	ErrKeyProviderDecryptFailed Code = "ERR_KEY_PROVIDER_DECRYPT_FAILED"
	// ErrKeyProviderRotateFailed signals a key-rotation operation failure
	// (Vault rotate API returned an error, new key version could not be read
	// back, malformed response). Distinct from ErrKeyProviderKeyNotFound so
	// rotation-path retries and alerting do not confuse "key absent" with
	// "rotation API unreachable".
	// Permanent error — EventBus handlers should return DispositionReject.
	ErrKeyProviderRotateFailed Code = "ERR_KEY_PROVIDER_ROTATE_FAILED"
	// ErrKeyProviderTransient signals a transient KeyProvider failure that is
	// safe to retry after back-off. Maps to Vault HTTP responses indicating
	// temporary unavailability:
	//
	//   - 503 Service Unavailable (sealed, standby, maintenance)
	//   - 429 Too Many Requests (rate-limited)
	//   - 408 Request Timeout / network timeout
	//
	// Contrast with ErrKeyProviderEncryptFailed / ErrKeyProviderDecryptFailed /
	// ErrKeyProviderKeyNotFound / ErrKeyProviderRotateFailed, which signal
	// permanent conditions (400 Bad Request, 403 Forbidden, 404 Not Found).
	//
	// EventBus Disposition routing:
	//   - ErrKeyProviderTransient → DispositionRequeue (back-off retry)
	//   - All other KeyProvider errors → DispositionReject (DLX)
	//
	// Use IsTransient to check the full error chain.
	//
	// ref: aws/aws-encryption-sdk-python src/aws_encryption_sdk/exceptions.py
	// (GenerateKeyError / DecryptKeyError transient vs permanent split)
	ErrKeyProviderTransient Code = "ERR_KEY_PROVIDER_TRANSIENT"
	// ErrConfigDecryptFailed signals that a sensitive config value could not be
	// decrypted at the repository boundary. Maps to HTTP 500 (internal error).
	ErrConfigDecryptFailed Code = "ERR_CONFIG_DECRYPT_FAILED"
	// ErrConfigKeyMissing signals that a required encryption key (GOCELL_MASTER_KEY
	// or vault token) is absent at startup. Triggers fail-fast in postgres mode.
	ErrConfigKeyMissing Code = "ERR_CONFIG_KEY_MISSING"
)

Sentinel error codes used throughout the GoCell framework.

type Error

type Error struct {
	Code            Code
	Message         string
	InternalMessage string
	Details         map[string]any
	Cause           error
	Category        Category
}

Error is a structured error that carries a machine-readable Code, a human-readable Message, optional Details, and an optional wrapped Cause.

InternalMessage holds diagnostic detail that must never be exposed to API consumers. When present, Error() uses it (for logs/traces); HTTP response writers use Message (safe for clients).

Category classifies the error origin for log-level routing and infra/domain triage. The zero value CategoryUnspecified is treated as infra (fail-closed). Use NewInfra / NewDomain constructors to set the appropriate category; the legacy New / Wrap / Safe constructors leave Category at its zero value to preserve backward compatibility.

func New

func New(code Code, message string) *Error

New creates an *Error with the given code and message.

func NewAuth

func NewAuth(code Code, message string) *Error

NewAuth creates an *Error with CategoryAuth. Use this for authentication / authorisation failures (401/403) and attack signals such as refresh token reuse detection (OAuth2 RFC 6749 §10.4).

func NewDomain

func NewDomain(code Code, message string) *Error

NewDomain creates an *Error with CategoryDomain. Use this for well-known business-layer conditions (resource missing, conflict, etc.) that callers may handle specifically.

func NewInfra

func NewInfra(code Code, message string) *Error

NewInfra creates an *Error with CategoryInfra. Use this for storage, network, and dependency failures so they are never confused with domain not-found conditions.

func Safe

func Safe(code Code, publicMsg, internalMsg string) *Error

Safe creates an *Error with separate public and internal messages. publicMsg is returned to API clients; internalMsg is used in logs/traces via Error() and must never be exposed over the wire.

func WithDetails

func WithDetails(err *Error, details map[string]any) *Error

WithDetails returns a shallow copy of err with the provided details merged in. If err.Details is nil a new map is allocated; existing keys are preserved unless overwritten by the supplied details. It panics if err is nil — callers must not pass a nil *Error.

func Wrap

func Wrap(code Code, message string, cause error) *Error

Wrap creates an *Error that wraps an existing error as its Cause.

func WrapAuth

func WrapAuth(code Code, message string, cause error) *Error

WrapAuth creates an *Error with CategoryAuth that wraps the supplied cause. Use this when an authentication / authorisation failure or attack signal (e.g. reuse detection per RFC 6749 §10.4) has an underlying cause to preserve for error chain inspection (errors.Is / errors.As / Unwrap). The cause is stored in Error.Cause and exposed via Error.Error() in logs.

func WrapDomain

func WrapDomain(code Code, message string, cause error) *Error

WrapDomain creates an *Error with CategoryDomain that wraps the supplied cause. Use this when a domain-layer condition has an underlying cause to preserve. The cause is stored in Error.Cause and exposed via Error.Error() in logs.

func WrapInfra

func WrapInfra(code Code, message string, cause error) *Error

WrapInfra creates an *Error with CategoryInfra that wraps the supplied cause. Use this when an infrastructure failure has an underlying cause that should be preserved for error chain inspection (errors.Is / errors.As / Unwrap). The cause is stored in Error.Cause and exposed via Error.Error() in logs.

func (*Error) Error

func (e *Error) Error() string

Error returns a formatted string representation for logging/diagnostics. When InternalMessage is set it is preferred over Message, because Error() is consumed by logs and traces — not by API clients. Format: "[CODE] msg" or "[CODE] msg: cause" when a Cause is present.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying Cause, enabling errors.Is / errors.As chains.

Jump to

Keyboard shortcuts

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