- package common
-
- import (
- "fmt"
- "runtime"
- )
-
- //----------------------------------------
- // Convenience method.
-
- func ErrorWrap(cause interface{}, format string, args ...interface{}) Error {
- if causeCmnError, ok := cause.(*cmnError); ok { //nolint:gocritic
- msg := fmt.Sprintf(format, args...)
- return causeCmnError.Stacktrace().Trace(1, msg)
- } else if cause == nil {
- return newCmnError(FmtError{format, args}).Stacktrace()
- } else {
- // NOTE: causeCmnError is a typed nil here.
- msg := fmt.Sprintf(format, args...)
- return newCmnError(cause).Stacktrace().Trace(1, msg)
- }
- }
-
- //----------------------------------------
- // Error & cmnError
-
- /*
-
- Usage with arbitrary error data:
-
- ```go
- // Error construction
- type MyError struct{}
- var err1 error = NewErrorWithData(MyError{}, "my message")
- ...
- // Wrapping
- var err2 error = ErrorWrap(err1, "another message")
- if (err1 != err2) { panic("should be the same")
- ...
- // Error handling
- switch err2.Data().(type){
- case MyError: ...
- default: ...
- }
- ```
- */
- type Error interface {
- Error() string
- Stacktrace() Error
- Trace(offset int, format string, args ...interface{}) Error
- Data() interface{}
- }
-
- // New Error with formatted message.
- // The Error's Data will be a FmtError type.
- func NewError(format string, args ...interface{}) Error {
- err := FmtError{format, args}
- return newCmnError(err)
- }
-
- // New Error with specified data.
- func NewErrorWithData(data interface{}) Error {
- return newCmnError(data)
- }
-
- type cmnError struct {
- data interface{} // associated data
- msgtraces []msgtraceItem // all messages traced
- stacktrace []uintptr // first stack trace
- }
-
- var _ Error = &cmnError{}
-
- // NOTE: do not expose.
- func newCmnError(data interface{}) *cmnError {
- return &cmnError{
- data: data,
- msgtraces: nil,
- stacktrace: nil,
- }
- }
-
- // Implements error.
- func (err *cmnError) Error() string {
- return fmt.Sprintf("%v", err)
- }
-
- // Captures a stacktrace if one was not already captured.
- func (err *cmnError) Stacktrace() Error {
- if err.stacktrace == nil {
- var offset = 3
- var depth = 32
- err.stacktrace = captureStacktrace(offset, depth)
- }
- return err
- }
-
- // Add tracing information with msg.
- // Set n=0 unless wrapped with some function, then n > 0.
- func (err *cmnError) Trace(offset int, format string, args ...interface{}) Error {
- msg := fmt.Sprintf(format, args...)
- return err.doTrace(msg, offset)
- }
-
- // Return the "data" of this error.
- // Data could be used for error handling/switching,
- // or for holding general error/debug information.
- func (err *cmnError) Data() interface{} {
- return err.data
- }
-
- func (err *cmnError) doTrace(msg string, n int) Error {
- pc, _, _, _ := runtime.Caller(n + 2) // +1 for doTrace(). +1 for the caller.
- // Include file & line number & msg.
- // Do not include the whole stack trace.
- err.msgtraces = append(err.msgtraces, msgtraceItem{
- pc: pc,
- msg: msg,
- })
- return err
- }
-
- func (err *cmnError) Format(s fmt.State, verb rune) {
- switch verb {
- case 'p':
- s.Write([]byte(fmt.Sprintf("%p", &err)))
- default:
- if s.Flag('#') {
- s.Write([]byte("--= Error =--\n"))
- // Write data.
- s.Write([]byte(fmt.Sprintf("Data: %#v\n", err.data)))
- // Write msg trace items.
- s.Write([]byte(fmt.Sprintf("Msg Traces:\n")))
- for i, msgtrace := range err.msgtraces {
- s.Write([]byte(fmt.Sprintf(" %4d %s\n", i, msgtrace.String())))
- }
- // Write stack trace.
- if err.stacktrace != nil {
- s.Write([]byte(fmt.Sprintf("Stack Trace:\n")))
- for i, pc := range err.stacktrace {
- fnc := runtime.FuncForPC(pc)
- file, line := fnc.FileLine(pc)
- s.Write([]byte(fmt.Sprintf(" %4d %s:%d\n", i, file, line)))
- }
- }
- s.Write([]byte("--= /Error =--\n"))
- } else {
- // Write msg.
- s.Write([]byte(fmt.Sprintf("%v", err.data)))
- }
- }
- }
-
- //----------------------------------------
- // stacktrace & msgtraceItem
-
- func captureStacktrace(offset int, depth int) []uintptr {
- var pcs = make([]uintptr, depth)
- n := runtime.Callers(offset, pcs)
- return pcs[0:n]
- }
-
- type msgtraceItem struct {
- pc uintptr
- msg string
- }
-
- func (mti msgtraceItem) String() string {
- fnc := runtime.FuncForPC(mti.pc)
- file, line := fnc.FileLine(mti.pc)
- return fmt.Sprintf("%s:%d - %s",
- file, line,
- mti.msg,
- )
- }
-
- //----------------------------------------
- // fmt error
-
- /*
-
- FmtError is the data type for NewError() (e.g. NewError().Data().(FmtError))
- Theoretically it could be used to switch on the format string.
-
- ```go
- // Error construction
- var err1 error = NewError("invalid username %v", "BOB")
- var err2 error = NewError("another kind of error")
- ...
- // Error handling
- switch err1.Data().(cmn.FmtError).Format() {
- case "invalid username %v": ...
- case "another kind of error": ...
- default: ...
- }
- ```
- */
- type FmtError struct {
- format string
- args []interface{}
- }
-
- func (fe FmtError) Error() string {
- return fmt.Sprintf(fe.format, fe.args...)
- }
-
- func (fe FmtError) String() string {
- return fmt.Sprintf("FmtError{format:%v,args:%v}",
- fe.format, fe.args)
- }
-
- func (fe FmtError) Format() string {
- return fe.format
- }
|