github.com/thanos-io/thanos@v0.32.5/pkg/errors/errors.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 //nolint 5 // The idea of writing errors package in thanos is highly motivated from the Tast project of Chromium OS Authors. However, instead of 6 // copying the package, we end up writing our own simplified logic borrowing some ideas from the errors and github.com/pkg/errors. 7 // A big thanks to all of them. 8 9 // Package errors provides basic utilities to manipulate errors with a useful stacktrace. It combines the 10 // benefits of errors.New and fmt.Errorf world into a single package. 11 package errors 12 13 import ( 14 //lint:ignore faillint Custom errors package needs to import standard library errors. 15 "errors" 16 "fmt" 17 "strings" 18 ) 19 20 // base is the fundamental struct that implements the error interface and the acts as the backbone of this errors package. 21 type base struct { 22 // info contains the error message passed through calls like errors.Wrap, errors.New. 23 info string 24 // stacktrace stores information about the program counters - i.e. where this error was generated. 25 stack stacktrace 26 // err is the actual error which is being wrapped with a stacktrace and message information. 27 err error 28 } 29 30 // Error implements the error interface. 31 func (b *base) Error() string { 32 if b.err != nil { 33 return fmt.Sprintf("%s: %s", b.info, b.err.Error()) 34 } 35 return b.info 36 } 37 38 // Unwrap implements the error Unwrap interface. 39 func (b *base) Unwrap() error { 40 return b.err 41 } 42 43 // Format implements the fmt.Formatter interface to support the formatting of an error chain with "%+v" verb. 44 // Whenever error is printed with %+v format verb, stacktrace info gets dumped to the output. 45 func (b *base) Format(s fmt.State, verb rune) { 46 if verb == 'v' && s.Flag('+') { 47 s.Write([]byte(formatErrorChain(b))) 48 return 49 } 50 51 s.Write([]byte(b.Error())) 52 } 53 54 // Newf formats according to a format specifier and returns a new error with a stacktrace 55 // with recent call frames. Each call to New returns a distinct error value even if the text is 56 // identical. An alternative of the errors.New function. 57 // 58 // If no args have been passed, it is same as `New` function without formatting. Character like 59 // '%' still has to be escaped in that scenario. 60 func Newf(format string, args ...interface{}) error { 61 return &base{ 62 info: fmt.Sprintf(format, args...), 63 stack: newStackTrace(), 64 err: nil, 65 } 66 } 67 68 // Wrapf returns a new error by formatting the error message with the supplied format specifier 69 // and wrapping another error with a stacktrace containing recent call frames. 70 // 71 // If cause is nil, this is the same as fmt.Errorf. If no args have been passed, it is same as `Wrap` 72 // function without formatting. Character like '%' still has to be escaped in that scenario. 73 func Wrapf(cause error, format string, args ...interface{}) error { 74 return &base{ 75 info: fmt.Sprintf(format, args...), 76 stack: newStackTrace(), 77 err: cause, 78 } 79 } 80 81 // Cause returns the result of repeatedly calling the Unwrap method on err, if err's 82 // type implements an Unwrap method. Otherwise, Cause returns the last encountered error. 83 // The difference between Unwrap and Cause is the first one performs unwrapping of one level 84 // but Cause returns the last err (whether it's nil or not) where it failed to assert 85 // the interface containing the Unwrap method. 86 // This is a replacement of errors.Cause without the causer interface from pkg/errors which 87 // actually can be sufficed through the errors.Is function. But considering some use cases 88 // where we need to peel off all the external layers applied through errors.Wrap family, 89 // it is useful ( where external SDK doesn't use errors.Is internally). 90 func Cause(err error) error { 91 for err != nil { 92 e, ok := err.(interface { 93 Unwrap() error 94 }) 95 if !ok { 96 return err 97 } 98 err = e.Unwrap() 99 } 100 return nil 101 } 102 103 // formatErrorChain formats an error chain. 104 func formatErrorChain(err error) string { 105 var buf strings.Builder 106 for err != nil { 107 if e, ok := err.(*base); ok { 108 buf.WriteString(fmt.Sprintf("%s\n%v", e.info, e.stack)) 109 err = e.err 110 } else { 111 buf.WriteString(fmt.Sprintf("%s\n", err.Error())) 112 err = nil 113 } 114 } 115 return buf.String() 116 } 117 118 // The functions `Is`, `As` & `Unwrap` provides a thin wrapper around the builtin errors 119 // package in go. Just for sake of completeness and correct autocompletion behaviors from 120 // IDEs they have been wrapped using functions instead of using variable to reference them 121 // as first class functions (eg: var Is = errros.Is ). 122 123 // Is is a wrapper of built-in errors.Is. It reports whether any error in err's 124 // chain matches target. The chain consists of err itself followed by the sequence 125 // of errors obtained by repeatedly calling Unwrap. 126 func Is(err, target error) bool { 127 return errors.Is(err, target) 128 } 129 130 // As is a wrapper of built-in errors.As. It finds the first error in err's 131 // chain that matches target, and if one is found, sets target to that error 132 // value and returns true. Otherwise, it returns false. 133 func As(err error, target interface{}) bool { 134 return errors.As(err, target) 135 } 136 137 // Unwrap is a wrapper of built-in errors.Unwrap. Unwrap returns the result of 138 // calling the Unwrap method on err, if err's type contains an Unwrap method 139 // returning error. Otherwise, Unwrap returns nil. 140 func Unwrap(err error) error { 141 return errors.Unwrap(err) 142 }