package adverr import ( "errors" "reflect" "strings" ) // Error is a wrapper for error with stack trace type Error struct { msg string callStack *CallStack tmpl *ErrTmpl cause error prev []error } // New returns a new Error with the given message func New(msg string) *Error { return &Error{ msg: msg, callStack: Trace(2), } } // Wrap returns a new Error with the given message which is caused by cause func Wrap(msg string, cause error) *Error { return &Error{ msg: msg, cause: cause, callStack: Trace(2), } } // Chain returns a new Error with the given message and a slice of errors // which were caused in the same function in succession func Chain(msg string, errors []error) *Error { return &Error{ msg: msg, callStack: Trace(2), prev: errors, } } func errtype(err error) string { if e, ok := err.(*Error); ok && e.tmpl != nil { return errtype(e.tmpl) } else if tmpl, ok := err.(*ErrTmpl); ok { return tmpl.name } t := reflect.TypeOf(err) for t.Kind() == reflect.Ptr { t = t.Elem() } return t.PkgPath() + "." + t.Name() } func (e *Error) Unwrap() error { return e.cause } func (e *Error) Error() string { b := new(strings.Builder) printErr(e, b) return b.String() } func (e *Error) Message() string { return e.msg } func (e *Error) Stack() *CallStack { return e.callStack } // Is implements the error equality function used by errors.Is() // It returns true if the error is the same instance or is created using the same ErrTmpl func (e *Error) Is(target error) bool { // same error instance if target == e { return true } // no template used, therefore no equality possible if e.tmpl == nil { return false } // same template, therefore errors are equal to another if tErr, ok := target.(*Error); ok { return tErr.tmpl == e.tmpl } // target is the template itself, therefore they are considered equal to another if tTmpl, ok := target.(*ErrTmpl); ok { return tTmpl == e.tmpl } return false } // Get Returns the first error in the chain for which errors.Is(target) returns true func (e *Error) Get(target error) error { if e.prev == nil { return nil } for _, prevErr := range e.prev { if errors.Is(prevErr, target) { return prevErr } } return nil } // GetByIndex returns the i'th error in the chain func (e *Error) GetByIndex(i int) error { if e.prev == nil { return nil } return e.prev[i] } // Contains is a shorthand for Get(target) != nil. // Can be considered as an errors.Is function but for chains instead of causes func (e *Error) Contains(target error) bool { return e.Get(target) != nil } // Chain returns a slice of all chained errors func (e *Error) Chain() []error { return e.prev[:] } func printErr(err error, b *strings.Builder) { e, ok := err.(*Error) if ok { b.WriteString(errtype(e)) b.WriteString(": ") b.WriteString(e.msg) b.WriteString("\n") b.WriteString(e.callStack.String()) } else { b.WriteString(errtype(err)) b.WriteString(": ") b.WriteString(err.Error()) b.WriteString("\n") b.WriteString("\t(Unknown source)\n") } cause := errors.Unwrap(err) if cause != nil { b.WriteString("Caused by ") printErr(cause, b) } if ok { for _, prevErr := range e.prev { b.WriteString("Previously thrown ") printErr(prevErr, b) } } }