Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom error type. #419

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion concurrency.go
Expand Up @@ -15,7 +15,7 @@ func (s *synchronize) Do(cb func()) {
// Synchronize wraps the underlying callback in a mutex. It receives an optional mutex.
func Synchronize(opt ...sync.Locker) *synchronize {
if len(opt) > 1 {
panic("unexpected arguments")
LoPanic("unexpected arguments")
} else if len(opt) == 0 {
opt = append(opt, &sync.Mutex{})
}
Expand Down
88 changes: 83 additions & 5 deletions errors.go
Expand Up @@ -6,11 +6,17 @@ import (
"reflect"
)

// LoPanic is user custom panic. example wrap error stack.
var LoPanic = func(e any) { panic(e) }

// LoErrorF is user custom error. example wrap error stack.
var LoErrorF = fmt.Errorf

// Validate is a helper that creates an error when a condition is not met.
// Play: https://go.dev/play/p/vPyh51XpCBt
func Validate(ok bool, format string, args ...any) error {
if !ok {
return fmt.Errorf(fmt.Sprintf(format, args...))
return LoErrorF(fmt.Sprintf(format, args...))
}
return nil
}
Expand Down Expand Up @@ -42,19 +48,19 @@ func must(err any, messageArgs ...interface{}) {
message = "not ok"
}

panic(message)
LoPanic(message)
}

case error:
message := messageFromMsgAndArgs(messageArgs...)
if message != "" {
panic(message + ": " + e.Error())
LoPanic(message + ": " + e.Error())
} else {
panic(e.Error())
LoPanic(e.Error())
}

default:
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
LoPanic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
}
}

Expand Down Expand Up @@ -113,6 +119,78 @@ func Must6[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any](val1 T1, val2 T2, val
return val1, val2, val3, val4, val5, val6
}

type wrapErrPrefixMsg struct {
Base error
Attach string
}

func (err wrapErrPrefixMsg) Error() string {
if err.Attach != "" {
return err.Attach + ": " + err.Base.Error()
}
return err.Base.Error()
}
func (err wrapErrPrefixMsg) String() string {
return err.Error()
}
func (err wrapErrPrefixMsg) Unwrap() error {
return err.Base
}

func mustE(err error, messageArgs ...any) {
if err == nil {
return
}
message := messageFromMsgAndArgs(messageArgs...)
LoPanic(wrapErrPrefixMsg{Base: err, Attach: message})
}

func MustE[T any](val T, err error, messageArgs ...any) T {
mustE(err, messageArgs...)
return val
}

// MustE0 has the same behavior as Must, but callback returns no variable.
func MustE0(err error, messageArgs ...any) {
mustE(err, messageArgs...)
}

// MustE1 is an alias to MustE
func MustE1[T1 any](val1 T1, err error, messageArgs ...any) T1 {
mustE(err, messageArgs...)
return val1
}

// MustE2 has the same behavior as MustE, but callback returns 2 variables.
func MustE2[T1, T2 any](val1 T1, val2 T2, err error, messageArgs ...any) (T1, T2) {
mustE(err, messageArgs...)
return val1, val2
}

// MustE3 has the same behavior as MustE, but callback returns 3 variables.
func MustE3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err error, messageArgs ...any) (T1, T2, T3) {
mustE(err, messageArgs...)
return val1, val2, val3
}

// MustE4 has the same behavior as MustE, but callback returns 4 variables.
func MustE4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err error, messageArgs ...any) (T1, T2, T3, T4) {
mustE(err, messageArgs...)
return val1, val2, val3, val4
}

// MustE5 has the same behavior as MustE, but callback returns 5 variables.
func MustE5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err error, messageArgs ...any) (T1, T2, T3, T4, T5) {
mustE(err, messageArgs...)
return val1, val2, val3, val4, val5
}

// MustE6 has the same behavior as MustE, but callback returns 6 variables.
func MustE6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err error, messageArgs ...any) (T1, T2, T3, T4, T5, T6) {
mustE(err, messageArgs...)
return val1, val2, val3, val4, val5, val6
}

// Try calls the function and return false in case of error.
func Try(callback func() error) (ok bool) {
ok = true
Expand Down
163 changes: 163 additions & 0 deletions errors_test.go
Expand Up @@ -3,6 +3,7 @@ package lo
import (
"errors"
"fmt"
stackErrors "github.com/pkg/errors"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -597,3 +598,165 @@ func TestErrorsAs(t *testing.T) {
is.False(ok)
is.Nil(nil, err)
}

func TestMustE(t *testing.T) {
t.Parallel()
is := assert.New(t)

{
is.Equal("foo", MustE("foo", nil))
is.PanicsWithError("something went wrong", func() {
MustE("", errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail: something went wrong", func() {
MustE("", errors.New("something went wrong"), "operation shouldn't fail")
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE("", errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
is.PanicsWithError("something went wrong", func() {
MustE0(errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE0(errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
is.NotPanics(func() {
MustE0(nil)
})
}

{
val1 := MustE1(1, nil)
is.Equal(1, val1)
is.PanicsWithError("something went wrong", func() {
MustE1(1, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE1(1, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
val1, val2 := MustE2(1, 2, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.PanicsWithError("something went wrong", func() {
MustE2(1, 2, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE2(1, 2, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
val1, val2, val3 := MustE3(1, 2, 3, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.PanicsWithError("something went wrong", func() {
MustE3(1, 2, 3, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE3(1, 2, 3, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
val1, val2, val3, val4 := MustE4(1, 2, 3, 4, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.PanicsWithError("something went wrong", func() {
MustE4(1, 2, 3, 4, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE4(1, 2, 3, 4, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
val1, val2, val3, val4, val5 := MustE5(1, 2, 3, 4, 5, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.PanicsWithError("something went wrong", func() {
MustE5(1, 2, 3, 4, 5, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE5(1, 2, 3, 4, 5, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}

{
val1, val2, val3, val4, val5, val6 := MustE6(1, 2, 3, 4, 5, 6, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.Equal(6, val6)
is.PanicsWithError("something went wrong", func() {
MustE6(1, 2, 3, 4, 5, 6, errors.New("something went wrong"))
})
is.PanicsWithError("operation shouldn't fail with foo: something went wrong", func() {
MustE6(1, 2, 3, 4, 5, 6, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
}

func TestErrorWrapUnWrap(t *testing.T) {

t.Run("wrap as", func(t *testing.T) {
e, ok := TryWithErrorValue(func() error {
MustE("foo", customErr{A: 11, B: 22})
return nil
})
assert.False(t, ok)
err, ok := e.(error)
assert.True(t, ok)
te, ok := ErrorsAs[customErr](err)
assert.True(t, ok)
assert.Equal(t, te.A, 11)
assert.Equal(t, te.B, 22)

})
}

type customErr struct {
A int
B int
}

func (e customErr) Error() string {
return fmt.Sprintf("customErr(%d,%d)", e.A, e.B)
}
func (e customErr) String() string {
return e.Error()
}

func TestErrorGlobalErrHandler(t *testing.T) {
t.Run("wrap stack", func(t *testing.T) {
LoPanic = func(e any) {
if err, ok := e.(error); ok {
if err.Error() == "wrap callstack" {
e = stackErrors.WithStack(err)
}
}
panic(e)
}

err, ok := TryWithErrorValue(func() error {
MustE("foo", errors.New("wrap callstack"))
return nil
})
assert.False(t, ok)
fullErrStr := fmt.Sprintf("%+v", err)
assert.Contains(t, fullErrStr, "/errors_test.go:", fullErrStr)
})
}
5 changes: 2 additions & 3 deletions find.go
@@ -1,7 +1,6 @@
package lo

import (
"fmt"
"math/rand"

"golang.org/x/exp/constraints"
Expand Down Expand Up @@ -316,7 +315,7 @@ func Last[T any](collection []T) (T, error) {

if length == 0 {
var t T
return t, fmt.Errorf("last: cannot extract the last element of an empty slice")
return t, LoErrorF("last: cannot extract the last element of an empty slice")
}

return collection[length-1], nil
Expand All @@ -329,7 +328,7 @@ func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
l := len(collection)
if n >= l || -n > l {
var t T
return t, fmt.Errorf("nth: %d out of slice bounds", n)
return t, LoErrorF("nth: %d out of slice bounds", n)
}

if n >= 0 {
Expand Down
2 changes: 1 addition & 1 deletion slice.go
Expand Up @@ -166,7 +166,7 @@ func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U
// Play: https://go.dev/play/p/EeKl0AuTehH
func Chunk[T any](collection []T, size int) [][]T {
if size <= 0 {
panic("Second parameter must be greater than 0")
LoPanic("Second parameter must be greater than 0")
}

chunksNum := len(collection) / size
Expand Down
6 changes: 3 additions & 3 deletions string.go
Expand Up @@ -20,10 +20,10 @@ var (
// Play: https://go.dev/play/p/rRseOQVVum4
func RandomString(size int, charset []rune) string {
if size <= 0 {
panic("lo.RandomString: Size parameter must be greater than 0")
LoPanic("lo.RandomString: Size parameter must be greater than 0")
}
if len(charset) <= 0 {
panic("lo.RandomString: Charset parameter must not be empty")
LoPanic("lo.RandomString: Charset parameter must not be empty")
}

b := make([]rune, size)
Expand Down Expand Up @@ -63,7 +63,7 @@ func Substring[T ~string](str T, offset int, length uint) T {
// Play: https://go.dev/play/p/__FLTuJVz54
func ChunkString[T ~string](str T, size int) []T {
if size <= 0 {
panic("lo.ChunkString: Size parameter must be greater than 0")
LoPanic("lo.ChunkString: Size parameter must be greater than 0")
}

if len(str) == 0 {
Expand Down