github.com/mailgun/holster/v4@v4.20.0/errors/errors.go (about)

     1  // Package errors provides simple error handling primitives.
     2  //
     3  // The traditional error handling idiom in Go is roughly akin to
     4  //
     5  //	if err != nil {
     6  //	        return err
     7  //	}
     8  //
     9  // which applied recursively up the call stack results in error reports
    10  // without context or debugging information. The errors package allows
    11  // programmers to add context to the failure path in their code in a way
    12  // that does not destroy the original value of the error.
    13  //
    14  // # Adding context to an error
    15  //
    16  // The errors.Wrap function returns a new error that adds context to the
    17  // original error by recording a stack trace at the point Wrap is called,
    18  // and the supplied message. For example
    19  //
    20  //	_, err := ioutil.ReadAll(r)
    21  //	if err != nil {
    22  //	        return errors.Wrap(err, "read failed")
    23  //	}
    24  //
    25  // If additional control is required the errors.WithStack and errors.WithMessage
    26  // functions destructure errors.Wrap into its component operations of annotating
    27  // an error with a stack trace and an a message, respectively.
    28  //
    29  // # Retrieving the cause of an error
    30  //
    31  // Using errors.Wrap constructs a stack of errors, adding context to the
    32  // preceding error. Depending on the nature of the error it may be necessary
    33  // to reverse the operation of errors.Wrap to retrieve the original error
    34  // for inspection. Any error value which implements this interface
    35  //
    36  //	type causer interface {
    37  //	        Cause() error
    38  //	}
    39  //
    40  // can be inspected by errors.Cause. errors.Cause will recursively retrieve
    41  // the topmost error which does not implement causer, which is assumed to be
    42  // the original cause. For example:
    43  //
    44  //	switch err := errors.Cause(err).(type) {
    45  //	case *MyError:
    46  //	        // handle specifically
    47  //	default:
    48  //	        // unknown error
    49  //	}
    50  //
    51  // causer interface is not exported by this package, but is considered a part
    52  // of stable public API.
    53  //
    54  // # Formatted printing of errors
    55  //
    56  // All error values returned from this package implement fmt.Formatter and can
    57  // be formatted by the fmt package. The following verbs are supported
    58  //
    59  //	%s    print the error. If the error has a Cause it will be
    60  //	      printed recursively
    61  //	%v    see %s
    62  //	%+v   extended format. Each Frame of the error's StackTrace will
    63  //	      be printed in detail.
    64  //
    65  // # Retrieving the stack trace of an error or wrapper
    66  //
    67  // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
    68  // invoked. This information can be retrieved with the following interface.
    69  //
    70  //	type stackTracer interface {
    71  //	        StackTrace() errors.StackTrace
    72  //	}
    73  //
    74  // Where errors.StackTrace is defined as
    75  //
    76  //	type StackTrace []Frame
    77  //
    78  // The Frame type represents a call site in the stack trace. Frame supports
    79  // the fmt.Formatter interface that can be used for printing information about
    80  // the stack trace of this error. For example:
    81  //
    82  //	if err, ok := err.(stackTracer); ok {
    83  //	        for _, f := range err.StackTrace() {
    84  //	                fmt.Printf("%+s:%d", f)
    85  //	        }
    86  //	}
    87  //
    88  // stackTracer interface is not exported by this package, but is considered a part
    89  // of stable public API.
    90  //
    91  // See the documentation for Frame.Format for more details.
    92  package errors
    93  
    94  import (
    95  	"fmt"
    96  	"io"
    97  
    98  	stack "github.com/mailgun/holster/v4/callstack"
    99  	pkgerrors "github.com/pkg/errors" //nolint:gomodguard // Legacy code requires deprecated package.
   100  	"github.com/sirupsen/logrus"
   101  )
   102  
   103  // New returns an error with the supplied message.
   104  // New also records the stack trace at the depth specified.
   105  func NewWithDepth(message string, depth int) error {
   106  	return &fundamental{
   107  		msg:       message,
   108  		CallStack: stack.New(depth),
   109  	}
   110  }
   111  
   112  // New returns an error with the supplied message.
   113  // New also records the stack trace at the point it was called.
   114  func New(message string) error {
   115  	return &fundamental{
   116  		msg:       message,
   117  		CallStack: stack.New(1),
   118  	}
   119  }
   120  
   121  // Errorf formats according to a format specifier and returns the string
   122  // as a value that satisfies error.
   123  // Errorf also records the stack trace at the point it was called.
   124  func Errorf(format string, args ...interface{}) error {
   125  	return &fundamental{
   126  		msg:       fmt.Sprintf(format, args...),
   127  		CallStack: stack.New(1),
   128  	}
   129  }
   130  
   131  // fundamental is an error that has a message and a stack, but no caller.
   132  type fundamental struct {
   133  	*stack.CallStack
   134  	msg string
   135  }
   136  
   137  func (f *fundamental) Error() string { return f.msg }
   138  
   139  func (f *fundamental) Format(s fmt.State, verb rune) {
   140  	switch verb {
   141  	case 'v':
   142  		if s.Flag('+') {
   143  			_, _ = io.WriteString(s, f.msg)
   144  			f.CallStack.Format(s, verb)
   145  			return
   146  		}
   147  		fallthrough
   148  	case 's':
   149  		_, _ = io.WriteString(s, f.msg)
   150  	case 'q':
   151  		fmt.Fprintf(s, "%q", f.msg)
   152  	}
   153  }
   154  
   155  // WithStack annotates err with a stack trace at the point WithStack was called.
   156  // If err is nil, WithStack returns nil.
   157  func WithStack(err error) error {
   158  	if err == nil {
   159  		return nil
   160  	}
   161  	return &withStack{
   162  		err,
   163  		stack.New(1),
   164  	}
   165  }
   166  
   167  type withStack struct {
   168  	error
   169  	*stack.CallStack
   170  }
   171  
   172  func (w *withStack) Cause() error  { return w.error }
   173  func (w *withStack) Unwrap() error { return w.error }
   174  func (w *withStack) Context() map[string]interface{} {
   175  	if child, ok := w.error.(HasContext); ok {
   176  		return child.Context()
   177  	}
   178  	return nil
   179  }
   180  
   181  func (w *withStack) Format(s fmt.State, verb rune) {
   182  	switch verb {
   183  	case 'v':
   184  		if s.Flag('+') {
   185  			fmt.Fprintf(s, "%+v", w.Cause())
   186  			w.CallStack.Format(s, verb)
   187  			return
   188  		}
   189  		fallthrough
   190  	case 's':
   191  		_, _ = io.WriteString(s, w.Error())
   192  	case 'q':
   193  		fmt.Fprintf(s, "%q", w.Error())
   194  	}
   195  }
   196  
   197  // WrapStack returns an error annotating err with a stack trace
   198  // at the depth indicated. A calling depth of 1 is the function that
   199  // called WrapStack. If err is nil, Wrap returns nil.
   200  func WrapWithDepth(err error, message string, depth int) error {
   201  	if err == nil {
   202  		return nil
   203  	}
   204  	err = &withMessage{
   205  		cause: err,
   206  		msg:   message,
   207  	}
   208  	return &withStack{
   209  		err,
   210  		stack.New(depth),
   211  	}
   212  }
   213  
   214  // Wrap returns an error annotating err with a stack trace
   215  // at the point Wrap is called, and the supplied message.
   216  // If err is nil, Wrap returns nil.
   217  func Wrap(err error, message string) error {
   218  	if err == nil {
   219  		return nil
   220  	}
   221  	err = &withMessage{
   222  		cause: err,
   223  		msg:   message,
   224  	}
   225  	return &withStack{
   226  		err,
   227  		stack.New(1),
   228  	}
   229  }
   230  
   231  // Wrapf returns an error annotating err with a stack trace
   232  // at the point Wrapf is call, and the format specifier.
   233  // If err is nil, Wrapf returns nil.
   234  func Wrapf(err error, format string, args ...interface{}) error {
   235  	if err == nil {
   236  		return nil
   237  	}
   238  	err = &withMessage{
   239  		cause: err,
   240  		msg:   fmt.Sprintf(format, args...),
   241  	}
   242  	return &withStack{
   243  		err,
   244  		stack.New(1),
   245  	}
   246  }
   247  
   248  // WithMessage annotates err with a new message.
   249  // If err is nil, WithMessage returns nil.
   250  func WithMessage(err error, message string) error {
   251  	if err == nil {
   252  		return nil
   253  	}
   254  	return &withMessage{
   255  		cause: err,
   256  		msg:   message,
   257  	}
   258  }
   259  
   260  type withMessage struct {
   261  	cause error
   262  	msg   string
   263  }
   264  
   265  func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
   266  func (w *withMessage) Cause() error  { return w.cause }
   267  func (w *withMessage) Unwrap() error { return w.cause }
   268  
   269  func (w *withMessage) Format(s fmt.State, verb rune) {
   270  	switch verb {
   271  	case 'v':
   272  		if s.Flag('+') {
   273  			fmt.Fprintf(s, "%+v\n", w.Cause())
   274  			_, _ = io.WriteString(s, w.msg)
   275  			return
   276  		}
   277  		fallthrough
   278  	case 's', 'q':
   279  		_, _ = io.WriteString(s, w.Error())
   280  	}
   281  }
   282  
   283  // Cause returns the underlying cause of the error, if possible.
   284  // An error value has a cause if it implements the following
   285  // interface:
   286  //
   287  //	type causer interface {
   288  //	       Cause() error
   289  //	}
   290  //
   291  // If the error does not implement Cause, the original error will
   292  // be returned. If the error is nil, nil will be returned without further
   293  // investigation.
   294  func Cause(err error) error {
   295  	type causer interface {
   296  		Cause() error
   297  	}
   298  
   299  	for err != nil {
   300  		cause, ok := err.(causer)
   301  		if !ok {
   302  			break
   303  		}
   304  		err = cause.Cause()
   305  	}
   306  	return err
   307  }
   308  
   309  // Returns the context for the underlying error as map[string]interface{}
   310  // If no context is available returns nil
   311  func ToMap(err error) map[string]interface{} {
   312  	var result map[string]interface{}
   313  
   314  	if child, ok := err.(HasContext); ok {
   315  		// Append the context map to our results
   316  		result = make(map[string]interface{})
   317  		for key, value := range child.Context() {
   318  			result[key] = value
   319  		}
   320  	}
   321  
   322  	return result
   323  }
   324  
   325  // Returns the context and stacktrace information for the underlying error as logrus.Fields{}
   326  // returns empty logrus.Fields{} if err has no context or no stacktrace
   327  //
   328  //	logrus.WithFields(errors.ToLogrus(err)).WithField("tid", 1).Error(err)
   329  func ToLogrus(err error) logrus.Fields {
   330  	result := logrus.Fields{
   331  		"excValue": err.Error(),
   332  		"excType":  fmt.Sprintf("%T", Cause(err)),
   333  		"excText":  fmt.Sprintf("%+v", err),
   334  	}
   335  
   336  	// Add the stack info if provided
   337  	if cast, ok := err.(stack.HasStackTrace); ok {
   338  		trace := cast.StackTrace()
   339  		caller := stack.GetLastFrame(trace)
   340  		result["excFuncName"] = caller.Func
   341  		result["excLineno"] = caller.LineNo
   342  		result["excFileName"] = caller.File
   343  	}
   344  
   345  	// Add context if provided
   346  	child, ok := err.(HasContext)
   347  	if !ok {
   348  		return result
   349  	}
   350  
   351  	// Append the context map to our results
   352  	for key, value := range child.Context() {
   353  		result[key] = value
   354  	}
   355  	return result
   356  }
   357  
   358  type CauseError struct {
   359  	stack *stack.CallStack
   360  	error error
   361  }
   362  
   363  // Creates a new error that becomes the cause even if 'err' is a wrapped error
   364  // but preserves the Context() and StackTrace() information. This allows the user
   365  // to create a concrete error type without losing context
   366  //
   367  //	// Our new concrete type encapsulates CauseError
   368  //	type RetryError struct {
   369  //		errors.CauseError
   370  //	}
   371  //
   372  //	func NewRetryError(err error) *RetryError {
   373  //		return &RetryError{errors.NewCauseError(err, 1)}
   374  //	}
   375  //
   376  //	// Returns true if the error is of type RetryError
   377  //	func IsRetryError(err error) bool {
   378  //		err = errors.Cause(err)
   379  //		_, ok := err.(*RetryError)
   380  //		return ok
   381  //	}
   382  func NewCauseError(err error, depth ...int) *CauseError {
   383  	var stk *stack.CallStack
   384  	if len(depth) > 0 {
   385  		stk = stack.New(1 + depth[0])
   386  	} else {
   387  		stk = stack.New(1)
   388  	}
   389  	return &CauseError{
   390  		stack: stk,
   391  		error: err,
   392  	}
   393  }
   394  
   395  func (e *CauseError) Error() string { return e.error.Error() }
   396  func (e *CauseError) Context() map[string]interface{} {
   397  	if child, ok := e.error.(HasContext); ok {
   398  		return child.Context()
   399  	}
   400  	return nil
   401  }
   402  func (e *CauseError) StackTrace() pkgerrors.StackTrace {
   403  	if child, ok := e.error.(stack.HasStackTrace); ok {
   404  		return child.StackTrace()
   405  	}
   406  	return e.stack.StackTrace()
   407  }
   408  
   409  // TODO: Add Format() support