package merry import "fmt" // Wrapper knows how to wrap errors with context information. type Wrapper interface { // Wrap returns a new error, wrapping the argument, and typically adding some context information. // skipCallers is how many callers to skip when capturing a stack to skip to the caller of the merry // API surface. It's intended to make it possible to write wrappers which capture stacktraces. e.g. // // func CaptureStack() Wrapper { // return WrapperFunc(func(err error, skipCallers int) error { // s := make([]uintptr, 50) // // Callers // l := runtime.Callers(2+skipCallers, s[:]) // return WithStack(s[:l]).Wrap(err, skipCallers + 1) // }) // } Wrap(err error, skipCallers int) error } // WrapperFunc implements Wrapper. type WrapperFunc func(error, int) error // Wrap implements the Wrapper interface. func (w WrapperFunc) Wrap(err error, callerDepth int) error { return w(err, callerDepth+1) } // WithValue associates a key/value pair with an error. func WithValue(key, value interface{}) Wrapper { return WrapperFunc(func(err error, _ int) error { return Set(err, key, value) }) } // WithMessage overrides the value returned by err.Error(). func WithMessage(msg string) Wrapper { return WithValue(errKeyMessage, msg) } // WithMessagef overrides the value returned by err.Error(). func WithMessagef(format string, args ...interface{}) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyMessage, fmt.Sprintf(format, args...)) }) } // WithUserMessage associates an end-user message with an error. func WithUserMessage(msg string) Wrapper { return WithValue(errKeyUserMessage, msg) } // WithUserMessagef associates a formatted end-user message with an error. func WithUserMessagef(format string, args ...interface{}) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyUserMessage, fmt.Sprintf(format, args...)) }) } // AppendMessage a message after the current error message, in the format "original: new". func AppendMessage(msg string) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyMessage, err.Error()+": "+msg) }) } // AppendMessagef is the same as AppendMessage, but with a formatted message. func AppendMessagef(format string, args ...interface{}) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyMessage, err.Error()+": "+fmt.Sprintf(format, args...)) }) } // PrependMessage a message before the current error message, in the format "new: original". func PrependMessage(msg string) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyMessage, msg+": "+err.Error()) }) } // PrependMessagef is the same as PrependMessage, but with a formatted message. func PrependMessagef(format string, args ...interface{}) Wrapper { return WrapperFunc(func(err error, _ int) error { if err == nil { return nil } return Set(err, errKeyMessage, fmt.Sprintf(format, args...)+": "+err.Error()) }) } // WithHTTPCode associates an HTTP status code with an error. func WithHTTPCode(statusCode int) Wrapper { return WithValue(errKeyHTTPCode, statusCode) } // WithStack associates a stack of caller frames with an error. Generally, this package // will automatically capture and associate a stack with errors which are created or // wrapped by this package. But this allows the caller to associate an externally // generated stack. func WithStack(stack []uintptr) Wrapper { return WithValue(errKeyStack, stack) } // WithFormattedStack associates a stack of pre-formatted strings describing frames of a // stacktrace. Generally, a formatted stack is generated from the raw []uintptr stack // associated with the error, but a pre-formatted stack can be associated with the error // instead, and takes precedence over the raw stack. This is useful if pre-formatted // stack information is coming from some other source. func WithFormattedStack(stack []string) Wrapper { return WithValue(errKeyStack, stack) } // NoCaptureStack will suppress capturing a stack, even if StackCaptureEnabled() == true. func NoCaptureStack() Wrapper { return WrapperFunc(func(err error, _ int) error { // if this err already has a stack set, there is no need to set the // stack property again, and we don't want to override the prior the stack if HasStack(err) { return err } return Set(err, errKeyStack, nil) }) } // CaptureStack will override an earlier stack with a stack captured from the current // call site. If StackCaptureEnabled() == false, this is a no-op. // // If force is set, StackCaptureEnabled() will be ignored: a stack will always be captured. func CaptureStack(force bool) Wrapper { return WrapperFunc(func(err error, callerDepth int) error { return captureStack(err, callerDepth+1, force || StackCaptureEnabled()) }) } // WithCause sets one error as the cause of another error. This is useful for associating errors // from lower API levels with sentinel errors in higher API levels. errors.Is() and errors.As() // will traverse both the main chain of error wrappers, as well as down the chain of causes. func WithCause(err error) Wrapper { return WrapperFunc(func(nerr error, _ int) error { if nerr == nil { return nil } return &errWithCause{err: nerr, cause: err} }) } // Set wraps an error with a key/value pair. This is the simplest form of associating // a value with an error. It does not capture a stacktrace, invoke hooks, or do any // other processing. It is mainly intended as a primitive for writing Wrapper implementations. // // if err is nil, returns nil. // // Keeping this private for now. If it proves useful, it may be made public later, but // for now, external packages can get the same behavor with this: // // WithValue(key, value).Wrap(err) // func Set(err error, key, value interface{}) error { if err == nil { return nil } return &errWithValue{ err: err, key: key, value: value, } }