Mike Schwörer
7577a2dd47
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 50s
299 lines
6.6 KiB
Go
299 lines
6.6 KiB
Go
package exerr
|
|
|
|
import (
|
|
"github.com/rs/xid"
|
|
"github.com/rs/zerolog"
|
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ExErr struct {
|
|
UniqueID string `json:"uniqueID"`
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Category ErrorCategory `json:"category"`
|
|
Severity ErrorSeverity `json:"severity"`
|
|
Type ErrorType `json:"type"`
|
|
|
|
StatusCode *int `json:"statusCode"`
|
|
|
|
Message string `json:"message"`
|
|
WrappedErrType string `json:"wrappedErrType"`
|
|
WrappedErr any `json:"-"`
|
|
Caller string `json:"caller"`
|
|
|
|
OriginalError *ExErr `json:"originalError"`
|
|
|
|
Meta MetaMap `json:"meta"`
|
|
}
|
|
|
|
func (ee *ExErr) Error() string {
|
|
return ee.Message
|
|
}
|
|
|
|
// Unwrap must be implemented so that some error.XXX methods work
|
|
func (ee *ExErr) Unwrap() error {
|
|
if ee.OriginalError == nil {
|
|
return nil // this is neccessary - otherwise we return a wrapped nil and the `x == nil` comparison fails (= panic in errors.Is and other failures)
|
|
}
|
|
return ee.OriginalError
|
|
}
|
|
|
|
// Is must be implemented so that error.Is(x) works
|
|
func (ee *ExErr) Is(e error) bool {
|
|
return IsFrom(ee, e)
|
|
}
|
|
|
|
// As must be implemented so that error.As(x) works
|
|
//
|
|
//goland:noinspection GoTypeAssertionOnErrors
|
|
func (ee *ExErr) As(target any) bool {
|
|
if dstErr, ok := target.(*ExErr); ok {
|
|
|
|
if dst0, ok := ee.contains(dstErr); ok {
|
|
dstErr = dst0
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
} else {
|
|
|
|
val := reflect.ValueOf(target)
|
|
|
|
typStr := val.Type().Elem().String()
|
|
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.Category == CatForeign && curr.WrappedErrType == typStr && curr.WrappedErr != nil {
|
|
val.Elem().Set(reflect.ValueOf(curr.WrappedErr))
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (ee *ExErr) Log(evt *zerolog.Event) {
|
|
evt.Msg(ee.FormatLog(LogPrintFull))
|
|
}
|
|
|
|
func (ee *ExErr) FormatLog(lvl LogPrintLevel) string {
|
|
if lvl == LogPrintShort {
|
|
|
|
msg := ee.Message
|
|
if ee.OriginalError != nil && ee.OriginalError.Category == CatForeign {
|
|
msg = msg + " (" + strings.ReplaceAll(ee.OriginalError.Message, "\n", " ") + ")"
|
|
}
|
|
|
|
if ee.Type != TypeWrap {
|
|
return "[" + ee.Type.Key + "] " + msg
|
|
} else {
|
|
return msg
|
|
}
|
|
|
|
} else if lvl == LogPrintOverview {
|
|
|
|
str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n"
|
|
|
|
indent := ""
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
indent += " "
|
|
|
|
str += indent
|
|
str += "-> "
|
|
strmsg := strings.Trim(curr.Message, " \r\n\t")
|
|
if lbidx := strings.Index(curr.Message, "\n"); lbidx >= 0 {
|
|
strmsg = strmsg[0:lbidx]
|
|
}
|
|
strmsg = langext.StrLimit(strmsg, 61, "...")
|
|
str += strmsg
|
|
str += "\n"
|
|
|
|
}
|
|
return str
|
|
|
|
} else if lvl == LogPrintFull {
|
|
|
|
str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n"
|
|
|
|
indent := ""
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
indent += " "
|
|
|
|
etype := ee.Type.Key
|
|
if ee.Type == TypeWrap {
|
|
etype = "~"
|
|
}
|
|
|
|
str += indent
|
|
str += "-> ["
|
|
str += etype
|
|
if curr.Category == CatForeign {
|
|
str += "|Foreign"
|
|
}
|
|
str += "] "
|
|
str += strings.ReplaceAll(curr.Message, "\n", " ")
|
|
if curr.Caller != "" {
|
|
str += " (@ "
|
|
str += curr.Caller
|
|
str += ")"
|
|
}
|
|
str += "\n"
|
|
|
|
if curr.Meta.Any() {
|
|
meta := indent + " {" + curr.Meta.FormatOneLine(240) + "}"
|
|
if len(meta) < 200 {
|
|
str += meta
|
|
str += "\n"
|
|
} else {
|
|
str += curr.Meta.FormatMultiLine(indent+" ", " ", 1024)
|
|
str += "\n"
|
|
}
|
|
}
|
|
}
|
|
return str
|
|
|
|
} else {
|
|
|
|
return "[?[" + ee.UniqueID + "]?]"
|
|
|
|
}
|
|
}
|
|
|
|
func (ee *ExErr) ShortLog(evt *zerolog.Event) {
|
|
ee.Meta.Apply(evt, langext.Ptr(240)).Msg(ee.FormatLog(LogPrintShort))
|
|
}
|
|
|
|
// RecursiveMessage returns the message to show
|
|
// = first error (top-down) that is not wrapping/foreign/empty
|
|
func (ee *ExErr) RecursiveMessage() string {
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.Message != "" && curr.Category != CatWrap && curr.Category != CatForeign {
|
|
return curr.Message
|
|
}
|
|
}
|
|
|
|
// fallback to self
|
|
return ee.Message
|
|
}
|
|
|
|
// RecursiveType returns the statuscode to use
|
|
// = first error (top-down) that is not wrapping/empty
|
|
func (ee *ExErr) RecursiveType() ErrorType {
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.Type != TypeWrap {
|
|
return curr.Type
|
|
}
|
|
|
|
}
|
|
|
|
// fallback to self
|
|
return ee.Type
|
|
}
|
|
|
|
// RecursiveStatuscode returns the HTTP Statuscode to use
|
|
// = first error (top-down) that has a statuscode set
|
|
func (ee *ExErr) RecursiveStatuscode() *int {
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.StatusCode != nil {
|
|
return langext.Ptr(*curr.StatusCode)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RecursiveCategory returns the ErrorCategory to use
|
|
// = first error (top-down) that has a statuscode set
|
|
func (ee *ExErr) RecursiveCategory() ErrorCategory {
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.Category != CatWrap {
|
|
return curr.Category
|
|
}
|
|
}
|
|
|
|
// fallback to <empty>
|
|
return ee.Category
|
|
}
|
|
|
|
// RecursiveMeta searches (top-down) for teh first error that has a meta value with teh specified key
|
|
// and returns its value (or nil)
|
|
func (ee *ExErr) RecursiveMeta(key string) *MetaValue {
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if metaval, ok := curr.Meta[key]; ok {
|
|
return langext.Ptr(metaval)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Depth returns the depth of recursively contained errors
|
|
func (ee *ExErr) Depth() int {
|
|
if ee.OriginalError == nil {
|
|
return 1
|
|
} else {
|
|
return ee.OriginalError.Depth() + 1
|
|
}
|
|
}
|
|
|
|
// contains test if the supplied error is contained in this error (anywhere in the chain)
|
|
func (ee *ExErr) contains(original *ExErr) (*ExErr, bool) {
|
|
if original == nil {
|
|
return nil, false
|
|
}
|
|
|
|
if ee == original {
|
|
return ee, true
|
|
}
|
|
|
|
for curr := ee; curr != nil; curr = curr.OriginalError {
|
|
if curr.equalsDirectProperties(curr) {
|
|
return curr, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// equalsDirectProperties tests if ee and other are equals, but only looks at primary properties (not `OriginalError` or `Meta`)
|
|
func (ee *ExErr) equalsDirectProperties(other *ExErr) bool {
|
|
|
|
if ee.UniqueID != other.UniqueID {
|
|
return false
|
|
}
|
|
if ee.Timestamp != other.Timestamp {
|
|
return false
|
|
}
|
|
if ee.Category != other.Category {
|
|
return false
|
|
}
|
|
if ee.Severity != other.Severity {
|
|
return false
|
|
}
|
|
if ee.Type != other.Type {
|
|
return false
|
|
}
|
|
if ee.StatusCode != other.StatusCode {
|
|
return false
|
|
}
|
|
if ee.Message != other.Message {
|
|
return false
|
|
}
|
|
if ee.WrappedErrType != other.WrappedErrType {
|
|
return false
|
|
}
|
|
if ee.Caller != other.Caller {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func newID() string {
|
|
return xid.New().String()
|
|
}
|