Repository navigation
Expand file tree
/
Copy pathdoc.go
More file actions
222 lines (222 loc) · 13 KB
/
Copy pathdoc.go
File metadata and controls
222 lines (222 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Package errors defines the foundation for describing failures across
// domain, application, infrastructure and transport boundaries.
//
// The first implementation covers classification, safe public projection,
// immutable metadata and condition/cause preservation. CONTRACT.md records the
// concrete API choices and scenarios E01-E10, exercised in spec_test.go.
//
// # Why this package comes first
//
// A domain must be able to explain a refusal without knowing which database,
// logger or transport will handle it. An adapter must be able to translate a
// dependency failure without making callers understand that dependency. The
// shared vocabulary gives both a small, common language.
//
// This is a leaf package: standard-library dependencies only, no imports from
// other project packages, no I/O, configuration, registration or background work.
// Logging, provenance and transport may depend on errors. Errors must not depend
// on them. Request IDs and trace IDs are attached by the observing boundary;
// capturing a context.Context or an active span in an error is outside this scope.
//
// # Three different questions
//
// Kind answers "what category of failure is this?" It is a small closed vocabulary
// shared across modules and backend blueprints.
//
// Type answers "which particular condition occurred?" It is an optional, stable
// machine-readable string owned by the module defining the condition, such as
// "account.email_taken". The name Type follows the existing frontend vocabulary;
// it is independent of Go's concrete type system. Changing a published Type can
// break consumers. Messages are prose and must never be used as identifiers.
//
// A sentinel or concrete error type answers "which local case can this caller
// handle?" Go callers use standard errors.Is and errors.As. A shared Kind does
// not replace domain-specific cases, and this package does not own a catalog of
// every domain's failures. Equal Kind, Type or message text alone does not make
// two independently constructed errors the same sentinel.
//
// # Initial kinds
//
// Start with the following meanings. Wire status codes belong to transports.
//
// - Invalid: input fails validation; field problems may explain corrections.
// - NotFound: a required resource does not exist or is not visible to this caller.
// - Conflict: the operation conflicts with current state or a uniqueness rule.
// - Unauthenticated: acceptable authentication is absent or invalid.
// - Forbidden: the caller is not permitted to perform the operation.
// - RateLimited: an applicable rate or usage limit currently refuses the request.
// - Unavailable: a capability needed by the operation is currently unavailable.
// - Timeout: the operation's allowed time elapsed; completion may be uncertain.
// - Canceled: the operation was canceled; completion may be uncertain.
// - Internal: an explicitly classified unexpected failure.
//
// Their textual forms are lower snake case, including "not_found" and
// "rate_limited". Transport numbers are not enum values. Add a Kind only when
// it expresses a distinct shared meaning with a concrete caller; use a domain
// Type or local error case for a more specific rule. Preconditions and richer
// validation categories can be considered when a real contract needs them.
//
// A foreign error is unclassified until a boundary recognizes it. Presentation
// falls back to Internal for such failures, but inspection must not pretend that
// a classification was supplied. Wrapped context.Canceled and
// context.DeadlineExceeded are recognized as Canceled and Timeout when there is
// no explicit outer classification. Cancellation of an unrelated context does
// not establish the meaning of the returned error.
//
// # Public information and private diagnostics
//
// Error carries a Kind, optional Type, public message, public field
// problems, private diagnostic details and an optional underlying cause. Fields
// map input names to safe explanations. Details describe operator context; they
// are not a second bag of response fields.
//
// Error() is diagnostic text, not a response message. Causes may contain driver
// messages, SQL or upstream response text. Never serialize the error object or
// its causal chain as a public response. A transport deliberately projects only
// the public vocabulary and adds its own correlation information.
//
// Public accessors return "internal error", no Type and no Fields for Internal
// or unclassified failures. A classified non-Internal failure exposes only the
// message, Type and Fields explicitly designated public by its owner; an empty
// public message falls back to "request failed". For nil, the message and Type
// are empty and Fields are absent: there is no failure to present.
//
// Public messages and field problems must not contain secrets or implementation
// details. Marking data private does not make it safe to log: avoid collecting
// secrets, and apply the logger's redaction policy at the observing boundary.
// Diagnostics must never determine the public classification or response fields.
//
// # Construct, annotate, translate
//
// Construction creates a failure with deliberate public meaning. A domain can
// define an immutable sentinel using the shared Error, or implement its own
// concrete error type and translate it at an owned boundary. Not every local
// error must be converted into the shared struct at its point of creation.
//
// Annotation adds diagnostic context while preserving Kind, Type, public data,
// sentinel identity and the original cause. Ordinary wrapping is sufficient for
// textual context:
//
// if err != nil {
// return fmt.Errorf("register account: %w", err)
// }
//
// Translation deliberately assigns new public meaning at a boundary. For
// example, an adapter may recognize a uniqueness violation as a domain's
// email-taken condition. It must preserve that domain identity and retain the
// original failure for diagnostics. A driver message alone is not the public
// explanation. Exposing a driver through Unwrap also makes it discoverable via
// errors.As; higher layers must still use the declared domain contract.
//
// The outermost explicit classification on a single causal chain wins. Its Kind,
// Type, message and Fields are selected together; missing public metadata is not
// filled from inner causes. Annotation must not accidentally reclassify an error
// by restating a guessed Kind. After deliberate translation, errors.Is may still
// match a retained inner sentinel; matching a cause and selecting the current
// public classification are different questions.
//
// Errors are immutable after construction. Fluent WithType, WithField(s) and
// WithDetail(s) operations produce independent values and preserve the identity
// of the error they enrich. They copy supplied maps; accessors return copies.
// Replacing a metadata key affects only the derived value, with the new value
// winning. Concurrent callers may safely reuse a sentinel without sharing
// mutable metadata. Adding context must not silently break errors.Is.
//
// # Go surface and nil behavior
//
// Keep ordinary (T, error) returns and compatibility with standard errors.Is,
// errors.As, errors.Unwrap and fmt.Errorf with %w. The implemented surface is:
//
// Kind, Error shared category and structured failure
// New(kind, message) create a non-nil *Error
// Wrap(err, kind, message) explicitly classify a cause; return error
// WithType / WithField(s) derive a value with deliberate public metadata
// WithDetail(s) derive a value with private metadata
// WithCause construct an occurrence with a replacement source
// KindOf(err) (Kind, bool) inspect an available classification
// IsKind(err, kind) bool test a recognized category
// Public copy the complete safe public projection
// Message / TypeOf / FieldsOf read individual public values by the same policy
//
// WithCause retains sentinel identity while assigning a diagnostic source to a
// new occurrence. It deliberately replaces any existing source on the template.
// A nil cause still constructs a failure; Wrap is the nil-preserving propagation
// helper. Kinds, Kind.String and ParseKind expose the stable vocabulary.
// DetailsOf copies one selected diagnostic frame without flattening inner causes.
//
// New always creates an error. Wrap and any later annotation helper return a
// literal nil error interface for a nil input; they do not manufacture a failure
// while adding context. Such helpers return error rather than *Error so ordinary
// propagation does not introduce a typed-nil interface. Constructors and callers
// must not return typed-nil errors; arbitrary foreign typed-nil values are not
// normalized by reflection.
//
// KindOf returns ok=false for nil or an unclassified failure. Its Kind result is
// not meaningful unless ok is true. IsKind is false for nil and unclassified
// failures, including when asked about Internal. Callers distinguish success
// with err == nil; the transport applies its Internal fallback only to a real
// failure. This keeps an absent error from satisfying an Internal assertion.
//
// Public returns (PublicInfo, present). Unlike KindOf's classification boolean,
// present reports whether the input error interface is non-nil. An unknown
// failure, including a typed nil inside a non-nil interface, receives the safe
// Internal snapshot. The snapshot's fields are copied and its Kind is a shared
// enum; transport encoding belongs to the eventual adapter.
//
// # Joined errors and partial outcomes
//
// A causal chain describes one failure. A join can describe several independent
// failures; it does not acquire a single classification from the first branch.
// Single-failure accessors stop at an unclassified branching node and use their
// unclassified behavior. An explicit classification outside the join can provide
// a deliberate summary while retaining the complete underlying error tree.
// Standard errors.Is and errors.As remain free to inspect that tree.
//
// Do not merge public fields or private details from unrelated branches into one
// map. The initial package does not offer batch classification, a flattened
// diagnostic view or an aggregate retry predicate. A future batch contract must
// preserve item identity and unknown failures, define ordering, and account for
// partial success. An unknown branch cannot be discarded as harmless.
//
// # Ownership of recovery and observation
//
// Kinds describe failures; they do not authorize retries. RateLimited,
// Unavailable and Timeout can inform a recovery policy, but the operation owner
// decides whether repetition is safe, whether a write may already have committed,
// and what budget remains. Cancellation normally stops the canceled operation.
// Backoff, retry schedules, idempotency and dead-letter decisions live outside
// this package. A timeout or cancellation is not evidence of rollback.
//
// Lower layers return errors with useful context. The boundary that handles a
// failure owns its observation; propagating layers do not each log the same
// failure. The errors package neither logs nor assigns log levels, HTTP status,
// WebSocket close codes, tracing status or metric labels. Those mappings need
// their own contracts, including how expected refusals are observed.
//
// Empty query results and valid domain outcomes remain successful values. A
// missing required record, a refused transition, a failed read and an uncertain
// write are distinct outcomes where they require different caller behavior.
// Errors represent failed operations and refusals, not every interesting state.
// Panics remain outside the expected-failure vocabulary; recovery belongs at
// process or transport boundaries that can actually handle them.
//
// # Scenarios for the first implementation
//
// - Nil stays nil through wrapping and never matches a Kind.
// - A foreign failure remains unclassified and presents no private information.
// - Context cancellation and deadlines survive ordinary wrapping.
// - Annotation preserves classification, identity and errors.Is/As traversal.
// - Translation changes the public classification and preserves its causes.
// - Enriching a sentinel preserves its identity without mutating another use.
// - Changing a supplied or returned map cannot mutate an existing error.
// - Internal failures conceal their message, Type and Fields in public accessors.
// - Reordering joined branches does not select a different public failure.
// - Joining an unknown failure with a transient one does not imply safe retry.
//
// Comparable scenarios should guide the Rust and NestJS contracts. Go's wrapping
// mechanics do not prescribe their representation: Rust can retain typed Result
// errors and NestJS can retain failure values with exceptions at framework edges.
// Cross-language parity concerns meaning, disclosure and ownership. HTTP problem
// envelopes, WebSocket error messages and client mappings are subsequent adapter
// contracts; documenting this package does not establish frontend integration.
package errors