diff --git a/go.work b/go.work new file mode 100644 index 0000000..437edda --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.20 + +use ( + . + ./v2 +) diff --git a/mergo_test.go b/mergo_test.go index 68c8aa5..be1dcbc 100644 --- a/mergo_test.go +++ b/mergo_test.go @@ -6,7 +6,7 @@ package mergo_test import ( - "io/ioutil" + "os" "reflect" "strings" "testing" @@ -846,7 +846,7 @@ func TestNestedPtrValueInMap(t *testing.T) { func loadYAML(path string) (m map[string]interface{}) { m = make(map[string]interface{}) - raw, _ := ioutil.ReadFile(path) + raw, _ := os.ReadFile(path) _ = yaml.Unmarshal(raw, &m) return } diff --git a/v2/addressables_test.go b/v2/addressables_test.go new file mode 100644 index 0000000..0f1f1bd --- /dev/null +++ b/v2/addressables_test.go @@ -0,0 +1,45 @@ +package mergo_test + +import ( + "testing" + + "dario.cat/mergo/v2" +) + +func litPtr[T any](v T) *T { + return &v +} + +func TestIntMerge(t *testing.T) { + t.Parallel() + + testCases := []struct { + dst *int + src int + want *int + }{ + {dst: litPtr(0), src: 1, want: litPtr(1)}, + {dst: litPtr(2), src: 1, want: litPtr(2)}, + {dst: nil, src: 1, want: nil}, + {dst: litPtr(3), src: 0, want: litPtr(3)}, + } + for _, tc := range testCases { + tc := tc + + t.Run("", func(t *testing.T) { + t.Parallel() + + mergo.Merge(tc.dst, tc.src) + if tc.dst == nil { + if tc.want != nil { + t.Errorf("expected %v, got %v", *tc.want, tc.dst) + } + + return + } + if *tc.dst != *tc.want { + t.Errorf("expected %v, got %v", *tc.want, *tc.dst) + } + }) + } +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..c64f359 --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,5 @@ +module dario.cat/mergo/v2 + +go 1.20 + +require github.com/google/go-cmp v0.5.9 diff --git a/v2/go.sum b/v2/go.sum new file mode 100644 index 0000000..62841cd --- /dev/null +++ b/v2/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/v2/merge.go b/v2/merge.go new file mode 100644 index 0000000..caf7584 --- /dev/null +++ b/v2/merge.go @@ -0,0 +1,28 @@ +package mergo + +import "reflect" + +// Merge (WIP) merges src into dst recursively setting src values on dst +// if src values are not zero values and dst values are zero values. +// Breaking change: src can't be a T pointer anymore. +// +//go:noinline +func Merge[T any](dst *T, src T) { + if dst == nil { + return + } + + elm := reflect.ValueOf(*dst) + + // If dst is an interface, we need to get the underlying value. + if elm.Kind() == reflect.Interface { + elm = elm.Elem() + } + + // If dst is a non-zero value, we don't need to do anything. + if !elm.IsZero() { + return + } + + *dst = src +} diff --git a/v2/unadressables_test.go b/v2/unadressables_test.go new file mode 100644 index 0000000..c5f09ab --- /dev/null +++ b/v2/unadressables_test.go @@ -0,0 +1,46 @@ +package mergo_test + +import ( + "testing" + + "dario.cat/mergo/v2" + "github.com/google/go-cmp/cmp" +) + +func ifc[T any](v T) interface{} { + return v +} + +func TestInterfaceMerge(t *testing.T) { + t.Parallel() + + testCases := []struct { + dst *interface{} + src interface{} + want interface{} + }{ + {dst: litPtr(ifc(0)), src: 1, want: 1}, + {dst: litPtr(ifc(2)), src: 1, want: 2}, + {dst: nil, src: 1, want: nil}, + {dst: litPtr(ifc(3)), src: 0, want: 3}, + } + for _, tc := range testCases { + tc := tc + + t.Run("", func(t *testing.T) { + t.Parallel() + + mergo.Merge(tc.dst, tc.src) + if tc.dst == nil { + if tc.want != nil { + t.Errorf("expected %v, got %v", tc.want, tc.dst) + } + + return + } + if !cmp.Equal(*tc.dst, tc.want) { + t.Errorf("expected %v, got %v", tc.want, *tc.dst) + } + }) + } +}