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 ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// must panics if err is error or false.
|
// MustChecker panics if err is error or false.
|
||||||
func must(err any, messageArgs ...any) {
|
var MustChecker = func(err any, messageArgs ...any) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,14 +63,14 @@ func must(err any, messageArgs ...any) {
|
|||||||
// and panics if err is error or false.
|
// and panics if err is error or false.
|
||||||
// Play: https://go.dev/play/p/fOqtX5HudtN
|
// Play: https://go.dev/play/p/fOqtX5HudtN
|
||||||
func Must[T any](val T, err any, messageArgs ...any) T {
|
func Must[T any](val T, err any, messageArgs ...any) T {
|
||||||
must(err, messageArgs...)
|
MustChecker(err, messageArgs...)
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must0 has the same behavior as Must, but callback returns no variable.
|
// Must0 has the same behavior as Must, but callback returns no variable.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||||
func Must0(err any, messageArgs ...any) {
|
func Must0(err any, messageArgs ...any) {
|
||||||
must(err, messageArgs...)
|
MustChecker(err, messageArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must1 is an alias to Must.
|
// 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.
|
// Must2 has the same behavior as Must, but callback returns 2 variables.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
||||||
func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) {
|
func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) {
|
||||||
must(err, messageArgs...)
|
MustChecker(err, messageArgs...)
|
||||||
return val1, val2
|
return val1, val2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must3 has the same behavior as Must, but callback returns 3 variables.
|
// Must3 has the same behavior as Must, but callback returns 3 variables.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// 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) {
|
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
|
return val1, val2, val3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must4 has the same behavior as Must, but callback returns 4 variables.
|
// Must4 has the same behavior as Must, but callback returns 4 variables.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// 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) {
|
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
|
return val1, val2, val3, val4
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must5 has the same behavior as Must, but callback returns 5 variables.
|
// Must5 has the same behavior as Must, but callback returns 5 variables.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// 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) {
|
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
|
return val1, val2, val3, val4, val5
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must6 has the same behavior as Must, but callback returns 6 variables.
|
// Must6 has the same behavior as Must, but callback returns 6 variables.
|
||||||
// Play: https://go.dev/play/p/TMoWrRp3DyC
|
// 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) {
|
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
|
return val1, val2, val3, val4, val5, val6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+114
-4
@@ -3,6 +3,11 @@ package lo
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -22,8 +27,8 @@ func TestValidate(t *testing.T) {
|
|||||||
is.NoError(result2)
|
is.NoError(result2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMust(t *testing.T) {
|
func TestMust(t *testing.T) { //nolint:paralleltest
|
||||||
t.Parallel()
|
// t.Parallel()
|
||||||
is := assert.New(t)
|
is := assert.New(t)
|
||||||
|
|
||||||
is.Equal("foo", Must("foo", nil))
|
is.Equal("foo", Must("foo", nil))
|
||||||
@@ -63,8 +68,8 @@ func TestMust(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMustX(t *testing.T) {
|
func TestMustX(t *testing.T) { //nolint:paralleltest
|
||||||
t.Parallel()
|
// t.Parallel()
|
||||||
is := assert.New(t)
|
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) {
|
func TestTry(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
is := assert.New(t)
|
is := assert.New(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user