Skip to content

Commit 0e537b3

Browse files
authored
Add labeled and sentinel errors (#9)
1 parent 169a3a2 commit 0e537b3

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

error.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,69 @@ func (se structuredError) MarshalText() ([]byte, error) {
226226
func (se structuredError) MarshalJSON() ([]byte, error) {
227227
return json.Marshal(se.err.Error())
228228
}
229+
230+
// SentinelError is a constant error.
231+
type SentinelError string
232+
233+
// Error returns error message.
234+
func (e SentinelError) Error() string {
235+
return string(e)
236+
}
237+
238+
// LabeledError adds indicative errors to an error wrap.
239+
//
240+
// Labels could be checked with errors.Is, errors.As.
241+
// Error message remains the same with original error.
242+
func LabeledError(err error, labels ...error) error {
243+
return labeledError{
244+
err: err,
245+
labels: labels,
246+
}
247+
}
248+
249+
type labeledError struct {
250+
err error
251+
labels []error
252+
}
253+
254+
// Error returns message.
255+
func (le labeledError) Error() string {
256+
return le.err.Error()
257+
}
258+
259+
// Is returns true if err matches original error or any of labels.
260+
func (le labeledError) Is(err error) bool {
261+
if errors.Is(le.err, err) {
262+
return true
263+
}
264+
265+
for _, l := range le.labels {
266+
if errors.Is(err, l) {
267+
return true
268+
}
269+
}
270+
271+
return false
272+
}
273+
274+
// As returns true if original error or any of labels can be assigned to v.
275+
//
276+
// If multiple assignations are possible, only first one is performed.
277+
func (le labeledError) As(v interface{}) bool {
278+
if errors.As(le.err, v) {
279+
return true
280+
}
281+
282+
for _, l := range le.labels {
283+
if errors.As(l, v) {
284+
return true
285+
}
286+
}
287+
288+
return false
289+
}
290+
291+
// Unwrap returns original error.
292+
func (le labeledError) Unwrap() error {
293+
return le.err
294+
}

error_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,28 @@ func BenchmarkWrapError(b *testing.B) {
222222
}
223223
ctxd.LogError(ctx, err, logFunc)
224224
}
225+
226+
func TestSentinelError_Error(t *testing.T) {
227+
assert.EqualError(t, ctxd.SentinelError("failed"), "failed")
228+
}
229+
230+
func TestLabeledError(t *testing.T) {
231+
err1 := errors.New("failed")
232+
label1 := ctxd.SentinelError("miserably")
233+
label2 := ctxd.SentinelError("hopelessly")
234+
235+
err := ctxd.LabeledError(fmt.Errorf("oops: %w", err1), label1, label2)
236+
237+
assert.True(t, errors.Is(err, err1))
238+
assert.True(t, errors.Is(err, label1))
239+
assert.True(t, errors.Is(err, label2))
240+
241+
// Labels do not implicitly contribute to error message.
242+
assert.Equal(t, "oops: failed", err.Error())
243+
244+
// If there are two matches, only first is returned.
245+
var se ctxd.SentinelError
246+
247+
assert.True(t, errors.As(err, &se))
248+
assert.Equal(t, "miserably", string(se))
249+
}

example_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,32 @@ func ExampleWrapError() {
9292
// error: something failed {"field1":1,"field2":"abc","field3":3}
9393
// warn: wrapped: something failed {"field1":1,"field2":"abc","field3":3,"field4":true,"field5":"V"}
9494
}
95+
96+
func ExampleLabeledError() {
97+
err1 := errors.New("failed")
98+
label1 := ctxd.SentinelError("miserably")
99+
label2 := ctxd.SentinelError("hopelessly")
100+
101+
err := ctxd.LabeledError(fmt.Errorf("oops: %w", err1), label1, label2)
102+
103+
fmt.Println("err is err1:", errors.Is(err, err1))
104+
fmt.Println("err is label1:", errors.Is(err, label1))
105+
fmt.Println("err is label2:", errors.Is(err, label2))
106+
107+
// Labels do not implicitly contribute to error message.
108+
fmt.Println("error message:", err.Error())
109+
110+
// If there are two matches, only first is returned.
111+
var se ctxd.SentinelError
112+
113+
fmt.Println("err as &se:", errors.As(err, &se))
114+
fmt.Println("err as value:", string(se))
115+
116+
// Output:
117+
// err is err1: true
118+
// err is label1: true
119+
// err is label2: true
120+
// error message: oops: failed
121+
// err as &se: true
122+
// err as value: miserably
123+
}

0 commit comments

Comments
 (0)