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
|
package errors
import (
"errors"
"fmt"
"io"
"strings"
)
// Export a number of functions or variables from package errors.
var (
As = errors.As
Is = errors.Is
Unwrap = errors.Unwrap
//Join = errors.Join
)
// IncludeBacktrace is a static variable that decides if when creating an error we store the backtrace with it.
var IncludeBacktrace = true
// Err is our custom error type that can store backtrace, file and line number
type Err struct {
m string
c error
t stack
}
func (e Err) Format(s fmt.State, verb rune) {
switch verb {
case 's':
io.WriteString(s, e.m)
switch {
case s.Flag('+'):
if e.c != nil {
io.WriteString(s, ": ")
io.WriteString(s, fmt.Sprintf("%+s", e.c))
}
}
case 'v':
e.Format(s, 's')
switch {
case s.Flag('+'):
if e.t != nil {
io.WriteString(s, "\n\t")
e.t.Format(s, 'v')
}
}
}
}
// Error implements the error interface
func (e Err) Error() string {
if IncludeBacktrace {
return e.m
}
s := strings.Builder{}
s.WriteString(e.m)
if ch := errors.Unwrap(e); ch != nil {
s.WriteString(": ")
s.WriteString(ch.Error())
}
return s.String()
}
// Unwrap implements the errors.Wrapper interface
func (e Err) Unwrap() error {
return e.c
}
// StackTrace returns the stack trace as returned by the debug.Stack function
func (e Err) StackTrace() StackTrace {
return e.t.StackTrace()
}
// Annotatef wraps an error with new message
func Annotatef(e error, s string, args ...interface{}) *Err {
err := wrap(e, s, args...)
return &err
}
// Newf creaates a new error
func Newf(s string, args ...interface{}) *Err {
err := wrap(nil, s, args...)
return &err
}
// Errorf is an alias for Newf
func Errorf(s string, args ...interface{}) error {
err := wrap(nil, s, args...)
return &err
}
// As implements support for errors.As
func (e *Err) As(err interface{}) bool {
switch x := err.(type) {
case **Err:
*x = e
case *Err:
*x = *e
default:
return false
}
return true
}
type StackTracer interface {
StackTrace() StackTrace
}
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
// should imply the error was generated directly from our goroutine.
func ancestorOfCause(ourStack stack, causeStack StackTrace) bool {
// Stack traces are ordered such that the deepest frame is first. We'll want to check
// for prefix matching in reverse.
//
// As an example, imagine we have a prefix-matching stack for ourselves:
// [
// "github.com/go-ap/processing/processing.Validate",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// We'll want to compare this against an error cause that will have happened further
// down the stack. An example stack trace from such an error might be:
// [
// "github.com/go-ap/errors/errors.New",
// "testing.tRunner",
// "runtime.goexit"
// ]
//
// Their prefix matches, but we'll have to handle the match carefully as we need to match
// from back to forward.
// We can't possibly prefix match if our stack is larger than the cause stack.
if len(ourStack) > len(causeStack) {
return false
}
// We know the sizes are compatible, so compare program counters from back to front.
for idx := 0; idx < len(ourStack); idx++ {
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
return false
}
}
return true
}
func wrap(e error, s string, args ...interface{}) Err {
err := Err{
c: e,
m: fmt.Sprintf(s, args...),
}
if IncludeBacktrace {
causeStackTracer := new(StackTracer)
// If our cause has set a stack trace, and that trace is a child of our own function
// as inferred by prefix matching our current program counter stack, then we only want
// to decorate the error message rather than add a redundant stack trace.
stack := callers(2)
if !(As(e, causeStackTracer) && ancestorOfCause(*stack, (*causeStackTracer).StackTrace())) {
err.t = *stack
}
}
return err
}
|