Skip to content

Commit

Permalink
feat: add errors/reason (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
antiphp authored Oct 2, 2024
1 parent a68b800 commit c80e0fa
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 0 deletions.
70 changes: 70 additions & 0 deletions errors/reason/reason.go
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
}
}
25 changes: 25 additions & 0 deletions errors/reason/reason_test.go
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)
}

0 comments on commit c80e0fa

Please sign in to comment.