Skip to content

Added 5 more utility functions for errors and type manipulation #624

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

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ Type manipulation helpers:
- [CoalesceSliceOrEmpty](#coalescesliceorempty)
- [CoalesceMap](#coalescemap)
- [CoalesceMapOrEmpty](#coalescemaporempty)
- [Cast](#cast)
= [CastJSON](#castjson)
- [FromBytes](#frombytes)

Function helpers:

Expand Down Expand Up @@ -326,6 +329,8 @@ Error handling:
- [TryWithErrorValue](#trywitherrorvalue)
- [TryCatchWithErrorValue](#trycatchwitherrorvalue)
- [ErrorsAs](#errorsas)
- [Ok](#ok)
- [OkOr](#okor)

Constraints:

Expand Down Expand Up @@ -3429,6 +3434,84 @@ result := lo.CoalesceMapOrEmpty(nil, map[string]int{})
// {}
```

### Cast

Converts any type to a given type. If conversion fails, it returns the zero value of the given type. This is a type-safe version of type assertion.

Cast enforces strict type assertion, for example trying to convert a number of 10 to float64 will return 0.0 instead of 10.0.

```go
var n any = 10

fmt.Println(lo.Cast[int](n) == 10) // true

fmt.Println(lo.Cast[float64](n) == 10.0) // false
```

[[play](https://go.dev/play/p/IxL5n8tOvTW)]


### CastJSON

Converts any type to a given type based on their json representations. It partially fills the target in case they are not directly compatible. Any errors are ignored.

```go
type TestObject1 struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
}

type TestObject2 struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
Qux string `json:"qux"`
}

testObject1 := TestObject1{
Foo: "bar",
Bar: 42,
}

testObject2 := TestObject2{
Foo: "bar",
Bar: 42,
Qux: "baz",
}

castedTestObject1 := lo.CastJSON[TestObject1](testObject2)

fmt.Println(castedTestObject1.Foo == testObject1.Foo) // true
fmt.Println(castedTestObject1.Bar == testObject1.Bar) // true
```

[[play](https://go.dev/play/p/s42Brn9UOug)]

### FromBytes

Converts a byte array to a given type. Ignores any errors.

```go
bytes, _ := json.Marshal("foo")

fmt.Println(lo.FromBytes[string](bytes) == "foo") // true

type TestObject struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
}

testObject := TestObject{
Foo: "bar",
Bar: 42,
}

bytes, _ = json.Marshal(testObject)

fmt.Println(lo.FromBytes[TestObject](bytes) == testObject) // true
```

[[play](https://go.dev/play/p/T9dfh5QPEzq)]

### Partial

Returns new function that, when called, has its first argument set to the provided value.
Expand Down Expand Up @@ -4095,6 +4178,50 @@ if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok {

[[play](https://go.dev/play/p/8wk5rH8UfrE)]

### Ok

`Ok` returns the value and ignores the error. Use with caution and only when you don't care about the error.

```go
doSomething := func() (int, error) {
return 1, nil
}

fmt.Println(lo.Ok(doSomething())) // 1

doSomethingWithError := func() (int, error) {
return 0, fmt.Errorf("my error")
}

fmt.Println(lo.Ok(doSomethingWithError())) // 0
```

[[play](https://go.dev/play/p/eqQ4T86iVjM)]

### OkOr

`OkOr` returns the value if err is nil, otherwise returns the fallback value.

```go
doSomething := func() (int, error) {
return 1, nil
}

v, err := doSomething()

fmt.Println(lo.OkOr(v, err, 2)) // 1

doSomethingWithError := func() (int, error) {
return 0, fmt.Errorf("my error")
}

v, err = doSomethingWithError()

fmt.Println(lo.OkOr(v, err, 2)) // 2
```

[[play](https://go.dev/play/p/gQ-ekv0mkx9)]

## 🛩 Benchmark

We executed a simple benchmark with a dead-simple `lo.Map` loop:
Expand Down
16 changes: 16 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,19 @@ func ErrorsAs[T error](err error) (T, bool) {
ok := errors.As(err, &t)
return t, ok
}

// Ok returns the value and ignores the error. Use with caution and only when you don't care about the error.
func Ok[T any](v T, err error) T {
if err != nil {
return v
}
return v
}

// OkOr returns the value if err is nil, otherwise returns the fallback value
func OkOr[T any](v T, err error, fallback T) T {
if err != nil {
return fallback
}
return v
}
36 changes: 36 additions & 0 deletions errors_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,39 @@ func ExampleErrorsAs() {

// Output: is type myError, err: my error
}

func ExampleOk() {
doSomething := func() (int, error) {
return 1, nil
}

// This will return 1
Ok(doSomething())

doSomethingWithError := func() (int, error) {
return 0, fmt.Errorf("my error")
}

// This will return 0
Ok(doSomethingWithError())
}

func ExampleOkOr() {
doSomething := func() (int, error) {
return 1, nil
}

v, err := doSomething()

// This will return 1
OkOr(v, err, 2)

doSomethingWithError := func() (int, error) {
return 0, fmt.Errorf("my error")
}

v, err = doSomethingWithError()

// This will return 2
OkOr(v, err, 2)
}
18 changes: 18 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,21 @@ func TestErrorsAs(t *testing.T) {
is.False(ok)
is.Nil(nil, err)
}

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

is.Equal(1, Ok(1, errors.New("something went wrong")))

is.Equal(2, Ok(2, nil))
}

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

is.Equal(2, OkOr(1, errors.New("something went wrong"), 2))

is.Equal(1, OkOr(1, nil, 2))
}
29 changes: 28 additions & 1 deletion type_manipulation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package lo

import "reflect"
import (
"encoding/json"
"reflect"
)

// IsNil checks if a value is nil or if it's a reference type with a nil underlying value.
func IsNil(x any) bool {
Expand Down Expand Up @@ -187,3 +190,27 @@ func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V {
}
return map[K]V{}
}

// Converts any type to a given type. If conversion fails, it returns the zero value of the given type. This is a type-safe version of type assertion.
//
// Cast enforces strict type assertion, for example trying to convert a number of 10 to float64 will return 0.0 instead of 10.0.
func Cast[T any](val any) T {
if val, ok := val.(T); ok {
return val
}
var zero T
return zero
}

// Converts any type to a given type based on their json representations. It partially fills the target in case they are not directly compatible. Any errors are ignored.
func CastJSON[T any](val any) T {
bytes, _ := json.Marshal(val)
return FromBytes[T](bytes)
}

// Converts a byte array to a given type. Ignores any errors.
func FromBytes[T any](bytes []byte) T {
var v T
_ = json.Unmarshal(bytes, &v)
return v
}
84 changes: 84 additions & 0 deletions type_manipulation_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lo

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -566,3 +567,86 @@ func TestCoalesceMapOrEmpty(t *testing.T) {
is.NotNil(result10)
is.Equal(map1, result10)
}

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

var n any = 10

is.Equal(10, Cast[int](n))

is.True(Cast[int](n) > 0)

is.NotZero(Cast[int](n))

is.Zero(Cast[int](nil))

is.Zero(Cast[string](n))

is.Zero(Cast[int32](n))

is.Zero(Cast[int64](n))

is.Zero(Cast[uint](n))

is.Zero(Cast[float32](n))

is.Zero(Cast[float64](n))
}

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

type TestObject1 struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
}

type TestObject2 struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
Qux string `json:"qux"`
}

testObject1 := TestObject1{
Foo: "bar",
Bar: 42,
}

testObject2 := TestObject2{
Foo: "bar",
Bar: 42,
Qux: "baz",
}

is.Equal(testObject1, CastJSON[TestObject1](testObject2))
}

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

is.Zero(FromBytes[string]([]byte{}))

bytes, _ := json.Marshal("foo")

is.Equal("foo", FromBytes[string](bytes))

bytes, _ = json.Marshal(42)

is.Equal(42, FromBytes[int](bytes))

type TestObject struct {
Foo string `json:"foo"`
Bar int `json:"bar"`
}

testObject := TestObject{
Foo: "bar",
Bar: 42,
}

is.Equal(testObject, FromBytes[TestObject]([]byte(`{"foo":"bar","bar":42}`)))
}