-
Notifications
You must be signed in to change notification settings - Fork 0
/
caller.go
114 lines (101 loc) · 2.99 KB
/
caller.go
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
// Copyright 2020 cloudeng llc. All rights reserved.
// Use of this source code is governed by the Apache-2.0
// license that can be found in the LICENSE file.
package errors
import (
"errors"
"fmt"
"path/filepath"
"runtime"
"strconv"
"strings"
)
// annotated represents an error annotated with additional metadata
// such as the source code file and line number it was created from.
type annotated struct {
annotation string
err error
}
// Error implements error.error
func (ae *annotated) Error() string {
return fmt.Sprintf("%v: %v", ae.annotation, ae.err)
}
// Unwrap implements errors.Unwrap. It returns the stored error
// without the annotation.
func (ae *annotated) Unwrap() error {
return ae.err
}
// Is supports errors.Is. It calls errors.Is with the stored error.
func (ae *annotated) Is(target error) bool {
return errors.Is(ae.err, target)
}
// As supports errors.As. It calls errors.As with the stored error.
func (ae *annotated) As(target interface{}) bool {
return errors.As(ae.err, target)
}
// Format implements fmt.Formatter.Format.
func (ae *annotated) Format(f fmt.State, c rune) {
format := "%" + string(c)
if !f.Flag('+') && !f.Flag('#') {
fmt.Fprintf(f, format, ae.Error())
return
}
switch {
case f.Flag('+'):
format = "%v: %+" + string(c)
case f.Flag('#'):
format = "%v: %#" + string(c)
}
fmt.Fprintf(f, format, ae.annotation, ae.err)
}
// Caller returns the caller's location as a filepath and line
// number. Depth follows the convention for runtime.Caller. The
// filepath is the trailing nameLen components of the filename
// returned by runtime.Caller. A nameLen of 2 is generally the
// best compromise between brevity and precision since it includes
// the enclosing directory component as well as the filename.
func Caller(depth, nameLen int) string {
_, file, line, _ := runtime.Caller(depth)
if nameLen <= 1 {
return filepath.Base(file) + ":" + strconv.Itoa(line)
}
base := ""
for i := 0; i < nameLen; i++ {
idx := strings.LastIndex(file, "/")
if idx < 0 {
break
}
base = file[idx:] + base
file = file[:idx]
}
if len(base) > 0 && base[0] == '/' {
base = base[1:]
}
return base + ":" + strconv.Itoa(line)
}
// WithCaller returns an error annotated with the location of its immediate caller.
func WithCaller(err error) error {
return Annotate(Caller(2, 2), err)
}
// WithCallerAll returns a slice conntaing annotated versions of all of the
// supplied errors.
func WithCallerAll(err ...error) []error {
return AnnotateAll(Caller(2, 2), err...)
}
// Annotate returns an error representing the original error and the
// supplied annotation.
func Annotate(annotation string, err error) error {
return &annotated{
annotation: annotation,
err: err,
}
}
// AnnotateAll returns a slice of errors representing the original
// errors and the supplied annotation.
func AnnotateAll(annotation string, errs ...error) []error {
result := make([]error, len(errs))
for i, err := range errs {
result[i] = Annotate(annotation, err)
}
return result
}