github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/errors/errors.go (about)

     1  package errors
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  )
     7  
     8  // ----------------------------------------
     9  // Convenience method.
    10  
    11  func Wrap(cause interface{}, format string, args ...interface{}) Error {
    12  	if causeCmnError, ok := cause.(*cmnError); ok { //nolint:gocritic
    13  		msg := fmt.Sprintf(format, args...)
    14  		return causeCmnError.Stacktrace().Trace(1, msg)
    15  	} else if cause == nil {
    16  		return newCmnError(FmtError{format, args}).Stacktrace()
    17  	} else {
    18  		// NOTE: causeCmnError is a typed nil here.
    19  		msg := fmt.Sprintf(format, args...)
    20  		return newCmnError(cause).Stacktrace().Trace(1, msg)
    21  	}
    22  }
    23  
    24  func Cause(err error) error {
    25  	if cerr, ok := err.(*cmnError); ok {
    26  		return cerr.Data().(error)
    27  	} else {
    28  		return err
    29  	}
    30  }
    31  
    32  // ----------------------------------------
    33  // Error & cmnError
    34  
    35  /*
    36  Usage with arbitrary error data:
    37  
    38  ```go
    39  
    40  	// Error construction
    41  	type MyError struct{}
    42  	var err1 error = NewWithData(MyError{}, "my message")
    43  	...
    44  	// Wrapping
    45  	var err2 error  = Wrap(err1, "another message")
    46  	if (err1 != err2) { panic("should be the same")
    47  	...
    48  	// Error handling
    49  	switch err2.Data().(type){
    50  		case MyError: ...
    51  	    default: ...
    52  	}
    53  
    54  ```
    55  */
    56  type Error interface {
    57  	Error() string
    58  	Stacktrace() Error
    59  	Trace(offset int, format string, args ...interface{}) Error
    60  	Data() interface{}
    61  }
    62  
    63  // New Error with formatted message.
    64  // The Error's Data will be a FmtError type.
    65  func New(format string, args ...interface{}) Error {
    66  	err := FmtError{format, args}
    67  	return newCmnError(err)
    68  }
    69  
    70  // New Error with specified data.
    71  func NewWithData(data interface{}) Error {
    72  	return newCmnError(data)
    73  }
    74  
    75  type cmnError struct {
    76  	data       interface{}    // associated data
    77  	msgtraces  []msgtraceItem // all messages traced
    78  	stacktrace []uintptr      // first stack trace
    79  }
    80  
    81  var _ Error = &cmnError{}
    82  
    83  // NOTE: do not expose.
    84  func newCmnError(data interface{}) *cmnError {
    85  	return &cmnError{
    86  		data:       data,
    87  		msgtraces:  nil,
    88  		stacktrace: nil,
    89  	}
    90  }
    91  
    92  // Implements error.
    93  func (err *cmnError) Error() string {
    94  	return fmt.Sprintf("%v", err)
    95  }
    96  
    97  // Implements Unwrap method for compat with stdlib errors.Is()/As().
    98  func (err *cmnError) Unwrap() error {
    99  	if err.data == nil {
   100  		return nil
   101  	}
   102  	werr, ok := err.data.(error)
   103  	if !ok {
   104  		return nil
   105  	}
   106  	return werr
   107  }
   108  
   109  // Captures a stacktrace if one was not already captured.
   110  func (err *cmnError) Stacktrace() Error {
   111  	if err.stacktrace == nil {
   112  		offset := 3
   113  		depth := 32
   114  		err.stacktrace = captureStacktrace(offset, depth)
   115  	}
   116  	return err
   117  }
   118  
   119  // Add tracing information with msg.
   120  // Set n=0 unless wrapped with some function, then n > 0.
   121  func (err *cmnError) Trace(offset int, format string, args ...interface{}) Error {
   122  	msg := fmt.Sprintf(format, args...)
   123  	return err.doTrace(msg, offset)
   124  }
   125  
   126  // Return the "data" of this error.
   127  // Data could be used for error handling/switching,
   128  // or for holding general error/debug information.
   129  func (err *cmnError) Data() interface{} {
   130  	return err.data
   131  }
   132  
   133  func (err *cmnError) doTrace(msg string, n int) Error {
   134  	// Ignoring linting on `runtime.Caller` for now, as it's
   135  	// a critical method that can't be currently reworked
   136  	//nolint:dogsled
   137  	pc, _, _, _ := runtime.Caller(n + 2) // +1 for doTrace().  +1 for the caller.
   138  	// Include file & line number & msg.
   139  	// Do not include the whole stack trace.
   140  	err.msgtraces = append(err.msgtraces, msgtraceItem{
   141  		pc:  pc,
   142  		msg: msg,
   143  	})
   144  	return err
   145  }
   146  
   147  func (err *cmnError) Format(s fmt.State, verb rune) {
   148  	switch {
   149  	case verb == 'p':
   150  		s.Write([]byte(fmt.Sprintf("%p", &err)))
   151  	case verb == 'v' && s.Flag('+'):
   152  		s.Write([]byte("--= Error =--\n"))
   153  		// Write data.
   154  		fmt.Fprintf(s, "Data: %+v\n", err.data)
   155  		// Write msg trace items.
   156  		s.Write([]byte("Msg Traces:\n"))
   157  		for i, msgtrace := range err.msgtraces {
   158  			fmt.Fprintf(s, " %4d  %s\n", i, msgtrace.String())
   159  		}
   160  		s.Write([]byte("--= /Error =--\n"))
   161  	case verb == 'v' && s.Flag('#'):
   162  		s.Write([]byte("--= Error =--\n"))
   163  		// Write data.
   164  		fmt.Fprintf(s, "Data: %#v\n", err.data)
   165  		// Write msg trace items.
   166  		s.Write([]byte("Msg Traces:\n"))
   167  		for i, msgtrace := range err.msgtraces {
   168  			fmt.Fprintf(s, " %4d  %s\n", i, msgtrace.String())
   169  		}
   170  		// Write stack trace.
   171  		if err.stacktrace != nil {
   172  			s.Write([]byte("Stack Trace:\n"))
   173  			frames := runtime.CallersFrames(err.stacktrace)
   174  			for i := 0; ; i++ {
   175  				frame, more := frames.Next()
   176  				fmt.Fprintf(s, " %4d  %s:%d\n", i, frame.File, frame.Line)
   177  				if !more {
   178  					break
   179  				}
   180  			}
   181  		}
   182  		s.Write([]byte("--= /Error =--\n"))
   183  	default:
   184  		// Write msg.
   185  		fmt.Fprintf(s, "%v", err.data)
   186  	}
   187  }
   188  
   189  // ----------------------------------------
   190  // stacktrace & msgtraceItem
   191  
   192  func captureStacktrace(offset int, depth int) []uintptr {
   193  	pcs := make([]uintptr, depth)
   194  	n := runtime.Callers(offset, pcs)
   195  	return pcs[0:n]
   196  }
   197  
   198  type msgtraceItem struct {
   199  	pc  uintptr
   200  	msg string
   201  }
   202  
   203  func (mti msgtraceItem) String() string {
   204  	fnc := runtime.FuncForPC(mti.pc)
   205  	file, line := fnc.FileLine(mti.pc)
   206  	return fmt.Sprintf("%s:%d - %s",
   207  		file, line,
   208  		mti.msg,
   209  	)
   210  }
   211  
   212  // ----------------------------------------
   213  // fmt error
   214  
   215  /*
   216  FmtError is the data type for New() (e.g. New().Data().(FmtError))
   217  Theoretically it could be used to switch on the format string.
   218  
   219  ```go
   220  
   221  	// Error construction
   222  	var err1 error = New("invalid username %v", "BOB")
   223  	var err2 error = New("another kind of error")
   224  	...
   225  	// Error handling
   226  	switch err1.Data().(cmn.FmtError).Format() {
   227  		case "invalid username %v": ...
   228  		case "another kind of error": ...
   229  	    default: ...
   230  	}
   231  
   232  ```
   233  */
   234  type FmtError struct {
   235  	format string
   236  	args   []interface{}
   237  }
   238  
   239  func (fe FmtError) Error() string {
   240  	return fmt.Sprintf(fe.format, fe.args...)
   241  }
   242  
   243  func (fe FmtError) String() string {
   244  	return fmt.Sprintf("FmtError{format:%v,args:%v}",
   245  		fe.format, fe.args)
   246  }
   247  
   248  func (fe FmtError) Format() string {
   249  	return fe.format
   250  }