-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfail.go
More file actions
155 lines (138 loc) · 4.91 KB
/
Copy pathfail.go
File metadata and controls
155 lines (138 loc) · 4.91 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
package durable
import "errors"
// FailureReasoner may be implemented by any error in a handler's error
// chain to carry a machine-readable failure reason. Reasons should be
// short, low-cardinality slugs ("invalid-image", "insufficient-capacity"):
// their destiny is metrics labels and alert routing. Human-readable detail
// belongs in the error message.
//
// The engine extracts the reason with errors.As at resolution time — for
// permanent failures (recorded on the Failure) and for ordinary
// retryable errors (recorded as the Run's LastReason). An explicit
// WithReason on Fail takes precedence over the chain.
type FailureReasoner interface {
FailureReason() string
}
// FailureKinder may be implemented by any error in a handler's error chain
// to attribute permanent failures at the point where the error is created,
// keeping resolution sites down to a plain Fail(err). An explicit
// WithUserKind on Fail takes precedence over the chain.
type FailureKinder interface {
FailureKind() FailureKind
}
// FailOption annotates a permanent failure declared with Fail.
type FailOption func(*permanentError)
// WithUserKind attributes the permanent failure to the request or intent
// (FailureKindUser). Without it, the kind comes from the first
// FailureKinder in the error chain, defaulting to FailureKindSystem.
func WithUserKind() FailOption {
return func(e *permanentError) {
e.kind = FailureKindUser
e.kindSet = true
}
}
// WithReason sets the machine-readable failure reason, overriding any
// FailureReasoner in the error chain. Keep reasons short, lowercase,
// low-cardinality slugs.
func WithReason(reason string) FailOption {
return func(e *permanentError) {
e.reason = reason
}
}
// Fail marks err as a permanent operation failure. It is the only
// permanent-failure mechanism; options attach attribution.
//
// Returned from a forward handler, it resolves the current operation as
// permanently failed, establishes the Run's Failure, and begins unwind.
// Returned from an Unwind handler, it records a permanent Failure and
// unwind continues with earlier eligible Steps.
//
// Any other non-nil error means the operation remains unresolved and is
// retried.
func Fail(err error, opts ...FailOption) error {
if err == nil {
err = errors.New("unspecified permanent failure")
}
pe := &permanentError{err: err}
for _, o := range opts {
o(pe)
}
return pe
}
type permanentError struct {
err error
kind FailureKind
kindSet bool
reason string
}
func (e *permanentError) Error() string {
return "durable: permanent failure: " + e.err.Error()
}
func (e *permanentError) Unwrap() error { return e.err }
// failureKind resolves the attribution: explicit option, then the error
// chain, then the system default.
func (e *permanentError) failureKind() FailureKind {
if e.kindSet {
return e.kind
}
// Not errors.AsType: FailureKinder is a plain interface, not an error,
// and AsType's constraint requires E to implement error.
var fk FailureKinder
if errors.As(e.err, &fk) {
return fk.FailureKind()
}
return FailureKindSystem
}
// failureReason resolves the reason: explicit option, then the error chain.
func (e *permanentError) failureReason() string {
if e.reason != "" {
return e.reason
}
return reasonOf(e.err)
}
// reasonOf extracts a FailureReasoner reason from an error chain.
func reasonOf(err error) string {
var fr FailureReasoner
if err != nil && errors.As(err, &fr) {
return fr.FailureReason()
}
return ""
}
// FailureInfo reports whether a handler's returned error declares
// permanent failure via Fail and, if so, the attribution it resolves
// to — the kind and reason that will reach the Run's Failure.
// Middleware wrapping handlers use it to label spans and metrics with
// the same attribution the engine will commit.
func FailureInfo(err error) (kind FailureKind, reason string, ok bool) {
pe, ok := asPermanent(err)
if !ok {
return FailureKindSystem, "", false
}
return pe.failureKind(), pe.failureReason(), true
}
// FailureCause reports whether err declares permanent failure via Fail
// and, if so, the error Fail wrapped — the cause whose message the
// engine records on the failure record, without the permanent-failure
// prefix.
func FailureCause(err error) (cause error, ok bool) {
pe, ok := asPermanent(err)
if !ok {
return nil, false
}
return pe.err, true
}
// FailureReason reports the reason carried by the first FailureReasoner
// in err's chain, or "" when there is none. It applies to ordinary
// retryable errors as well as permanent ones: the engine records it as
// the Run's LastReason between retries. For a Fail resolution,
// FailureInfo's reason additionally honors WithReason.
func FailureReason(err error) string {
return reasonOf(err)
}
// asPermanent reports whether err declares permanent failure via Fail.
func asPermanent(err error) (*permanentError, bool) {
if pe, ok := errors.AsType[*permanentError](err); ok {
return pe, true
}
return nil, false
}