mirror of
https://github.com/samber/lo.git
synced 2026-04-22 15:37:14 +08:00
Must support Custom error handler. (#752)
* Custom error type. * remove "github.com/pkg/errors" depend * disable some Test Parallel * fix test code in go 1.18 * golangcli-lint warning fix --------- Co-authored-by: Samuel Berthe <dev@samuel-berthe.fr>
This commit is contained in:
@@ -30,8 +30,8 @@ func messageFromMsgAndArgs(msgAndArgs ...any) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// must panics if err is error or false.
|
||||
func must(err any, messageArgs ...any) {
|
||||
// MustChecker panics if err is error or false.
|
||||
var MustChecker = func(err any, messageArgs ...any) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@@ -63,14 +63,14 @@ func must(err any, messageArgs ...any) {
|
||||
// and panics if err is error or false.
|
||||
// Play: https://go.dev/play/p/fOqtX5HudtN
|
||||
func Must[T any](val T, err any, messageArgs ...any) T {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val
|
||||
}
|
||||
|
||||
// Must0 has the same behavior as Must, but callback returns no variable.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must0(err any, messageArgs ...any) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
}
|
||||
|
||||
// Must1 is an alias to Must.
|
||||
@@ -82,35 +82,35 @@ func Must1[T any](val T, err any, messageArgs ...any) T {
|
||||
// Must2 has the same behavior as Must, but callback returns 2 variables.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val1, val2
|
||||
}
|
||||
|
||||
// Must3 has the same behavior as Must, but callback returns 3 variables.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...any) (T1, T2, T3) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val1, val2, val3
|
||||
}
|
||||
|
||||
// Must4 has the same behavior as Must, but callback returns 4 variables.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...any) (T1, T2, T3, T4) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val1, val2, val3, val4
|
||||
}
|
||||
|
||||
// Must5 has the same behavior as Must, but callback returns 5 variables.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...any) (T1, T2, T3, T4, T5) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val1, val2, val3, val4, val5
|
||||
}
|
||||
|
||||
// Must6 has the same behavior as Must, but callback returns 6 variables.
|
||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||
func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...any) (T1, T2, T3, T4, T5, T6) {
|
||||
must(err, messageArgs...)
|
||||
MustChecker(err, messageArgs...)
|
||||
return val1, val2, val3, val4, val5, val6
|
||||
}
|
||||
|
||||
|
||||
+114
-4
@@ -3,6 +3,11 @@ package lo
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -22,8 +27,8 @@ func TestValidate(t *testing.T) {
|
||||
is.NoError(result2)
|
||||
}
|
||||
|
||||
func TestMust(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestMust(t *testing.T) { //nolint:paralleltest
|
||||
// t.Parallel()
|
||||
is := assert.New(t)
|
||||
|
||||
is.Equal("foo", Must("foo", nil))
|
||||
@@ -63,8 +68,8 @@ func TestMust(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestMustX(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestMustX(t *testing.T) { //nolint:paralleltest
|
||||
// t.Parallel()
|
||||
is := assert.New(t)
|
||||
|
||||
{
|
||||
@@ -254,6 +259,111 @@ func TestMustX(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustCheckerWithStack(err any, messageArgs ...any) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case bool:
|
||||
if !e {
|
||||
message := messageFromMsgAndArgs(messageArgs...)
|
||||
if message == "" {
|
||||
message = "not ok"
|
||||
}
|
||||
|
||||
// panic(stackErrors.New(message))
|
||||
panic(errorsJoin(errors.New(message), errors.New(string(debug.Stack()))))
|
||||
}
|
||||
|
||||
case error:
|
||||
message := messageFromMsgAndArgs(messageArgs...)
|
||||
if message != "" {
|
||||
// panic(stackErrors.Wrap(e, message))
|
||||
panic(errorsJoin(e, errors.New(message), errors.New(string(debug.Stack()))))
|
||||
}
|
||||
// panic(stackErrors.WithStack(e))
|
||||
panic(errorsJoin(e, errors.New(string(debug.Stack()))))
|
||||
|
||||
default:
|
||||
// panic(stackErrors.New("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error"))
|
||||
panic(errorsJoin(errors.New("must: invalid err type '"+reflect.TypeOf(err).Name()+"', should either be a bool or an error"),
|
||||
errors.New(string(debug.Stack()))))
|
||||
}
|
||||
}
|
||||
|
||||
// errorsJoin: var errorsJoin = errors.Join // only go 1.20+, not in go 1.18 .
|
||||
func errorsJoin(es ...error) joinErrors { return joinErrors(es) }
|
||||
|
||||
type joinErrors []error
|
||||
|
||||
func (es joinErrors) Is(target error) bool {
|
||||
for _, e := range es {
|
||||
if errors.Is(e, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return error(es) == target
|
||||
}
|
||||
|
||||
func (es joinErrors) Error() string {
|
||||
sb := strings.Builder{}
|
||||
for _, e := range es {
|
||||
sb.WriteString(e.Error())
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (es joinErrors) As(t any) bool {
|
||||
for _, e := range es {
|
||||
if errors.As(e, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestMustUserCustomHandler(t *testing.T) { //nolint:paralleltest
|
||||
oldMustChecker := MustChecker
|
||||
MustChecker = mustCheckerWithStack
|
||||
defer func() {
|
||||
MustChecker = oldMustChecker
|
||||
}()
|
||||
|
||||
t.Run("wrap stack", func(t *testing.T) { //nolint:paralleltest
|
||||
err, ok := TryWithErrorValue(func() error {
|
||||
Must("foo", errors.New("wrap callstack"))
|
||||
return nil
|
||||
})
|
||||
assert.False(t, ok)
|
||||
fullErrStr := fmt.Sprintf("%+v", err)
|
||||
assert.Contains(t, fullErrStr, "/errors_test.go:", fullErrStr)
|
||||
})
|
||||
t.Run("wrap as", func(t *testing.T) { //nolint:paralleltest
|
||||
e, ok := TryWithErrorValue(func() error {
|
||||
Must("foo", errorsJoin(io.EOF, &url.Error{
|
||||
Op: "test op",
|
||||
URL: "test url",
|
||||
Err: io.ErrUnexpectedEOF,
|
||||
}))
|
||||
return nil
|
||||
})
|
||||
assert.False(t, ok)
|
||||
err, ok := e.(error)
|
||||
assert.True(t, ok)
|
||||
errURL, ok := ErrorsAs[*url.Error](err)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, errURL)
|
||||
if errURL != nil {
|
||||
assert.Equal(t, "test url", errURL.URL)
|
||||
assert.Equal(t, "test op", errURL.Op)
|
||||
assert.ErrorIs(t, err, io.EOF)
|
||||
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTry(t *testing.T) {
|
||||
t.Parallel()
|
||||
is := assert.New(t)
|
||||
|
||||
Reference in New Issue
Block a user