Documentation
¶
Overview ¶
Package apperror defines the typed application error that handlers, services, and repositories return instead of writing HTTP error responses inline. A single mapper at the routing layer (middleware.MapErrors) catches the error, logs it, and serializes it onto the wire via WriteHTTP — see resources/ERROR_HANDLING_IMPLEMENTATION_PLAN.md.
Index ¶
- Constants
- Variables
- func NewValidationErrorResponse(w http.ResponseWriter, err error)
- func WriteHTTP(w http.ResponseWriter, e *Error, trackingID string)
- type Def
- type Error
- func (e *Error) Error() string
- func (e *Error) Origin() string
- func (e *Error) StackString() string
- func (e *Error) Unwrap() error
- func (e *Error) WithCause(err error) *Error
- func (e *Error) WithDetails(details any) *Error
- func (e *Error) WithFieldErrors(fe []FieldError) *Error
- func (e *Error) WithLogMessage(msg string) *Error
- type ErrorResponse
- type FieldError
Constants ¶
const ( CodeCommonValidationFailed = "VALIDATION_FAILED" CodeCommonForbidden = "FORBIDDEN" CodeCommonNotFound = "NOT_FOUND" CodeCommonConflict = "CONFLICT" CodeCommonNotAcceptable = "NOT_ACCEPTABLE" CodeCommonUnprocessableEntity = "UNPROCESSABLE_ENTITY" CodeCommonInternalError = "INTERNAL_ERROR" CodeCommonTooManyRequests = "TOO_MANY_REQUESTS" )
Common, domain-agnostic catalog codes. These mirror the codes documented as shared response examples in resources/openapi.yaml, and are the fallback used by NewErrorResponse when a handler hasn't been migrated to a more specific domain code via NewErrorResponseWithCode.
const ( CodeLLMProviderNotFound = "LLM_PROVIDER_NOT_FOUND" CodeLLMProviderExists = "LLM_PROVIDER_EXISTS" CodeLLMProviderLimitReached = "LLM_PROVIDER_LIMIT_REACHED" CodeLLMProviderAPIKeyNotFound = "LLM_PROVIDER_API_KEY_NOT_FOUND" CodeLLMProviderDeploymentValidationFailed = "LLM_PROVIDER_DEPLOYMENT_VALIDATION_FAILED" CodeLLMProxyNotFound = "LLM_PROXY_NOT_FOUND" CodeLLMProxyExists = "LLM_PROXY_EXISTS" CodeLLMProxyLimitReached = "LLM_PROXY_LIMIT_REACHED" CodeLLMProxyAPIKeyNotFound = "LLM_PROXY_API_KEY_NOT_FOUND" CodeLLMProviderRefNotFound = "LLM_PROVIDER_REF_NOT_FOUND" CodeLLMProxyDeploymentValidationFailed = "LLM_PROXY_DEPLOYMENT_VALIDATION_FAILED" CodeLLMProviderAPIKeyForbidden = "LLM_PROVIDER_API_KEY_FORBIDDEN" CodeLLMProxyAPIKeyForbidden = "LLM_PROXY_API_KEY_FORBIDDEN" )
LLM provider/proxy domain codes, matching the examples documented in resources/openapi.yaml for the corresponding operations.
const ( CodeLLMProviderTemplateNotFound = "LLM_PROVIDER_TEMPLATE_NOT_FOUND" CodeLLMProviderTemplateVersionNotFound = "LLM_PROVIDER_TEMPLATE_VERSION_NOT_FOUND" CodeLLMProviderTemplateRefNotFound = "LLM_PROVIDER_TEMPLATE_REF_NOT_FOUND" CodeLLMProviderTemplateExists = "LLM_PROVIDER_TEMPLATE_EXISTS" CodeLLMProviderTemplateVersionExists = "LLM_PROVIDER_TEMPLATE_VERSION_EXISTS" CodeLLMProviderTemplateManagedByReserved = "LLM_PROVIDER_TEMPLATE_MANAGED_BY_RESERVED" CodeLLMProviderTemplateInUse = "LLM_PROVIDER_TEMPLATE_IN_USE" CodeLLMProviderTemplateReadOnly = "LLM_PROVIDER_TEMPLATE_READ_ONLY" CodeLLMProviderTemplateNotToggleable = "LLM_PROVIDER_TEMPLATE_NOT_TOGGLEABLE" )
LLM provider template domain codes. The *_VERSION_* and *_REF_* codes keep one HTTP status per code in the apperror catalog: NOT_FOUND codes are 404s for the resource targeted by the URL, REF_NOT_FOUND codes are 400s for a resource referenced in the request body, and VERSION codes distinguish a missing/duplicate version from a missing/duplicate template.
const ( CodeGatewayNotFound = "GATEWAY_NOT_FOUND" CodeGatewayTokenNotFound = "GATEWAY_TOKEN_NOT_FOUND" CodeGatewayHasActiveDeployments = "GATEWAY_HAS_ACTIVE_DEPLOYMENTS" CodeGatewayNameConflict = "GATEWAY_NAME_CONFLICT" CodeGatewayTokenLimitReached = "GATEWAY_TOKEN_LIMIT_REACHED" )
Gateway domain codes, matching the examples documented in resources/openapi.yaml.
const ( CodeDeploymentBaseNotFound = "DEPLOYMENT_BASE_NOT_FOUND" CodeDeploymentRestoreConflict = "DEPLOYMENT_RESTORE_CONFLICT" CodeDeploymentNotFound = "DEPLOYMENT_NOT_FOUND" CodeDeploymentNotActive = "DEPLOYMENT_NOT_ACTIVE" CodeDeploymentGatewayMismatch = "DEPLOYMENT_GATEWAY_MISMATCH" CodeDeploymentActive = "DEPLOYMENT_ACTIVE" CodeDeploymentInvalidStatus = "DEPLOYMENT_INVALID_STATUS" )
Deployment domain codes, shared across REST API / LLM provider / LLM proxy / MCP proxy deployment operations (identical conditions across all four).
const ( CodeRESTAPINotFound = "REST_API_NOT_FOUND" CodeRESTAPIExists = "REST_API_EXISTS" CodeRESTAPIDeploymentValidationFailed = "REST_API_DEPLOYMENT_VALIDATION_FAILED" )
REST API domain codes.
const ( CodeMCPProxyNotFound = "MCP_PROXY_NOT_FOUND" CodeMCPProxyExists = "MCP_PROXY_EXISTS" CodeMCPProxyLimitReached = "MCP_PROXY_LIMIT_REACHED" CodeMCPProxyDeploymentValidationFailed = "MCP_PROXY_DEPLOYMENT_VALIDATION_FAILED" )
MCP proxy domain codes.
const ( CodeOrganizationNotFound = "ORGANIZATION_NOT_FOUND" CodeOrganizationExists = "ORGANIZATION_EXISTS" )
Organization domain codes.
const ( CodeProjectNotFound = "PROJECT_NOT_FOUND" CodeProjectRefNotFound = "PROJECT_REF_NOT_FOUND" CodeProjectExists = "PROJECT_EXISTS" )
Project domain codes. PROJECT_REF_NOT_FOUND is the 400 counterpart of PROJECT_NOT_FOUND for a project referenced in a request body (e.g. the projectId in a create request) rather than targeted by the URL.
const ( CodeApplicationNotFound = "APPLICATION_NOT_FOUND" CodeApplicationExists = "APPLICATION_EXISTS" )
Application domain codes.
const ( CodeSubscriptionNotFound = "SUBSCRIPTION_NOT_FOUND" CodeSubscriptionExists = "SUBSCRIPTION_EXISTS" CodeSubscriptionForbidden = "SUBSCRIPTION_FORBIDDEN" CodeSubscriptionPlanNotFound = "SUBSCRIPTION_PLAN_NOT_FOUND" CodeSubscriptionPlanExists = "SUBSCRIPTION_PLAN_EXISTS" )
Subscription domain codes.
const ( CodePolicyVersionConflict = "POLICY_VERSION_CONFLICT" CodePolicyInvalidState = "POLICY_INVALID_STATE" CodePolicyInUse = "POLICY_IN_USE" )
Custom policy domain codes.
const ( CodeSecretNotFound = "SECRET_NOT_FOUND" CodeSecretExists = "SECRET_EXISTS" CodeSecretInUse = "SECRET_IN_USE" )
Secret domain codes.
const ( CodeArtifactNotFound = "ARTIFACT_NOT_FOUND" CodeArtifactExists = "ARTIFACT_EXISTS" CodeArtifactReadOnly = "ARTIFACT_READ_ONLY" CodeArtifactRuntimeImmutable = "ARTIFACT_RUNTIME_IMMUTABLE" CodeArtifactDeployed = "ARTIFACT_DEPLOYED" )
Artifact domain codes. Used by flows that operate on a generic artifact reference (REST API / LLM provider / LLM proxy / MCP proxy) — API keys, subscriptions, and application associations — where the caller shouldn't need to know which concrete artifact kind was targeted, and by the data-plane-origin guard that protects DP-originated artifacts from control-plane mutation.
const ( CodeCustomPolicyNotFound = "CUSTOM_POLICY_NOT_FOUND" CodeCustomPolicyVersionNotFound = "CUSTOM_POLICY_VERSION_NOT_FOUND" )
Custom policy domain codes.
const ( CodeApplicationAPIKeyNotFound = "APPLICATION_API_KEY_NOT_FOUND" CodeApplicationAPIKeyForbidden = "APPLICATION_API_KEY_FORBIDDEN" )
Application API key domain codes (application-scoped API key mappings, distinct from the LLM provider/proxy API key codes above).
Variables ¶
var ( Forbidden = def(CodeCommonForbidden, http.StatusForbidden, "You do not have permission to perform this action.") ValidationFailed = def(CodeCommonValidationFailed, http.StatusBadRequest, "%s") NotFound = def(CodeCommonNotFound, http.StatusNotFound, "The requested resource could not be found.") Conflict = def(CodeCommonConflict, http.StatusConflict, "The request conflicts with the current state of the resource.") NotAcceptable = def(CodeCommonNotAcceptable, http.StatusNotAcceptable, "The requested media type is not supported.") UnprocessableEntity = def(CodeCommonUnprocessableEntity, http.StatusUnprocessableEntity, "The request could not be processed.") Internal = def(CodeCommonInternalError, http.StatusInternalServerError, "An unexpected error occurred.") TooManyRequests = def(CodeCommonTooManyRequests, http.StatusTooManyRequests, "%s") )
Common, domain-agnostic entries. Unauthorized is intentionally the ONLY 401 entry, with a fixed message: every authentication failure (missing, expired, invalid, revoked) returns the identical payload per the unified auth-failure rule in error-handling.md; the specific reason travels in the wrapped cause / log message only.
var ( RESTAPINotFound = def(CodeRESTAPINotFound, http.StatusNotFound, "The specified REST API could not be found.") // RESTAPIExists covers three distinct conflicts (handle, name+version, // project) — the call site supplies which one. RESTAPIExists = def(CodeRESTAPIExists, http.StatusConflict, "%s") RESTAPIDeploymentValidationFailed = def(CodeRESTAPIDeploymentValidationFailed, http.StatusBadRequest, "%s") )
REST API entries.
var ( LLMProviderNotFound = def(CodeLLMProviderNotFound, http.StatusNotFound, "The specified LLM provider could not be found.") LLMProviderRefNotFound = def(CodeLLMProviderRefNotFound, http.StatusBadRequest, "The referenced LLM provider could not be found.") LLMProviderExists = def(CodeLLMProviderExists, http.StatusConflict, "An LLM provider with this ID already exists.") LLMProviderLimitReached = def(CodeLLMProviderLimitReached, http.StatusConflict, "LLM provider limit reached for the organization.") LLMProviderAPIKeyNotFound = def(CodeLLMProviderAPIKeyNotFound, http.StatusNotFound, "The specified API key could not be found.") LLMProviderAPIKeyForbidden = def(CodeLLMProviderAPIKeyForbidden, http.StatusForbidden, "You do not have permission to access this API key.") LLMProviderDeploymentValidationFailed = def(CodeLLMProviderDeploymentValidationFailed, http.StatusBadRequest, "%s") LLMProxyNotFound = def(CodeLLMProxyNotFound, http.StatusNotFound, "The specified LLM proxy could not be found.") LLMProxyExists = def(CodeLLMProxyExists, http.StatusConflict, "An LLM proxy with this ID already exists.") LLMProxyLimitReached = def(CodeLLMProxyLimitReached, http.StatusConflict, "LLM proxy limit reached for the organization.") LLMProxyAPIKeyNotFound = def(CodeLLMProxyAPIKeyNotFound, http.StatusNotFound, "The specified API key could not be found.") LLMProxyAPIKeyForbidden = def(CodeLLMProxyAPIKeyForbidden, http.StatusForbidden, "You do not have permission to access this API key.") LLMProxyDeploymentValidationFailed = def(CodeLLMProxyDeploymentValidationFailed, http.StatusBadRequest, "%s") )
LLM provider / proxy entries.
var ( LLMProviderTemplateNotFound = def(CodeLLMProviderTemplateNotFound, http.StatusNotFound, "The specified LLM provider template could not be found.") LLMProviderTemplateVersionNotFound = def(CodeLLMProviderTemplateVersionNotFound, http.StatusNotFound, "The specified LLM provider template version could not be found.") LLMProviderTemplateRefNotFound = def(CodeLLMProviderTemplateRefNotFound, http.StatusBadRequest, "The referenced LLM provider template could not be found.") LLMProviderTemplateExists = def(CodeLLMProviderTemplateExists, http.StatusConflict, "An LLM provider template with this ID already exists.") LLMProviderTemplateVersionExists = def(CodeLLMProviderTemplateVersionExists, http.StatusConflict, "This template version already exists.") LLMProviderTemplateManagedByReserved = def(CodeLLMProviderTemplateManagedByReserved, http.StatusBadRequest, "'wso2' is reserved and cannot be used as managedBy on custom templates.") LLMProviderTemplateInUse = def(CodeLLMProviderTemplateInUse, http.StatusConflict, "This template version is in use by one or more providers.") LLMProviderTemplateReadOnly = def(CodeLLMProviderTemplateReadOnly, http.StatusForbidden, "Built-in templates are read-only and cannot be modified.") LLMProviderTemplateNotToggleable = def(CodeLLMProviderTemplateNotToggleable, http.StatusForbidden, "Only built-in templates can be enabled or disabled.") )
LLM provider template entries.
var ( GatewayNotFound = def(CodeGatewayNotFound, http.StatusNotFound, "The specified gateway could not be found.") GatewayTokenNotFound = def(CodeGatewayTokenNotFound, http.StatusNotFound, "The specified gateway token could not be found.") GatewayHasActiveDeployments = def(CodeGatewayHasActiveDeployments, http.StatusConflict, "The gateway has active deployments and cannot be deleted.") GatewayNameConflict = def(CodeGatewayNameConflict, http.StatusConflict, "A gateway with this name already exists.") GatewayTokenLimitReached = def(CodeGatewayTokenLimitReached, http.StatusConflict, "Gateway token limit reached.") )
Gateway entries.
var ( DeploymentBaseNotFound = def(CodeDeploymentBaseNotFound, http.StatusNotFound, "The specified base deployment could not be found.") DeploymentRestoreConflict = def(CodeDeploymentRestoreConflict, http.StatusConflict, "Cannot restore the currently deployed deployment, or the deployment is invalid.") DeploymentNotFound = def(CodeDeploymentNotFound, http.StatusNotFound, "The specified deployment could not be found.") DeploymentNotActive = def(CodeDeploymentNotActive, http.StatusConflict, "No active deployment found for this %s on the gateway.") DeploymentGatewayMismatch = def(CodeDeploymentGatewayMismatch, http.StatusBadRequest, "Deployment is bound to a different gateway.") DeploymentActive = def(CodeDeploymentActive, http.StatusConflict, "Cannot delete an active deployment - undeploy it first.") DeploymentInvalidStatus = def(CodeDeploymentInvalidStatus, http.StatusBadRequest, "The specified deployment status filter is invalid.") )
Deployment entries, shared across REST API / LLM provider / LLM proxy / MCP proxy deployment operations. DeploymentNotActive's verb is the artifact kind, e.g. "API", "LLM provider".
var ( MCPProxyNotFound = def(CodeMCPProxyNotFound, http.StatusNotFound, "The specified MCP proxy could not be found.") MCPProxyExists = def(CodeMCPProxyExists, http.StatusConflict, "An MCP proxy with this ID already exists.") MCPProxyLimitReached = def(CodeMCPProxyLimitReached, http.StatusConflict, "MCP proxy limit reached for the organization.") MCPProxyDeploymentValidationFailed = def(CodeMCPProxyDeploymentValidationFailed, http.StatusBadRequest, "%s") )
MCP proxy entries.
var ( OrganizationNotFound = def(CodeOrganizationNotFound, http.StatusNotFound, "The specified organization could not be found.") OrganizationExists = def(CodeOrganizationExists, http.StatusConflict, "An organization with this name already exists.") ProjectNotFound = def(CodeProjectNotFound, http.StatusNotFound, "The specified project could not be found.") ProjectRefNotFound = def(CodeProjectRefNotFound, http.StatusBadRequest, "The referenced project could not be found.") ProjectExists = def(CodeProjectExists, http.StatusConflict, "A project with this name already exists in the organization.") ApplicationNotFound = def(CodeApplicationNotFound, http.StatusNotFound, "The specified application could not be found.") ApplicationExists = def(CodeApplicationExists, http.StatusConflict, "An application with this name already exists.") )
Organization / project / application entries.
var ( SubscriptionNotFound = def(CodeSubscriptionNotFound, http.StatusNotFound, "The specified subscription could not be found.") SubscriptionExists = def(CodeSubscriptionExists, http.StatusConflict, "A subscription for this application and API already exists.") SubscriptionForbidden = def(CodeSubscriptionForbidden, http.StatusForbidden, "You do not have permission to access this subscription.") SubscriptionPlanNotFound = def(CodeSubscriptionPlanNotFound, http.StatusNotFound, "The specified subscription plan could not be found.") SubscriptionPlanExists = def(CodeSubscriptionPlanExists, http.StatusConflict, "A subscription plan with this name already exists.") )
Subscription entries.
var ( PolicyVersionConflict = def(CodePolicyVersionConflict, http.StatusConflict, "The policy version conflicts with an existing version.") PolicyInvalidState = def(CodePolicyInvalidState, http.StatusConflict, "The policy is not in a valid state for this operation.") PolicyInUse = def(CodePolicyInUse, http.StatusConflict, "The policy is in use and cannot be deleted.") CustomPolicyNotFound = def(CodeCustomPolicyNotFound, http.StatusNotFound, "The specified policy could not be found.") CustomPolicyVersionNotFnd = def(CodeCustomPolicyVersionNotFound, http.StatusNotFound, "The specified policy version could not be found.") )
Custom policy entries.
var ( SecretNotFound = def(CodeSecretNotFound, http.StatusNotFound, "The specified secret could not be found.") SecretExists = def(CodeSecretExists, http.StatusConflict, "A secret with this name already exists.") SecretInUse = def(CodeSecretInUse, http.StatusConflict, "The secret is in use and cannot be deleted.") )
Secret entries.
var ( ArtifactNotFound = def(CodeArtifactNotFound, http.StatusNotFound, "The specified artifact could not be found.") ArtifactExists = def(CodeArtifactExists, http.StatusConflict, "The artifact already exists.") ArtifactReadOnly = def(CodeArtifactReadOnly, http.StatusForbidden, "%s") ArtifactRuntimeImmutable = def(CodeArtifactRuntimeImmutable, http.StatusForbidden, "%s") ArtifactDeployed = def(CodeArtifactDeployed, http.StatusConflict, "%s") )
Artifact entries — generic artifact-reference flows and the data-plane-origin guard. The guard produces client-appropriate messages at the call site, so these are passthrough templates.
var ( ApplicationAPIKeyNotFound = def(CodeApplicationAPIKeyNotFound, http.StatusNotFound, "The specified API key could not be found.") ApplicationAPIKeyForbidden = def(CodeApplicationAPIKeyForbidden, http.StatusForbidden, "You do not have permission to access this API key.") )
Application API key entries.
Functions ¶
func NewValidationErrorResponse ¶
func NewValidationErrorResponse(w http.ResponseWriter, err error)
NewValidationErrorResponse writes a 400 JSON error response for validation errors.
func WriteHTTP ¶
func WriteHTTP(w http.ResponseWriter, e *Error, trackingID string)
WriteHTTP is the one place in the codebase that serializes an *Error onto an http.ResponseWriter as the standard ErrorResponse shape. Both the error mapper (middleware.MapErrors) and the pre-routing auth middleware call it, so the wire format cannot drift between the two paths.
trackingID, when non-empty, is echoed in the response body so the client can quote it back for log correlation; the mapper passes it only for 5xx responses. Callers with no correlation ID to expose pass "".
Types ¶
type Def ¶
type Def struct {
Code string // stable, client-visible catalog code, e.g. "REST_API_EXISTS"
HTTPStatus int
MessageFmt string // client-facing message template; may contain fmt verbs
}
Def is a catalog entry: the code, HTTP status, and client-facing message template are bound together at declaration time (see catalog.go) so call sites cannot pair a code with the wrong status or a drifting message. It is the Go equivalent of a Java ExceptionCodes enum constant. Errors are created only through Def.New / Def.Wrap (plus NewValidation for validator.ValidationErrors), never by picking a code/status/message triple at the call site.
func (Def) Is ¶
Is reports whether err is (or wraps) an *Error carrying this Def's code, letting callers branch on catalog entries without sentinel errors.
func (Def) New ¶
New instantiates the catalog entry for a business-rule failure with no underlying error. args fill the MessageFmt verbs, if any; they must be user-supplied values (names, handles, IDs), never internal error text. The call stack is captured automatically.
func (Def) Wrap ¶
Wrap instantiates the catalog entry for a failure caused by a lower-level error (DB, IO, downstream call). The cause is a required positional parameter — not a fluent afterthought — so it cannot be forgotten; it feeds the mapper's log line and errors.Is/As chains, and is never sent to the client.
type Error ¶
type Error struct {
Code string // catalog code, e.g. CodeCommonValidationFailed
HTTPStatus int // status to write
Message string // client-facing message
FieldErrors []FieldError // optional, for validation failures
Details any // optional structured metadata
LogMessage string // internal-only detail; never sent to client
Cause error // wrapped original error, for logging/unwrapping
Stack []uintptr // captured at construction time, see New
}
Error carries everything the error mapper needs to log a failure and write the client-facing ErrorResponse: the catalog code and HTTP status, the sanitized client message, optional field-level validation errors and structured details, plus internal-only diagnostics (log message, wrapped cause, and the call stack captured at construction time).
func NewValidation ¶
NewValidation converts a request-validation failure into an Error, mirroring NewValidationErrorResponse: validator.ValidationErrors become field-level errors under VALIDATION_FAILED; anything else maps to the generic "Invalid input." message with the original error kept as the cause for internal logging.
func (*Error) Error ¶
Error satisfies the error interface. It intentionally includes only the code and client message — internal diagnostics stay in LogMessage/Cause.
func (*Error) Origin ¶
Origin symbolizes just the first frame of the stack captured at construction time — the file:line of the Def.New/Def.Wrap call site where the error actually originated, e.g. "/internal/handler/application.go:142". The path is trimmed to start at "/internal/" so logs carry a short, repo-relative path instead of the full build-machine absolute path. This is distinct from slog's own "source" attribute, which always points at the mapper's log call site (middleware/error_mapper.go) regardless of where the error was created, and is therefore useless for locating the throw site. Returns "" if no stack was captured.
func (*Error) StackString ¶
StackString symbolizes the stack captured at construction time. Kept as raw []uintptr on the struct (cheap to capture) and only symbolized lazily when the mapper actually logs it, rather than paying runtime.CallersFrames cost on every construction.
func (*Error) WithDetails ¶
WithDetails attaches optional structured metadata surfaced to the client in the details field.
func (*Error) WithFieldErrors ¶
func (e *Error) WithFieldErrors(fe []FieldError) *Error
WithFieldErrors attaches field-level validation errors surfaced to the client in the errors[] array.
func (*Error) WithLogMessage ¶
WithLogMessage overrides the default internal-only detail message (see newWithSkip) that the mapper logs but never sends to the client.
type ErrorResponse ¶
type ErrorResponse struct {
Status string `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
Errors []FieldError `json:"errors,omitempty"`
Details any `json:"details,omitempty"`
// TrackingID correlates a 5xx response with its server-side log line (a
// bare UUID, no source markers). Set only for 5xx responses — 4xx
// responses already tell the client exactly what was wrong.
TrackingID string `json:"trackingId,omitempty"`
}
ErrorResponse is the standard error response shape mandated by APR-004: { status, code, message, errors[], details }. status is always "error"; code is a stable, machine-readable string from the error catalog (see codes.go). Details carries optional structured metadata whose shape is specific to the code (e.g. the resources referencing a secret that blocked its deletion) — it is not a substitute for errors[], which is reserved for field-level validation failures.
func NewErrorResponse ¶
func NewErrorResponse(httpStatus int, title string, description ...string) ErrorResponse
NewErrorResponse builds a standard ErrorResponse for the given HTTP status. title is retained for call-site compatibility but is no longer surfaced in the response body; the catalog code is derived from httpStatus (see codeForStatus above), and description[0], when given, becomes the human-readable message. Handlers that need a specific catalog code should use NewErrorResponseWithCode instead.
func NewErrorResponseWithCode ¶
func NewErrorResponseWithCode(code, message string) ErrorResponse
NewErrorResponseWithCode builds a standard ErrorResponse using an explicit catalog code (see codes.go), for handlers that know the precise failure reason rather than just the HTTP status.
type FieldError ¶
FieldError describes a single field-level validation failure.