Skip to content

Commit e8e21bc

Browse files
searKinghaixinchen
authored and
haixinchen
committed
errors: add errors.Mark to support error marks for errors.Is and '%v' in fmt, without any side effect otherwise
1 parent 16d6a52 commit e8e21bc

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

src/errors/mark.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package errors
6+
7+
import (
8+
"fmt"
9+
"io"
10+
)
11+
12+
var _ error = markError{} // verify that Error implements error
13+
14+
// Mark returns an error with the supplied errors as marks.
15+
// If err is nil, return nil.
16+
// marks take effects only when Is and '%v' in fmt.
17+
// Is returns true if err or any marks match the target.
18+
func Mark(err error, marks ...error) error {
19+
if err == nil {
20+
return nil
21+
}
22+
if len(marks) == 0 {
23+
return err
24+
}
25+
me := markError{
26+
err: err,
27+
marks: marks,
28+
}
29+
return me
30+
}
31+
32+
type markError struct {
33+
err error // visual error
34+
marks []error // hidden errors as marks, take effects only when Is and '%v' in fmt.
35+
}
36+
37+
func (e markError) Error() string {
38+
if e.err == nil {
39+
return ""
40+
}
41+
return e.err.Error()
42+
}
43+
44+
func (e markError) Format(s fmt.State, verb rune) {
45+
if e.err == nil {
46+
return
47+
}
48+
switch verb {
49+
case 'v':
50+
if s.Flag('+') {
51+
me := e.clean()
52+
if len(me.marks) == 0 {
53+
_, _ = fmt.Fprintf(s, "%+v", me.err)
54+
return
55+
}
56+
_, _ = io.WriteString(s, "Marked errors occurred:\n")
57+
58+
_, _ = fmt.Fprintf(s, "|\t%+v", me.err)
59+
for _, mark := range me.marks {
60+
_, _ = fmt.Fprintf(s, "\nM\t%+v", mark)
61+
}
62+
return
63+
}
64+
fallthrough
65+
case 's', 'q':
66+
_, _ = io.WriteString(s, e.Error())
67+
}
68+
}
69+
70+
// clean removes all none nil elem in all the marks
71+
func (e markError) clean() markError {
72+
var marks []error
73+
for _, err := range e.marks {
74+
if err != nil {
75+
marks = append(marks, err)
76+
}
77+
}
78+
return markError{
79+
err: e.err,
80+
marks: marks,
81+
}
82+
}
83+
84+
// Is reports whether any error in markError or it's mark errors matches target.
85+
func (e markError) Is(target error) bool {
86+
if Is(e.err, target) {
87+
return true
88+
}
89+
for _, err := range e.marks {
90+
if Is(err, target) {
91+
return true
92+
}
93+
}
94+
return false
95+
}
96+
97+
// Unwrap returns the error in e, if there is exactly one. If there is more than one
98+
// error, Unwrap returns nil, since there is no way to determine which should be
99+
// returned.
100+
func (e markError) Unwrap() error {
101+
return e.err
102+
}

src/errors/mark_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package errors_test
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
)
11+
12+
func ExampleMark() {
13+
err := errors.Mark(nil, nil)
14+
fmt.Println(err)
15+
fmt.Println("-----")
16+
err = errors.Mark(fmt.Errorf("whoops"), nil)
17+
fmt.Println(err)
18+
fmt.Println("-----")
19+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"))
20+
fmt.Println(err)
21+
fmt.Println("-----")
22+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar"))
23+
fmt.Println(err)
24+
fmt.Println("-----")
25+
26+
// Output:
27+
// <nil>
28+
// -----
29+
// whoops
30+
// -----
31+
// whoops
32+
// -----
33+
// whoops
34+
// -----
35+
}
36+
37+
func ExampleMark_format() {
38+
err := errors.Mark(nil)
39+
fmt.Printf("v: %v\n", err)
40+
fmt.Printf("+v: %+v\n", err)
41+
fmt.Println("-----")
42+
43+
err = errors.Mark(fmt.Errorf("whoops"), nil)
44+
fmt.Printf("v: %v\n", err)
45+
fmt.Printf("+v: %+v\n", err)
46+
fmt.Println("-----")
47+
48+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"))
49+
fmt.Printf("v: %v\n", err)
50+
fmt.Printf("+v: %+v\n", err)
51+
fmt.Println("-----")
52+
53+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar"))
54+
fmt.Printf("v: %v\n", err)
55+
fmt.Printf("+v: %+v\n", err)
56+
fmt.Println("-----")
57+
58+
// Output:
59+
// v: <nil>
60+
// +v: <nil>
61+
// -----
62+
// v: whoops
63+
// +v: whoops
64+
// -----
65+
// v: whoops
66+
// +v: Marked errors occurred:
67+
// | whoops
68+
// M foo
69+
// -----
70+
// v: whoops
71+
// +v: Marked errors occurred:
72+
// | whoops
73+
// M foo
74+
// M bar
75+
// -----
76+
}
77+
78+
func ExampleMark_is() {
79+
var mark = errors.New("mark")
80+
err := errors.Mark(nil, mark)
81+
fmt.Printf("%v\n", errors.Is(err, nil))
82+
fmt.Printf("%v\n", errors.Is(err, mark))
83+
fmt.Println("-----")
84+
85+
err = errors.Mark(fmt.Errorf("whoops"), nil, mark)
86+
fmt.Printf("%v\n", errors.Is(err, nil))
87+
fmt.Printf("%v\n", errors.Is(err, mark))
88+
fmt.Println("-----")
89+
90+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), mark)
91+
fmt.Printf("%v\n", errors.Is(err, nil))
92+
fmt.Printf("%v\n", errors.Is(err, mark))
93+
fmt.Println("-----")
94+
95+
err = errors.Mark(fmt.Errorf("whoops"), fmt.Errorf("foo"), fmt.Errorf("bar"), mark)
96+
fmt.Printf("%v\n", errors.Is(err, nil))
97+
fmt.Printf("%v\n", errors.Is(err, mark))
98+
fmt.Println("-----")
99+
100+
// Output:
101+
// true
102+
// false
103+
// -----
104+
// false
105+
// true
106+
// -----
107+
// false
108+
// true
109+
// -----
110+
// false
111+
// true
112+
// -----
113+
114+
}

0 commit comments

Comments
 (0)