From 6d4883632c78c27dddbf74af258b06b70069190d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Mon, 11 Sep 2023 20:57:04 +0200 Subject: [PATCH 1/4] chore: migrate from io/ioutil to os --- mergo_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 } From a6380c2e89e0771dfd953a08405d57693314bbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Mon, 11 Sep 2023 20:57:23 +0200 Subject: [PATCH 2/4] chore: migrate to Go 1.20 with workspace --- go.work | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 go.work 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 +) From 330fba1c6dc1d44816e037baffc2114c322cf489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Mon, 11 Sep 2023 20:57:47 +0200 Subject: [PATCH 3/4] feat: naive v2 merge for simple values --- v2/go.mod | 3 +++ v2/merge.go | 24 ++++++++++++++++++++++++ v2/merge_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 v2/go.mod create mode 100644 v2/merge.go create mode 100644 v2/merge_test.go diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..0bf4022 --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,3 @@ +module dario.cat/mergo/v2 + +go 1.20 diff --git a/v2/merge.go b/v2/merge.go new file mode 100644 index 0000000..4382246 --- /dev/null +++ b/v2/merge.go @@ -0,0 +1,24 @@ +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. +func Merge[T any](dst *T, src T) { + if dst == nil { + return + } + + vDst := reflect.ValueOf(dst) + + e := vDst.Elem() + if !e.CanSet() { + return + } + + if !e.IsZero() { + return + } + + *dst = src +} diff --git a/v2/merge_test.go b/v2/merge_test.go new file mode 100644 index 0000000..6e9b3d0 --- /dev/null +++ b/v2/merge_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 TestMerge(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) + } + }) + } +} From 794237f37a3cc8799e6f8d1f86685b5d080fd96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Casta=C3=B1=C3=A9?= Date: Tue, 12 Sep 2023 21:55:09 +0200 Subject: [PATCH 4/4] feat: support for interface{} values --- v2/{merge_test.go => addressables_test.go} | 2 +- v2/go.mod | 2 + v2/go.sum | 2 + v2/merge.go | 14 ++++--- v2/unadressables_test.go | 46 ++++++++++++++++++++++ 5 files changed, 60 insertions(+), 6 deletions(-) rename v2/{merge_test.go => addressables_test.go} (95%) create mode 100644 v2/go.sum create mode 100644 v2/unadressables_test.go diff --git a/v2/merge_test.go b/v2/addressables_test.go similarity index 95% rename from v2/merge_test.go rename to v2/addressables_test.go index 6e9b3d0..0f1f1bd 100644 --- a/v2/merge_test.go +++ b/v2/addressables_test.go @@ -10,7 +10,7 @@ func litPtr[T any](v T) *T { return &v } -func TestMerge(t *testing.T) { +func TestIntMerge(t *testing.T) { t.Parallel() testCases := []struct { diff --git a/v2/go.mod b/v2/go.mod index 0bf4022..c64f359 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -1,3 +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 index 4382246..caf7584 100644 --- a/v2/merge.go +++ b/v2/merge.go @@ -4,19 +4,23 @@ 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 } - vDst := reflect.ValueOf(dst) + elm := reflect.ValueOf(*dst) - e := vDst.Elem() - if !e.CanSet() { - return + // If dst is an interface, we need to get the underlying value. + if elm.Kind() == reflect.Interface { + elm = elm.Elem() } - if !e.IsZero() { + // If dst is a non-zero value, we don't need to do anything. + if !elm.IsZero() { return } 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) + } + }) + } +}