adverr/error.go

158 lines
3.3 KiB
Go

package adverr
import (
"errors"
"reflect"
"strings"
)
// Error is a wrapper for error with stack trace
type Error struct {
msg string
callTrace *CallTrace
tmpl *ErrTmpl
cause error
prev []error
}
// New returns a new Error with the given message
func New(msg string) *Error {
return &Error{
msg: msg,
callTrace: 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,
callTrace: 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,
callTrace: 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()
}
// 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.callTrace.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)
}
}
}