369 lines
9.5 KiB
Go
369 lines
9.5 KiB
Go
package errors
|
|
|
|
// Original package created by Dave Cheney
|
|
// Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
|
//
|
|
// Modifications of the original package by Friends of Go
|
|
// Copyright (c) 2019, Friends of Go <contact@friendsofgo.tech>
|
|
//
|
|
// Package errors provides simple error handling primitives.
|
|
//
|
|
// The traditional error handling idiom in Go is roughly akin to
|
|
//
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
//
|
|
// which when applied recursively up the call stack results in error reports
|
|
// without context or debugging information. The errors package allows
|
|
// programmers to add context to the failure path in their code in a way
|
|
// that does not destroy the original value of the error.
|
|
//
|
|
// Adding context to an error
|
|
//
|
|
// The errors.Wrap function returns a new error that adds context to the
|
|
// original error by recording a stack trace at the point Wrap is called,
|
|
// together with the supplied message. For example
|
|
//
|
|
// _, err := ioutil.ReadAll(r)
|
|
// if err != nil {
|
|
// return errors.Wrap(err, "read failed")
|
|
// }
|
|
//
|
|
// If additional control is required, the errors.WithStack and
|
|
// errors.WithMessage functions destructure errors.Wrap into its component
|
|
// operations: annotating an error with a stack trace and with a message,
|
|
// respectively.
|
|
//
|
|
// Retrieving the cause of an error
|
|
//
|
|
// Using errors.Wrap constructs a stack of errors, adding context to the
|
|
// preceding error. Depending on the nature of the error it may be necessary
|
|
// to reverse the operation of errors.Wrap to retrieve the original error
|
|
// for inspection. Any error value which implements this interface
|
|
//
|
|
// type causer interface {
|
|
// Cause() error
|
|
// }
|
|
//
|
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
|
// the topmost error that does not implement causer, which is assumed to be
|
|
// the original cause. For example:
|
|
//
|
|
// switch err := errors.Cause(err).(type) {
|
|
// case *MyError:
|
|
// // handle specifically
|
|
// default:
|
|
// // unknown error
|
|
// }
|
|
//
|
|
// Although the causer interface is not exported by this package, it is
|
|
// considered a part of its stable public interface.
|
|
//
|
|
// With the new standard package error we have two new ways to figure what is the cause of
|
|
// our error:
|
|
//
|
|
// var target *MyError
|
|
// if errors.As(err, &target) {
|
|
// // handle specifically
|
|
// } else {
|
|
// // unknown error
|
|
// }
|
|
//
|
|
// or even with sentinel errors:
|
|
//
|
|
// var ErrMyError = errors.New("my sentinel error")
|
|
// if errors.Is(err, ErrMyError) {
|
|
// // handle specifically
|
|
// } else {
|
|
// // unknown error
|
|
// }
|
|
//
|
|
// Formatted printing of errors
|
|
//
|
|
// All error values returned from this package implement fmt.Formatter and can
|
|
// be formatted by the fmt package. The following verbs are supported:
|
|
//
|
|
// %s print the error. If the error has a Cause it will be
|
|
// printed recursively.
|
|
// %v see %s
|
|
// %+v extended format. Each Frame of the error's StackTrace will
|
|
// be printed in detail.
|
|
//
|
|
// Retrieving the stack trace of an error or wrapper
|
|
//
|
|
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
|
// invoked. This information can be retrieved with the following interface:
|
|
//
|
|
// type stackTracer interface {
|
|
// StackTrace() errors.StackTrace
|
|
// }
|
|
//
|
|
// The returned errors.StackTrace type is defined as
|
|
//
|
|
// type StackTrace []Frame
|
|
//
|
|
// The Frame type represents a call site in the stack trace. Frame supports
|
|
// the fmt.Formatter interface that can be used for printing information about
|
|
// the stack trace of this error. For example:
|
|
//
|
|
// if err, ok := err.(stackTracer); ok {
|
|
// for _, f := range err.StackTrace() {
|
|
// fmt.Printf("%+s:%d\n", f, f)
|
|
// }
|
|
// }
|
|
//
|
|
// Although the stackTracer interface is not exported by this package, it is
|
|
// considered a part of its stable public interface.
|
|
//
|
|
// See the documentation for Frame.Format for more details.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// New returns an error with the supplied message.
|
|
// New also records the stack trace at the point it was called.
|
|
func New(message string) error {
|
|
return &fundamental{
|
|
msg: message,
|
|
stack: callers(),
|
|
}
|
|
}
|
|
|
|
// Errorf formats according to a format specifier and returns the string
|
|
// as a value that satisfies error.
|
|
// Errorf also records the stack trace at the point it was called.
|
|
func Errorf(format string, args ...interface{}) error {
|
|
return &fundamental{
|
|
msg: fmt.Sprintf(format, args...),
|
|
stack: callers(),
|
|
}
|
|
}
|
|
|
|
// fundamental is an error that has a message and a stack, but no caller.
|
|
type fundamental struct {
|
|
msg string
|
|
*stack
|
|
}
|
|
|
|
func (f *fundamental) Error() string { return f.msg }
|
|
|
|
func (f *fundamental) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 'v':
|
|
if s.Flag('+') {
|
|
io.WriteString(s, f.msg)
|
|
f.stack.Format(s, verb)
|
|
return
|
|
}
|
|
if s.Flag('-') {
|
|
io.WriteString(s, f.msg)
|
|
f.stack.Format(s, verb)
|
|
return
|
|
}
|
|
fallthrough
|
|
case 's':
|
|
io.WriteString(s, f.msg)
|
|
case 'q':
|
|
fmt.Fprintf(s, "%q", f.msg)
|
|
}
|
|
}
|
|
|
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
|
// If err is nil, WithStack returns nil.
|
|
func WithStack(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &withStack{
|
|
err,
|
|
callers(),
|
|
}
|
|
}
|
|
|
|
type withStack struct {
|
|
error
|
|
*stack
|
|
}
|
|
|
|
func (w *withStack) Cause() error { return w.error }
|
|
func (w *withStack) Unwrap() error { return w.error }
|
|
|
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 'v':
|
|
if s.Flag('+') {
|
|
fmt.Fprintf(s, "%+v", w.Cause())
|
|
w.stack.Format(s, verb)
|
|
return
|
|
}
|
|
if s.Flag('-') {
|
|
fmt.Fprintf(s, "%-v", w.Cause())
|
|
w.stack.Format(s, verb)
|
|
return
|
|
}
|
|
fallthrough
|
|
case 's':
|
|
io.WriteString(s, w.Error())
|
|
case 'q':
|
|
fmt.Fprintf(s, "%q", w.Error())
|
|
}
|
|
}
|
|
|
|
// Wrap returns an error annotating err with a stack trace
|
|
// at the point Wrap is called, and the supplied message.
|
|
// If err is nil, Wrap returns nil.
|
|
func Wrap(err error, message string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
err = &withMessage{
|
|
cause: err,
|
|
msg: message,
|
|
}
|
|
return &withStack{
|
|
err,
|
|
callers(),
|
|
}
|
|
}
|
|
|
|
// Wrapf returns an error annotating err with a stack trace
|
|
// at the point Wrapf is called, and the format specifier.
|
|
// If err is nil, Wrapf returns nil.
|
|
func Wrapf(err error, format string, args ...interface{}) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
err = &withMessage{
|
|
cause: err,
|
|
msg: fmt.Sprintf(format, args...),
|
|
}
|
|
return &withStack{
|
|
err,
|
|
callers(),
|
|
}
|
|
}
|
|
|
|
// Is reports whether any error in err's chain matches target.
|
|
//
|
|
// The chain consists of err itself followed by the sequence of errors obtained by
|
|
// repeatedly calling Unwrap.
|
|
//
|
|
// An error is considered to match a target if it is equal to that target or if
|
|
// it implements a method Is(error) bool such that Is(target) returns true.
|
|
func Is(err error, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|
|
|
|
// As finds the first error in err's chain that matches target, and if so, sets
|
|
// target to that error value and returns true.
|
|
//
|
|
// The chain consists of err itself followed by the sequence of errors obtained by
|
|
// repeatedly calling Unwrap.
|
|
//
|
|
// An error matches target if the error's concrete value is assignable to the value
|
|
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
|
// As(target) returns true. In the latter case, the As method is responsible for
|
|
// setting target.
|
|
//
|
|
// As will panic if target is not a non-nil pointer to either a type that implements
|
|
// error, or to any interface type. As returns false if err is nil.
|
|
func As(err error, target interface{}) bool {
|
|
return errors.As(err, target)
|
|
}
|
|
|
|
// WithMessage annotates err with a new message.
|
|
// If err is nil, WithMessage returns nil.
|
|
func WithMessage(err error, message string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &withMessage{
|
|
cause: err,
|
|
msg: message,
|
|
}
|
|
}
|
|
|
|
// WithMessagef annotates err with the format specifier.
|
|
// If err is nil, WithMessagef returns nil.
|
|
func WithMessagef(err error, format string, args ...interface{}) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &withMessage{
|
|
cause: err,
|
|
msg: fmt.Sprintf(format, args...),
|
|
}
|
|
}
|
|
|
|
type withMessage struct {
|
|
cause error
|
|
msg string
|
|
}
|
|
|
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
|
func (w *withMessage) Cause() error { return w.cause }
|
|
func (w *withMessage) Unwrap() error { return w.cause }
|
|
|
|
func (w *withMessage) Format(s fmt.State, verb rune) {
|
|
switch verb {
|
|
case 'v':
|
|
if s.Flag('+') {
|
|
fmt.Fprintf(s, "%+v\n", w.Cause())
|
|
io.WriteString(s, w.msg)
|
|
return
|
|
}
|
|
if s.Flag('-') {
|
|
fmt.Fprintf(s, "%-v ; ", w.Cause())
|
|
io.WriteString(s, w.msg)
|
|
return
|
|
}
|
|
fallthrough
|
|
case 's', 'q':
|
|
io.WriteString(s, w.Error())
|
|
}
|
|
}
|
|
|
|
// Cause returns the underlying cause of the error, if possible.
|
|
// An error value has a cause if it implements the following
|
|
// interface:
|
|
//
|
|
// type causer interface {
|
|
// Cause() error
|
|
// }
|
|
//
|
|
// If the error does not implement Cause, the original error will
|
|
// be returned. If the error is nil, nil will be returned without further
|
|
// investigation.
|
|
func Cause(err error) error {
|
|
type causer interface {
|
|
Cause() error
|
|
}
|
|
|
|
for err != nil {
|
|
var c causer
|
|
if !As(err, &c) {
|
|
break
|
|
}
|
|
err = c.Cause()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func FormatPanicDebugStack(msg string) string {
|
|
msg = strings.ReplaceAll(msg, "\n", " < ")
|
|
msg = strings.ReplaceAll(msg, ") <", ") at")
|
|
idx := strings.Index(msg, "panic.go:")
|
|
if idx != -1 {
|
|
msg = msg[idx:]
|
|
}
|
|
if len(msg) > 2000 {
|
|
msg = msg[:2000] + "..."
|
|
}
|
|
return msg
|
|
}
|