package merry // The merry package augments standard golang errors with stacktraces // and other context information. // // You can add any context information to an error with `e = merry.WithValue(e, "code", 12345)` // You can retrieve that value with `v, _ := merry.Value(e, "code").(int)` // // Any error augmented like this will automatically get a stacktrace attached, if it doesn't have one // already. If you just want to add the stacktrace, use `Wrap(e)` // // It also providers a way to override an error's message: // // var InvalidInputs = errors.New("Bad inputs") // // `Here()` captures a new stacktrace, and WithMessagef() sets a new error message: // // return merry.Here(InvalidInputs).WithMessagef("Bad inputs: %v", inputs) // // Errors are immutable. All functions and methods which add context return new errors. // But errors can still be compared to the originals with `Is()` // // if merry.Is(err, InvalidInputs) { // // Functions which add context to errors have equivalent methods on *Error, to allow // convenient chaining: // // return merry.New("Invalid body").WithHTTPCode(400) // // merry.Errors also implement fmt.Formatter, similar to github.com/pkg/errors. // // fmt.Sprintf("%+v", e) == merry.Details(e) // // pkg/errors Cause() interface is not implemented (yet). import ( "errors" "fmt" "io" "runtime" ) // MaxStackDepth is the maximum number of stackframes on any error. var MaxStackDepth = 50 var captureStacks = true var verbose = false // StackCaptureEnabled returns whether stack capturing is enabled func StackCaptureEnabled() bool { return captureStacks } // SetStackCaptureEnabled sets stack capturing globally. Disabling stack capture can increase performance func SetStackCaptureEnabled(enabled bool) { captureStacks = enabled } // VerboseDefault returns the global default for verbose mode. // When true, e.Error() == Details(e) // When false, e.Error() == Message(e) func VerboseDefault() bool { return verbose } // SetVerboseDefault sets the global default for verbose mode. // When true, e.Error() == Details(e) // When false, e.Error() == Message(e) func SetVerboseDefault(b bool) { verbose = b } // Error extends the standard golang `error` interface with functions // for attachment additional data to the error type Error interface { error Appendf(format string, args ...interface{}) Error Append(msg string) Error Prepend(msg string) Error Prependf(format string, args ...interface{}) Error WithMessage(msg string) Error WithMessagef(format string, args ...interface{}) Error WithUserMessage(msg string) Error WithUserMessagef(format string, args ...interface{}) Error WithValue(key, value interface{}) Error Here() Error WithStackSkipping(skip int) Error WithHTTPCode(code int) Error fmt.Formatter } // New creates a new error, with a stack attached. The equivalent of golang's errors.New() func New(msg string) Error { return WrapSkipping(errors.New(msg), 1) } // Errorf creates a new error with a formatted message and a stack. The equivalent of golang's fmt.Errorf() func Errorf(format string, a ...interface{}) Error { return WrapSkipping(fmt.Errorf(format, a...), 1) } // UserError creates a new error with a message intended for display to an // end user. func UserError(msg string) Error { return WrapSkipping(errors.New(""), 1).WithUserMessage(msg) } // UserErrorf is like UserError, but uses fmt.Sprintf() func UserErrorf(format string, a ...interface{}) Error { return WrapSkipping(errors.New(""), 1).WithUserMessagef(format, a...) } // Wrap turns the argument into a merry.Error. If the argument already is a // merry.Error, this is a no-op. // If e == nil, return nil func Wrap(e error) Error { return WrapSkipping(e, 1) } // WrapSkipping turns the error arg into a merry.Error if the arg is not // already a merry.Error. // If e is nil, return nil. // If a merry.Error is created by this call, the stack captured will skip // `skip` frames (0 is the call site of `WrapSkipping()`) func WrapSkipping(e error, skip int) Error { switch e1 := e.(type) { case nil: return nil case *merryErr: return e1 default: return &merryErr{ err: e, key: stack, value: captureStack(skip + 1), } } } // WithValue adds a context an error. If the key was already set on e, // the new value will take precedence. // If e is nil, returns nil. func WithValue(e error, key, value interface{}) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithValue(key, value) } // Value returns the value for key, or nil if not set. // If e is nil, returns nil. func Value(e error, key interface{}) interface{} { for { switch m := e.(type) { case nil: return nil case *merryErr: if m.key == key { return m.value } e = m.err default: return nil } } } // Values returns a map of all values attached to the error // If a key has been attached multiple times, the map will // contain the last value mapped // If e is nil, returns nil. func Values(e error) map[interface{}]interface{} { if e == nil { return nil } var values map[interface{}]interface{} for { w, ok := e.(*merryErr) if !ok { return values } if values == nil { values = make(map[interface{}]interface{}, 1) } if _, ok := values[w.key]; !ok { values[w.key] = w.value } e = w.err } } // Here returns an error with a new stacktrace, at the call site of Here(). // Useful when returning copies of exported package errors. // If e is nil, returns nil. func Here(e error) Error { switch m := e.(type) { case nil: return nil case *merryErr: // optimization: only capture the stack once, since its expensive return m.WithStackSkipping(1) default: return WrapSkipping(e, 1) } } // Stack returns the stack attached to an error, or nil if one is not attached // If e is nil, returns nil. func Stack(e error) []uintptr { stack, _ := Value(e, stack).([]uintptr) return stack } // WithHTTPCode returns an error with an http code attached. // If e is nil, returns nil. func WithHTTPCode(e error, code int) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithHTTPCode(code) } // HTTPCode converts an error to an http status code. All errors // map to 500, unless the error has an http code attached. // If e is nil, returns 200. func HTTPCode(e error) int { if e == nil { return 200 } code, _ := Value(e, httpCode).(int) if code == 0 { return 500 } return code } // UserMessage returns the end-user safe message. Returns empty if not set. // If e is nil, returns "". func UserMessage(e error) string { if e == nil { return "" } msg, _ := Value(e, userMessage).(string) return msg } // Message returns just the error message. It is equivalent to // Error() when Verbose is false. // The behavior of Error() is (pseudo-code): // // if verbose // Details(e) // else // Message(e) || UserMessage(e) // // If e is nil, returns "". func Message(e error) string { if e == nil { return "" } m, _ := Value(e, message).(string) if m == "" { return Unwrap(e).Error() } return m } // WithMessage returns an error with a new message. // The resulting error's Error() method will return // the new message. // If e is nil, returns nil. func WithMessage(e error, msg string) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithValue(message, msg) } // WithMessagef is the same as WithMessage(), using fmt.Sprintf(). func WithMessagef(e error, format string, a ...interface{}) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithMessagef(format, a...) } // WithUserMessage adds a message which is suitable for end users to see. // If e is nil, returns nil. func WithUserMessage(e error, msg string) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithUserMessage(msg) } // WithUserMessagef is the same as WithMessage(), using fmt.Sprintf() func WithUserMessagef(e error, format string, args ...interface{}) Error { if e == nil { return nil } return WrapSkipping(e, 1).WithUserMessagef(format, args...) } // Append a message after the current error message, in the format "original: new". // If e == nil, return nil. func Append(e error, msg string) Error { if e == nil { return nil } return WrapSkipping(e, 1).Append(msg) } // Appendf is the same as Append, but uses fmt.Sprintf(). func Appendf(e error, format string, args ...interface{}) Error { if e == nil { return nil } return WrapSkipping(e, 1).Appendf(format, args...) } // Prepend a message before the current error message, in the format "new: original". // If e == nil, return nil. func Prepend(e error, msg string) Error { if e == nil { return nil } return WrapSkipping(e, 1).Prepend(msg) } // Prependf is the same as Prepend, but uses fmt.Sprintf() func Prependf(e error, format string, args ...interface{}) Error { if e == nil { return nil } return WrapSkipping(e, 1).Prependf(format, args...) } // Is checks whether e is equal to or wraps the original, at any depth. // If e == nil, return false. // This is useful if your package uses the common golang pattern of // exported error constants. If your package exports an ErrEOF constant, // which is initialized like this: // // var ErrEOF = errors.New("End of file error") // // ...and your user wants to compare an error returned by your package // with ErrEOF: // // err := urpack.Read() // if err == urpack.ErrEOF { // // ...the comparison will fail if the error has been wrapped by merry // at some point. Replace the comparison with: // // if merry.Is(err, urpack.ErrEOF) { // func Is(e error, originals ...error) bool { is := func(e, original error) bool { for { if e == original { return true } if e == nil || original == nil { return false } w, ok := e.(*merryErr) if !ok { return false } e = w.err } } for _, o := range originals { if is(e, o) { return true } } return false } // Unwrap returns the innermost underlying error. // Only useful in advanced cases, like if you need to // cast the underlying error to some type to get // additional information from it. // If e == nil, return nil. func Unwrap(e error) error { if e == nil { return nil } for { w, ok := e.(*merryErr) if !ok { return e } e = w.err } } func captureStack(skip int) []uintptr { if !captureStacks { return nil } stack := make([]uintptr, MaxStackDepth) length := runtime.Callers(2+skip, stack[:]) return stack[:length] } type errorProperty string const ( stack errorProperty = "stack" message = "message" httpCode = "http status code" userMessage = "user message" ) type merryErr struct { err error key, value interface{} } // Format implements fmt.Formatter func (e *merryErr) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { io.WriteString(s, Details(e)) return } fallthrough case 's': io.WriteString(s, e.Error()) case 'q': fmt.Fprintf(s, "%q", e.Error()) } } // make sure merryErr implements Error var _ Error = (*merryErr)(nil) // Error implements golang's error interface // returns the message value if set, otherwise // delegates to inner error func (e *merryErr) Error() string { if verbose { return Details(e) } m := Message(e) if m == "" { return UserMessage(e) } return m } // return a new error with additional context func (e *merryErr) WithValue(key, value interface{}) Error { if e == nil { return nil } return &merryErr{ err: e, key: key, value: value, } } // Shorthand for capturing a new stack trace func (e *merryErr) Here() Error { if e == nil { return nil } return e.WithStackSkipping(1) } // return a new error with a new stack capture func (e *merryErr) WithStackSkipping(skip int) Error { if e == nil { return nil } return &merryErr{ err: e, key: stack, value: captureStack(skip + 1), } } // return a new error with an http status code attached func (e *merryErr) WithHTTPCode(code int) Error { if e == nil { return nil } return e.WithValue(httpCode, code) } // return a new error with a new message func (e *merryErr) WithMessage(msg string) Error { if e == nil { return nil } return e.WithValue(message, msg) } // return a new error with a new formatted message func (e *merryErr) WithMessagef(format string, a ...interface{}) Error { if e == nil { return nil } return e.WithMessage(fmt.Sprintf(format, a...)) } // Add a message which is suitable for end users to see func (e *merryErr) WithUserMessage(msg string) Error { if e == nil { return nil } return e.WithValue(userMessage, msg) } // Add a message which is suitable for end users to see func (e *merryErr) WithUserMessagef(format string, args ...interface{}) Error { if e == nil { return nil } return e.WithUserMessage(fmt.Sprintf(format, args...)) } // Append a message after the current error message, in the format "original: new" func (e *merryErr) Append(msg string) Error { if e == nil { return nil } return e.WithMessagef("%s: %s", e.Error(), msg) } // Append a message after the current error message, in the format "original: new" func (e *merryErr) Appendf(format string, args ...interface{}) Error { if e == nil { return nil } return e.Append(fmt.Sprintf(format, args...)) } // Prepend a message before the current error message, in the format "new: original" func (e *merryErr) Prepend(msg string) Error { if e == nil { return nil } return e.WithMessagef("%s: %s", msg, e.Error()) } // Prepend a message before the current error message, in the format "new: original" func (e *merryErr) Prependf(format string, args ...interface{}) Error { if e == nil { return nil } return e.Prepend(fmt.Sprintf(format, args...)) }