package errx // Package `errx` named like this to not confuse with built-in `errors` package. import "errors" type detailedError struct { error details []any } func (e *detailedError) Unwrap() error { return e.error } func (e *detailedError) Details() []any { return e.details } // WithDetails creates new error value with embedded details func WithDetails(err error, details ...any) error { derr := &detailedError{} if errors.As(err, &derr) { derr.details = append(derr.details, details...) return err } return &detailedError{ error: err, details: details, } } // Details will extract details array from error func Details(err error) []any { if err == nil { return nil } // detailedError on top level of errors chain if derr, ok := err.(interface{ Details() []any }); ok { return derr.Details() } // detailed error were wrapped into another error, and we need to dig it up derr := &detailedError{} if errors.As(err, &derr) { return derr.Details() } return nil } // LookupDetails will find some specific details by its type // NOTE: I'm not sure it's good idea or expected behaviour for function named Lookup func LookupDetails[T any](err error) T { var empty T for _, d := range Details(err) { if v, ok := d.(T); ok { return v } } return empty }