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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
|
// Package errs provides a simple error package with stack traces.
package errs
import (
"fmt"
"io"
"runtime"
)
// Namer is implemented by all errors returned in this package. It returns a
// name for the class of error it is, and a boolean indicating if the name is
// valid.
type Namer interface{ Name() (string, bool) }
// Causer is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying cause.
type Causer interface{ Cause() error }
// unwrapper is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying error.
type unwrapper interface{ Unwrap() error }
// ungrouper is implemented by combinedError returned in this package. It
// returns all underlying errors, or nil if there is no underlying error.
type ungrouper interface{ Ungroup() []error }
// New returns an error not contained in any class. This is the same as calling
// fmt.Errorf(...) except it captures a stack trace on creation.
func New(format string, args ...interface{}) error {
return (*Class).create(nil, 3, fmt.Errorf(format, args...))
}
// Wrap returns an error not contained in any class. It just associates a stack
// trace with the error. Wrap returns nil if err is nil.
func Wrap(err error) error {
return (*Class).create(nil, 3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error not
// contained in any class. It just associates a stack trace with the error. WrapP
// does nothing if the pointer or pointed at error is nil.
func WrapP(err *error) {
if err != nil && *err != nil {
*err = (*Class).create(nil, 3, *err)
}
}
// Often, we call Cause as much as possible. Since comparing arbitrary
// interfaces with equality isn't panic safe, we only loop up to 100
// times to ensure that a poor implementation that causes a cycle does
// not run forever.
const maxCause = 100
// Unwrap returns the underlying error, if any, or just the error.
func Unwrap(err error) error {
for i := 0; err != nil && i < maxCause; i++ {
var nerr error
switch e := err.(type) {
case Causer:
nerr = e.Cause()
case unwrapper:
nerr = e.Unwrap()
}
if nerr == nil {
return err
}
err = nerr
}
return err
}
// Classes returns all the classes that have wrapped the error.
func Classes(err error) (classes []*Class) {
causes := 0
for {
switch e := err.(type) {
case *errorT:
if e.class != nil {
classes = append(classes, e.class)
}
err = e.err
continue
case Causer:
err = e.Cause()
case unwrapper:
err = e.Unwrap()
default:
return classes
}
if causes >= maxCause {
return classes
}
causes++
}
}
// Is checks if any of the underlying errors matches target
func Is(err, target error) bool {
return IsFunc(err, func(err error) bool {
return err == target
})
}
// IsFunc checks if any of the underlying errors matches the func
func IsFunc(err error, is func(err error) bool) bool {
causes := 0
errs := []error{err}
for len(errs) > 0 {
var next []error
for _, err := range errs {
if is(err) {
return true
}
switch e := err.(type) {
case ungrouper:
ungrouped := e.Ungroup()
for _, unerr := range ungrouped {
if unerr != nil {
next = append(next, unerr)
}
}
case Causer:
cause := e.Cause()
if cause != nil {
next = append(next, cause)
}
case unwrapper:
unwrapped := e.Unwrap()
if unwrapped != nil {
next = append(next, unwrapped)
}
}
if causes >= maxCause {
return false
}
causes++
}
errs = next
}
return false
}
//
// error classes
//
// Class represents a class of errors. You can construct errors, and check if
// errors are part of the class.
type Class string
// Has returns true if the passed in error was wrapped by this class.
func (c *Class) Has(err error) bool {
return IsFunc(err, func(err error) bool {
errt, ok := err.(*errorT)
return ok && errt.class == c
})
}
// New constructs an error with the format string that will be contained by
// this class. This is the same as calling Wrap(fmt.Errorf(...)).
func (c *Class) New(format string, args ...interface{}) error {
return c.create(3, fmt.Errorf(format, args...))
}
// Wrap returns a new error based on the passed in error that is contained in
// this class. Wrap returns nil if err is nil.
func (c *Class) Wrap(err error) error {
return c.create(3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error contained
// in this class. WrapP does nothing if the pointer or pointed at error is nil.
func (c *Class) WrapP(err *error) {
if err != nil && *err != nil {
*err = c.create(3, *err)
}
}
// create constructs the error, or just adds the class to the error, keeping
// track of the stack if it needs to construct it.
func (c *Class) create(depth int, err error) error {
if err == nil {
return nil
}
var pcs []uintptr
if err, ok := err.(*errorT); ok {
if c == nil || err.class == c {
return err
}
pcs = err.pcs
}
errt := &errorT{
class: c,
err: err,
pcs: pcs,
}
if errt.pcs == nil {
errt.pcs = make([]uintptr, 64)
n := runtime.Callers(depth, errt.pcs)
errt.pcs = errt.pcs[:n:n]
}
return errt
}
//
// errors
//
// errorT is the type of errors returned from this package.
type errorT struct {
class *Class
err error
pcs []uintptr
}
var ( // ensure *errorT implements the helper interfaces.
_ Namer = (*errorT)(nil)
_ Causer = (*errorT)(nil)
_ error = (*errorT)(nil)
)
// Stack returns the pcs for the stack trace associated with the error.
func (e *errorT) Stack() []uintptr { return e.pcs }
// errorT implements the error interface.
func (e *errorT) Error() string {
return fmt.Sprintf("%v", e)
}
// Format handles the formatting of the error. Using a "+" on the format string
// specifier will also write the stack trace.
func (e *errorT) Format(f fmt.State, c rune) {
sep := ""
if e.class != nil && *e.class != "" {
fmt.Fprintf(f, "%s", string(*e.class))
sep = ": "
}
if text := e.err.Error(); len(text) > 0 {
fmt.Fprintf(f, "%s%v", sep, text)
}
if f.Flag(int('+')) {
summarizeStack(f, e.pcs)
}
}
// Cause implements the interface wrapping errors are expected to implement
// to allow getting at underlying causes.
func (e *errorT) Cause() error {
return e.err
}
// Unwrap implements the draft design for error inspection. Since this is
// on an unexported type, it should not be hard to maintain going forward
// given that it also is the exact same semantics as Cause.
func (e *errorT) Unwrap() error {
return e.err
}
// Name returns the name for the error, which is the first wrapping class.
func (e *errorT) Name() (string, bool) {
if e.class == nil {
return "", false
}
return string(*e.class), true
}
// summarizeStack writes stack line entries to the writer.
func summarizeStack(w io.Writer, pcs []uintptr) {
frames := runtime.CallersFrames(pcs)
for {
frame, more := frames.Next()
if !more {
return
}
fmt.Fprintf(w, "\n\t%s:%d", frame.Function, frame.Line)
}
}
|