package exerr import ( "encoding/json" "fmt" "go.mongodb.org/mongo-driver/bson/primitive" "gogs.mikescher.com/BlackForestBytes/goext/langext" "reflect" "time" ) var reflectTypeStr = reflect.TypeOf("") func FromError(err error) *ExErr { //goland:noinspection GoTypeAssertionOnErrors if verr, ok := err.(*ExErr); ok { // A simple ExErr return verr } //goland:noinspection GoTypeAssertionOnErrors if verr, ok := err.(langext.PanicWrappedErr); ok { return &ExErr{ UniqueID: newID(), Category: CatForeign, Type: TypePanic, Severity: SevErr, Timestamp: time.Time{}, StatusCode: nil, Message: "A panic occured", WrappedErrType: fmt.Sprintf("%T", verr), WrappedErr: err, Caller: "", OriginalError: nil, Meta: MetaMap{ "panic_object": {DataType: MDTString, Value: fmt.Sprintf("%+v", verr)}, "panic_type": {DataType: MDTString, Value: fmt.Sprintf("%T", verr)}, "stack": {DataType: MDTString, Value: verr.Stack}, }, Extra: make(map[string]any), } } //goland:noinspection GoTypeAssertionOnErrors if verr, ok := err.(*langext.PanicWrappedErr); ok && verr != nil { return &ExErr{ UniqueID: newID(), Category: CatForeign, Type: TypePanic, Severity: SevErr, Timestamp: time.Time{}, StatusCode: nil, Message: "A panic occured", WrappedErrType: fmt.Sprintf("%T", verr), WrappedErr: err, Caller: "", OriginalError: nil, Meta: MetaMap{ "panic_object": {DataType: MDTString, Value: fmt.Sprintf("%+v", verr.RecoveredObj())}, "panic_type": {DataType: MDTString, Value: fmt.Sprintf("%T", verr.RecoveredObj())}, "stack": {DataType: MDTString, Value: verr.Stack}, }, Extra: make(map[string]any), } } // A foreign error (eg a MongoDB exception) return &ExErr{ UniqueID: newID(), Category: CatForeign, Type: TypeInternal, Severity: SevErr, Timestamp: time.Time{}, StatusCode: nil, Message: err.Error(), WrappedErrType: fmt.Sprintf("%T", err), WrappedErr: err, Caller: "", OriginalError: nil, Meta: getForeignMeta(err), Extra: make(map[string]any), } } func newExErr(cat ErrorCategory, errtype ErrorType, msg string) *ExErr { return &ExErr{ UniqueID: newID(), Category: cat, Type: errtype, Severity: SevErr, Timestamp: time.Now(), StatusCode: nil, Message: msg, WrappedErrType: "", WrappedErr: nil, Caller: callername(2), OriginalError: nil, Meta: make(map[string]MetaValue), Extra: make(map[string]any), } } func wrapExErr(e *ExErr, msg string, cat ErrorCategory, stacktraceskip int) *ExErr { return &ExErr{ UniqueID: newID(), Category: cat, Type: TypeWrap, Severity: e.Severity, Timestamp: time.Now(), StatusCode: e.StatusCode, Message: msg, WrappedErrType: "", WrappedErr: nil, Caller: callername(1 + stacktraceskip), OriginalError: e, Meta: make(map[string]MetaValue), Extra: langext.CopyMap(langext.ForceMap(e.Extra)), } } func getForeignMeta(err error) (mm MetaMap) { mm = make(map[string]MetaValue) defer func() { if panicerr := recover(); panicerr != nil { New(TypePanic, "Panic while trying to get foreign meta"). Str("source", err.Error()). Interface("panic-object", panicerr). Stack(). Print() } }() rval := reflect.ValueOf(err) if rval.Kind() == reflect.Interface || rval.Kind() == reflect.Ptr { rval = reflect.ValueOf(err).Elem() } mm.add("foreign.errortype", MDTString, rval.Type().String()) for k, v := range addMetaPrefix("foreign", getReflectedMetaValues(err, 8)) { mm[k] = v } return mm } func getReflectedMetaValues(value interface{}, remainingDepth int) map[string]MetaValue { if remainingDepth <= 0 { return map[string]MetaValue{} } if langext.IsNil(value) { return map[string]MetaValue{"": {DataType: MDTNil, Value: nil}} } rval := reflect.ValueOf(value) if rval.Type().Kind() == reflect.Ptr { if rval.IsNil() { return map[string]MetaValue{"*": {DataType: MDTNil, Value: nil}} } elem := rval.Elem() return addMetaPrefix("*", getReflectedMetaValues(elem.Interface(), remainingDepth-1)) } if !rval.CanInterface() { return map[string]MetaValue{"": {DataType: MDTString, Value: "<>"}} } raw := rval.Interface() switch ifraw := raw.(type) { case time.Time: return map[string]MetaValue{"": {DataType: MDTTime, Value: ifraw}} case time.Duration: return map[string]MetaValue{"": {DataType: MDTDuration, Value: ifraw}} case int: return map[string]MetaValue{"": {DataType: MDTInt, Value: ifraw}} case int8: return map[string]MetaValue{"": {DataType: MDTInt8, Value: ifraw}} case int16: return map[string]MetaValue{"": {DataType: MDTInt16, Value: ifraw}} case int32: return map[string]MetaValue{"": {DataType: MDTInt32, Value: ifraw}} case int64: return map[string]MetaValue{"": {DataType: MDTInt64, Value: ifraw}} case string: return map[string]MetaValue{"": {DataType: MDTString, Value: ifraw}} case bool: return map[string]MetaValue{"": {DataType: MDTBool, Value: ifraw}} case []byte: return map[string]MetaValue{"": {DataType: MDTBytes, Value: ifraw}} case float32: return map[string]MetaValue{"": {DataType: MDTFloat32, Value: ifraw}} case float64: return map[string]MetaValue{"": {DataType: MDTFloat64, Value: ifraw}} case []int: return map[string]MetaValue{"": {DataType: MDTIntArray, Value: ifraw}} case []int32: return map[string]MetaValue{"": {DataType: MDTInt32Array, Value: ifraw}} case primitive.ObjectID: return map[string]MetaValue{"": {DataType: MDTObjectID, Value: ifraw}} case []string: return map[string]MetaValue{"": {DataType: MDTStringArray, Value: ifraw}} } if rval.Type().Kind() == reflect.Struct { m := make(map[string]MetaValue) for i := 0; i < rval.NumField(); i++ { fieldtype := rval.Type().Field(i) fieldname := fieldtype.Name if fieldtype.IsExported() { for k, v := range addMetaPrefix(fieldname, getReflectedMetaValues(rval.Field(i).Interface(), remainingDepth-1)) { m[k] = v } } } return m } if rval.Type().ConvertibleTo(reflectTypeStr) { return map[string]MetaValue{"": {DataType: MDTString, Value: rval.Convert(reflectTypeStr).String()}} } jsonval, err := json.Marshal(value) if err != nil { return map[string]MetaValue{"": {DataType: MDTString, Value: fmt.Sprintf("Failed to Marshal %T:\n%+v", value, value)}} } return map[string]MetaValue{"": {DataType: MDTString, Value: string(jsonval)}} } func addMetaPrefix(prefix string, m map[string]MetaValue) map[string]MetaValue { if len(m) == 1 { for k, v := range m { if k == "" { return map[string]MetaValue{prefix: v} } } } r := make(map[string]MetaValue, len(m)) for k, v := range m { r[prefix+"."+k] = v } return r }