Skip to content

Commit

Permalink
Add more generic Functions Get, OrDefault, MustGet, MustOrDefault (#14)
Browse files Browse the repository at this point in the history
* add more generic Functions Get, OrDefault, MustGet, MustOrDefault

* update codecov settings
  • Loading branch information
moukoublen authored Nov 2, 2024
1 parent 6233b9c commit 82e6629
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 6 deletions.
9 changes: 8 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ codecov:
ignore:
- "internal/testingx" # test helpers

comment: false
# https://docs.codecov.com/docs/pull-request-comments
comment:
layout: "condensed_header, condensed_files, condensed_footer"
behavior: default
require_changes: false # Post comment even if there's no change in coverage
require_base: false # [true :: must have a base report to post]
require_head: true # [true :: must have a head report to post]
hide_project_coverage: false # [true :: only show coverage on the git diff]
76 changes: 71 additions & 5 deletions root.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package pick

import (
"errors"
"fmt"
"reflect"

"github.com/moukoublen/pick/cast"
"github.com/moukoublen/pick/cast/slices"
)

Expand Down Expand Up @@ -74,14 +79,41 @@ func FlatMap[Output any](p *Picker, selector string, transform func(*Picker) ([]
}

//nolint:ireturn
func Path[Output any](p *Picker, path []Key, castFn func(any) (Output, error)) (Output, error) {
func Path[Output any](p *Picker, path []Key) (Output, error) {
item, err := p.Path(path)
if err != nil {
var o Output
return o, err
}

return castFn(item)
var defaultValue Output
return castAs(p.Caster, item, defaultValue)
}

// OrDefault will return the default value if any error occurs. If the error is ErrFieldNotFound the error will not be returned.
func OrDefault[Output any](p *Picker, selector string, defaultValue Output) (Output, error) { //nolint:ireturn
item, err := p.Any(selector)
if err != nil {
if errors.Is(err, ErrFieldNotFound) {
return defaultValue, nil
}

return defaultValue, err
}

return castAs(p.Caster, item, defaultValue)
}

// Get resolves the cast type from the generic type.
func Get[Output any](p *Picker, selector string) (Output, error) { //nolint:ireturn
var defaultValue Output

item, err := p.Any(selector)
if err != nil {
return defaultValue, err
}

return castAs(p.Caster, item, defaultValue)
}

//
Expand Down Expand Up @@ -162,16 +194,36 @@ func MustFlatMap[Output any](a SelectorMustAPI, selector string, transform func(
return flatten[Output](item)
}

//nolint:ireturn
func MustPath[Output any](a SelectorMustAPI, path []Key, castFn func(any) (Output, error)) Output {
casted, err := Path(a.Picker, path, castFn)
// MustPath is the version of [Path] that uses SelectorMustAPI.
func MustPath[Output any](a SelectorMustAPI, path []Key) Output { //nolint:ireturn
casted, err := Path[Output](a.Picker, path)
if err != nil {
selector := DotNotation{}.Format(path...)
a.gather(selector, err)
}
return casted
}

// MustOrDefault will return the default value if any error occurs. Version of [OrDefault] that uses SelectorMustAPI.
func MustOrDefault[Output any](a SelectorMustAPI, selector string, defaultValue Output) Output { //nolint:ireturn
item, err := OrDefault(a.Picker, selector, defaultValue)
if err != nil {
a.gather(selector, err)
}

return item
}

// MustGet resolves the cast type from the generic type. Version of [Get] that uses SelectorMustAPI.
func MustGet[Output any](a SelectorMustAPI, selector string) Output { //nolint:ireturn
item, err := Get[Output](a.Picker, selector)
if err != nil {
a.gather(selector, err)
}

return item
}

func flatten[Output any](doubleSlice [][]Output) []Output {
// calculate total capacity
l := 0
Expand All @@ -197,3 +249,17 @@ func parseSelectorAndTraverse(p *Picker, selector string) (any, []Key, error) {
item, err := p.Path(path)
return item, path, err
}

func castAs[Output any](caster Caster, data any, defaultValue Output) (Output, error) { //nolint:ireturn
c, err := caster.ByType(data, reflect.TypeOf(defaultValue))
if err != nil {
return defaultValue, err
}

asOutput, is := c.(Output)
if !is {
return defaultValue, fmt.Errorf("casted value cannot be asserted to type: %w", cast.ErrInvalidType) // this is not possible
}

return asOutput, nil
}
218 changes: 218 additions & 0 deletions root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package pick

import (
"testing"

"github.com/moukoublen/pick/internal/testingx"
)

func TestOrDefault(t *testing.T) {
type stringAlias string

tests := map[string]struct {
data any
call func(*Picker) (any, error)

expectedValue any
expectedErr func(t *testing.T, err error)
}{
"exists - no cast": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := OrDefault(p, "one", "default")
return v, err
},
expectedValue: "value",
expectedErr: nil,
},
"not exists - return default": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := OrDefault(p, "two", "default")
return v, err
},
expectedValue: "default",
expectedErr: nil,
},
"exists - with cast": {
data: map[string]any{"one": 123},
call: func(p *Picker) (any, error) {
v, err := OrDefault(p, "one", "default")
return v, err
},
expectedValue: "123",
expectedErr: nil,
},
"exists - with cast to alias": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := OrDefault[stringAlias](p, "one", stringAlias("default"))
return v, err
},
expectedValue: stringAlias("value"),
expectedErr: nil,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
p := Wrap(tc.data)
got, gotErr := tc.call(p)
testingx.AssertError(t, tc.expectedErr, gotErr)
testingx.AssertEqual(t, got, tc.expectedValue)
})
}
}

func TestMustOrDefault(t *testing.T) {
type stringAlias string

tests := map[string]struct {
data any
call func(SelectorMustAPI) any

expectedValue any
}{
"exists - no cast": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustOrDefault(a, "one", "default")
},
expectedValue: "value",
},
"not exists - default": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustOrDefault(a, "two", "default")
},
expectedValue: "default",
},
"exists - cast": {
data: map[string]any{"one": 123},
call: func(a SelectorMustAPI) any {
return MustOrDefault(a, "one", "default")
},
expectedValue: "123",
},
"exists - with cast to alias": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustOrDefault[stringAlias](a, "one", stringAlias("default"))
},
expectedValue: stringAlias("value"),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
p := Wrap(tc.data)
got := tc.call(p.Must())
testingx.AssertEqual(t, got, tc.expectedValue)
})
}
}

func TestGet(t *testing.T) {
type stringAlias string

tests := map[string]struct {
data any
call func(*Picker) (any, error)

expectedValue any
expectedErr func(t *testing.T, err error)
}{
"exists - no cast": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := Get[string](p, "one")
return v, err
},
expectedValue: "value",
expectedErr: nil,
},
"not exists": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := Get[string](p, "two")
return v, err
},
expectedValue: "",
expectedErr: testingx.ExpectedErrorIs(ErrFieldNotFound),
},
"exists - with cast": {
data: map[string]any{"one": 123},
call: func(p *Picker) (any, error) {
v, err := Get[string](p, "one")
return v, err
},
expectedValue: "123",
expectedErr: nil,
},
"exists - with cast to alias": {
data: map[string]any{"one": "value"},
call: func(p *Picker) (any, error) {
v, err := Get[stringAlias](p, "one")
return v, err
},
expectedValue: stringAlias("value"),
expectedErr: nil,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
p := Wrap(tc.data)
got, gotErr := tc.call(p)
testingx.AssertError(t, tc.expectedErr, gotErr)
testingx.AssertEqual(t, got, tc.expectedValue)
})
}
}

func TestMustGet(t *testing.T) {
type stringAlias string

tests := map[string]struct {
data any
call func(SelectorMustAPI) any
expectedValue any
}{
"exists - no cast": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustGet[string](a, "one")
},
expectedValue: "value",
},
"not exists": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustGet[string](a, "two")
},
expectedValue: "",
},
"exists - cast": {
data: map[string]any{"one": 123},
call: func(a SelectorMustAPI) any {
return MustGet[string](a, "one")
},
expectedValue: "123",
},
"not exists with type alias": {
data: map[string]any{"one": "value"},
call: func(a SelectorMustAPI) any {
return MustGet[stringAlias](a, "one")
},
expectedValue: stringAlias("value"),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
p := Wrap(tc.data)
got := tc.call(p.Must())
testingx.AssertEqual(t, got, tc.expectedValue)
})
}
}

0 comments on commit 82e6629

Please sign in to comment.