-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
95 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Package reason contains types and functions commonly used to handle reason detection and extraction. | ||
package reason | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// InternalReason is the reason set when the system is experiencing | ||
// an error that the user cannot resolve. | ||
const InternalReason = "The system has an internal error" | ||
|
||
// Error is a reason error that is detectable. | ||
// | ||
// This should not be used as a "normal" error, | ||
// instead extract the reasons from the chains | ||
// using Extract. | ||
type Error struct { | ||
// Msg is the reason message. | ||
Msg string | ||
} | ||
|
||
// Errorf returns a formatted reason error. | ||
func Errorf(format string, a ...any) Error { | ||
return Error{Msg: fmt.Sprintf(format, a...)} | ||
} | ||
|
||
// Error return the reason as if it were a message. | ||
// This is used to conform with the error type. | ||
func (e Error) Error() string { | ||
return e.Msg | ||
} | ||
|
||
// Extract removes all reason errors from the error | ||
// chains, returning all other errors and the reason | ||
// messages. | ||
func Extract(err error) ([]string, error) { | ||
//nolint:errorlint // This is the only way to check for the interface. | ||
switch x := err.(type) { | ||
case interface{ Unwrap() []error }: | ||
var ( | ||
reasons []string | ||
errs []error | ||
) | ||
for _, err = range x.Unwrap() { | ||
r, e := Extract(err) | ||
reasons = append(reasons, r...) | ||
if e != nil { | ||
errs = append(errs, e) | ||
} | ||
} | ||
|
||
switch len(errs) { | ||
case 0: | ||
return reasons, nil | ||
case 1: | ||
return reasons, errs[0] | ||
default: | ||
return reasons, errors.Join(errs...) | ||
} | ||
case interface{ Unwrap() error }: | ||
return Extract(x.Unwrap()) | ||
default: | ||
var r Error | ||
if errors.As(err, &r) { | ||
return []string{r.Msg}, nil | ||
} | ||
return nil, err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package reason_test | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hamba/pkg/v2/errors/reason" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestExtract(t *testing.T) { | ||
var errs error | ||
errs = errors.Join(errs, errors.New("test1")) | ||
errs = errors.Join(errs, reason.Error{Msg: "First Error"}) | ||
errs = errors.Join(errs, fmt.Errorf("some error: %w", reason.Errorf("Second %s", "Error"))) | ||
errs = errors.Join(errs, errors.New("test2")) | ||
|
||
reasons, errs := reason.Extract(errs) | ||
|
||
require.NotEmpty(t, errs) | ||
assert.Equal(t, "test1\ntest2", errs.Error()) | ||
assert.Equal(t, []string{"First Error", "Second Error"}, reasons) | ||
} |