166 lines
3.4 KiB
Go
166 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|