|
| 1 | +// Package reason handles error reasons to separate internal from user errors. |
| 2 | +package reason |
| 3 | + |
| 4 | +import ( |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | +) |
| 8 | + |
| 9 | +// InternalReason is the reason set when the system is experiencing |
| 10 | +// an error that the user cannot resolve. |
| 11 | +const InternalReason = "The system has an internal error" |
| 12 | + |
| 13 | +// Error is a reason error that is detectable. |
| 14 | +// |
| 15 | +// This should not be used as a "normal" error, |
| 16 | +// instead extract the reasons from the chains |
| 17 | +// using Extract. |
| 18 | +type Error struct { |
| 19 | + // Msg is the reason message. |
| 20 | + Msg string |
| 21 | +} |
| 22 | + |
| 23 | +// Errorf returns a formatted reason error. |
| 24 | +func Errorf(format string, a ...any) Error { |
| 25 | + return Error{Msg: fmt.Sprintf(format, a...)} |
| 26 | +} |
| 27 | + |
| 28 | +// Error return the reason as if it were a message. |
| 29 | +// This is used to conform with the error type. |
| 30 | +func (e Error) Error() string { |
| 31 | + return e.Msg |
| 32 | +} |
| 33 | + |
| 34 | +// Extract removes all reason errors from the error |
| 35 | +// chains, returning all other errors and the reason |
| 36 | +// messages. |
| 37 | +func Extract(err error) ([]string, error) { |
| 38 | + //nolint:errorlint // This is the only way to check for the interface. |
| 39 | + switch x := err.(type) { |
| 40 | + case interface{ Unwrap() []error }: |
| 41 | + var ( |
| 42 | + reasons []string |
| 43 | + errs []error |
| 44 | + ) |
| 45 | + for _, err = range x.Unwrap() { |
| 46 | + r, e := Extract(err) |
| 47 | + reasons = append(reasons, r...) |
| 48 | + if e != nil { |
| 49 | + errs = append(errs, e) |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + switch len(errs) { |
| 54 | + case 0: |
| 55 | + return reasons, nil |
| 56 | + case 1: |
| 57 | + return reasons, errs[0] |
| 58 | + default: |
| 59 | + return reasons, errors.Join(errs...) |
| 60 | + } |
| 61 | + case interface{ Unwrap() error }: |
| 62 | + return Extract(x.Unwrap()) |
| 63 | + default: |
| 64 | + var r Error |
| 65 | + if errors.As(err, &r) { |
| 66 | + return []string{r.Msg}, nil |
| 67 | + } |
| 68 | + return nil, err |
| 69 | + } |
| 70 | +} |
0 commit comments