hanayo/vendor/github.com/ansel1/merry/errors.go
2019-02-23 13:29:15 +00:00

557 lines
14 KiB
Go

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...))
}