From ef0a383ec767ba64a50f64d53b384b8407e6ea6e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin <tomasz@maczukin.pl> Date: Tue, 26 Mar 2019 20:54:34 +0100 Subject: [PATCH] Update github.com/imdario/mergo version --- Gopkg.lock | 6 +- Gopkg.toml | 8 +- .../github.com/imdario/mergo/issue17_test.go | 4 +- .../github.com/imdario/mergo/issue23_test.go | 2 +- .../github.com/imdario/mergo/issue33_test.go | 33 +++ .../github.com/imdario/mergo/issue38_test.go | 59 +++++ .../github.com/imdario/mergo/issue50_test.go | 18 ++ .../github.com/imdario/mergo/issue52_test.go | 99 +++++++ .../github.com/imdario/mergo/issue61_test.go | 20 ++ .../github.com/imdario/mergo/issue64_test.go | 38 +++ .../github.com/imdario/mergo/issue66_test.go | 48 ++++ .../github.com/imdario/mergo/issue84_test.go | 82 ++++++ vendor/github.com/imdario/mergo/map.go | 53 ++-- vendor/github.com/imdario/mergo/merge.go | 164 ++++++++++-- .../imdario/mergo/merge_appendslice_test.go | 33 +++ vendor/github.com/imdario/mergo/merge_test.go | 50 ++++ vendor/github.com/imdario/mergo/mergo.go | 9 +- vendor/github.com/imdario/mergo/mergo_test.go | 241 ++++++++++++++++-- vendor/github.com/imdario/mergo/pr80_test.go | 18 ++ vendor/github.com/imdario/mergo/pr81_test.go | 42 +++ .../imdario/mergo/testdata/license.yml | 1 + 21 files changed, 969 insertions(+), 59 deletions(-) create mode 100644 vendor/github.com/imdario/mergo/issue33_test.go create mode 100644 vendor/github.com/imdario/mergo/issue38_test.go create mode 100644 vendor/github.com/imdario/mergo/issue50_test.go create mode 100644 vendor/github.com/imdario/mergo/issue52_test.go create mode 100644 vendor/github.com/imdario/mergo/issue61_test.go create mode 100644 vendor/github.com/imdario/mergo/issue64_test.go create mode 100644 vendor/github.com/imdario/mergo/issue66_test.go create mode 100644 vendor/github.com/imdario/mergo/issue84_test.go create mode 100644 vendor/github.com/imdario/mergo/merge_appendslice_test.go create mode 100644 vendor/github.com/imdario/mergo/merge_test.go create mode 100644 vendor/github.com/imdario/mergo/pr80_test.go create mode 100644 vendor/github.com/imdario/mergo/pr81_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 2afd27df9f5..dec803bf0e2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -411,11 +411,12 @@ revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" [[projects]] - digest = "1:ff6e83dd3b5a66d41c58a3d5e144bd06fcd4c376fd1a84379ad8c9a7dd308643" + digest = "1:158a976af1c463daebfa72efdfb66ccd78edfa475ed6d66937744bca05ee6c9c" name = "github.com/imdario/mergo" packages = ["."] pruneopts = "N" - revision = "50d4dbd4eb0e84778abe37cefef140271d96fade" + revision = "7c29201646fa3de8506f701213473dd407f19646" + version = "v0.3.7" [[projects]] digest = "1:485d36dae750af66fac78a968360bb1eb5a635d9d1e3a3b92116556cc2d45b6a" @@ -1066,6 +1067,7 @@ "github.com/gorilla/mux", "github.com/gorilla/websocket", "github.com/hashicorp/go-version", + "github.com/imdario/mergo", "github.com/jpillora/backoff", "github.com/kardianos/osext", "github.com/kr/pty", diff --git a/Gopkg.toml b/Gopkg.toml index d4939268916..0e8096b0fb0 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -166,6 +166,10 @@ ignored = ["test", "appengine"] name = "github.com/gofrs/flock" branch = "master" +[[constraint]] + name = "github.com/imdario/mergo" + version = "v0.3.7" + ## ## Refrain innovations ;) ## @@ -230,10 +234,6 @@ ignored = ["test", "appengine"] name = "github.com/gorilla/mux" revision = "599cba5e7b6137d46ddf58fb1765f5d928e69604" -[[override]] - name = "github.com/imdario/mergo" - revision = "50d4dbd4eb0e84778abe37cefef140271d96fade" - [[override]] name = "github.com/jonboulle/clockwork" revision = "e3653ace2d63753697e0e5b07b9393971c0bba9d" diff --git a/vendor/github.com/imdario/mergo/issue17_test.go b/vendor/github.com/imdario/mergo/issue17_test.go index 0ee96f37714..f9de805ab77 100644 --- a/vendor/github.com/imdario/mergo/issue17_test.go +++ b/vendor/github.com/imdario/mergo/issue17_test.go @@ -17,9 +17,9 @@ var ( func TestIssue17MergeWithOverwrite(t *testing.T) { var something map[string]interface{} if err := json.Unmarshal([]byte(request), &something); err != nil { - t.Errorf("Error while Unmarshalling maprequest %s", err) + t.Errorf("Error while Unmarshalling maprequest: %s", err) } if err := MergeWithOverwrite(&something, maprequest); err != nil { - t.Errorf("Error while merging %s", err) + t.Errorf("Error while merging: %s", err) } } diff --git a/vendor/github.com/imdario/mergo/issue23_test.go b/vendor/github.com/imdario/mergo/issue23_test.go index 9c32584131e..283f8c6a3f5 100644 --- a/vendor/github.com/imdario/mergo/issue23_test.go +++ b/vendor/github.com/imdario/mergo/issue23_test.go @@ -21,7 +21,7 @@ func TestIssue23MergeWithOverwrite(t *testing.T) { if err := MergeWithOverwrite(&dst, src); err != nil { t.Errorf("Error while merging %s", err) } - if dst.Created != src.Created { + if !dst.Created.Equal(*src.Created) { //--> https://golang.org/pkg/time/#pkg-overview t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) } } diff --git a/vendor/github.com/imdario/mergo/issue33_test.go b/vendor/github.com/imdario/mergo/issue33_test.go new file mode 100644 index 00000000000..ae55ae236f9 --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue33_test.go @@ -0,0 +1,33 @@ +package mergo + +import ( + "testing" +) + +type Foo struct { + Str string + Bslice []byte +} + +func TestIssue33Merge(t *testing.T) { + dest := Foo{Str: "a"} + toMerge := Foo{ + Str: "b", + Bslice: []byte{1, 2}, + } + if err := Merge(&dest, toMerge); err != nil { + t.Errorf("Error while merging: %s", err) + } + // Merge doesn't overwrite an attribute if in destination it doesn't have a zero value. + // In this case, Str isn't a zero value string. + if dest.Str != "a" { + t.Errorf("dest.Str should have not been override as it has a non-zero value: dest.Str(%v) != 'a'", dest.Str) + } + // If we want to override, we must use MergeWithOverwrite or Merge using WithOverride. + if err := Merge(&dest, toMerge, WithOverride); err != nil { + t.Errorf("Error while merging: %s", err) + } + if dest.Str != toMerge.Str { + t.Errorf("dest.Str should have been override: dest.Str(%v) != toMerge.Str(%v)", dest.Str, toMerge.Str) + } +} diff --git a/vendor/github.com/imdario/mergo/issue38_test.go b/vendor/github.com/imdario/mergo/issue38_test.go new file mode 100644 index 00000000000..286b68cb1ce --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue38_test.go @@ -0,0 +1,59 @@ +package mergo + +import ( + "testing" + "time" +) + +type structWithoutTimePointer struct { + Created time.Time +} + +func TestIssue38Merge(t *testing.T) { + dst := structWithoutTimePointer{ + time.Now(), + } + + expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + src := structWithoutTimePointer{ + expected, + } + if err := Merge(&dst, src); err != nil { + t.Errorf("Error while merging %s", err) + } + if dst.Created == src.Created { + t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) + } +} + +func TestIssue38MergeEmptyStruct(t *testing.T) { + dst := structWithoutTimePointer{} + + expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + src := structWithoutTimePointer{ + expected, + } + if err := Merge(&dst, src); err != nil { + t.Errorf("Error while merging %s", err) + } + if dst.Created == src.Created { + t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) + } +} + +func TestIssue38MergeWithOverwrite(t *testing.T) { + dst := structWithoutTimePointer{ + time.Now(), + } + + expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + src := structWithoutTimePointer{ + expected, + } + if err := MergeWithOverwrite(&dst, src); err != nil { + t.Errorf("Error while merging %s", err) + } + if dst.Created != src.Created { + t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) + } +} diff --git a/vendor/github.com/imdario/mergo/issue50_test.go b/vendor/github.com/imdario/mergo/issue50_test.go new file mode 100644 index 00000000000..89aa36345c8 --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue50_test.go @@ -0,0 +1,18 @@ +package mergo + +import ( + "testing" + "time" +) + +type testStruct struct { + time.Duration +} + +func TestIssue50Merge(t *testing.T) { + to := testStruct{} + from := testStruct{} + if err := Merge(&to, from); err != nil { + t.Fail() + } +} diff --git a/vendor/github.com/imdario/mergo/issue52_test.go b/vendor/github.com/imdario/mergo/issue52_test.go new file mode 100644 index 00000000000..62cd9fa7c0c --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue52_test.go @@ -0,0 +1,99 @@ +package mergo + +import ( + "reflect" + "testing" + "time" +) + +type structWithTime struct { + Birth time.Time +} + +type timeTransfomer struct { + overwrite bool +} + +func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { + if typ == reflect.TypeOf(time.Time{}) { + return func(dst, src reflect.Value) error { + if dst.CanSet() { + if t.overwrite { + isZero := src.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if !result[0].Bool() { + dst.Set(src) + } + } else { + isZero := dst.MethodByName("IsZero") + result := isZero.Call([]reflect.Value{}) + if result[0].Bool() { + dst.Set(src) + } + } + } + return nil + } + } + return nil +} + +func TestOverwriteZeroSrcTime(t *testing.T) { + now := time.Now() + dst := structWithTime{now} + src := structWithTime{} + if err := MergeWithOverwrite(&dst, src); err != nil { + t.FailNow() + } + if !dst.Birth.IsZero() { + t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + } +} + +func TestOverwriteZeroSrcTimeWithTransformer(t *testing.T) { + now := time.Now() + dst := structWithTime{now} + src := structWithTime{} + if err := MergeWithOverwrite(&dst, src, WithTransformers(timeTransfomer{true})); err != nil { + t.FailNow() + } + if dst.Birth.IsZero() { + t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + } +} + +func TestOverwriteZeroDstTime(t *testing.T) { + now := time.Now() + dst := structWithTime{} + src := structWithTime{now} + if err := MergeWithOverwrite(&dst, src); err != nil { + t.FailNow() + } + if dst.Birth.IsZero() { + t.Fatalf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) + } +} + +func TestZeroDstTime(t *testing.T) { + now := time.Now() + dst := structWithTime{} + src := structWithTime{now} + if err := Merge(&dst, src); err != nil { + t.FailNow() + } + if !dst.Birth.IsZero() { + t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) + } +} + +func TestZeroDstTimeWithTransformer(t *testing.T) { + now := time.Now() + dst := structWithTime{} + src := structWithTime{now} + if err := Merge(&dst, src, WithTransformers(timeTransfomer{})); err != nil { + t.FailNow() + } + if dst.Birth.IsZero() { + t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) + } +} diff --git a/vendor/github.com/imdario/mergo/issue61_test.go b/vendor/github.com/imdario/mergo/issue61_test.go new file mode 100644 index 00000000000..8efa5e45704 --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue61_test.go @@ -0,0 +1,20 @@ +package mergo + +import ( + "reflect" + "testing" +) + +func TestIssue61MergeNilMap(t *testing.T) { + type T struct { + I map[string][]string + } + t1 := T{} + t2 := T{I: map[string][]string{"hi": {"there"}}} + if err := Merge(&t1, t2); err != nil { + t.Fail() + } + if !reflect.DeepEqual(t2, T{I: map[string][]string{"hi": {"there"}}}) { + t.FailNow() + } +} diff --git a/vendor/github.com/imdario/mergo/issue64_test.go b/vendor/github.com/imdario/mergo/issue64_test.go new file mode 100644 index 00000000000..32382bef16c --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue64_test.go @@ -0,0 +1,38 @@ +package mergo + +import ( + "testing" +) + +type Student struct { + Name string + Books []string +} + +var testData = []struct { + S1 Student + S2 Student + ExpectedSlice []string +}{ + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}}, + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, +} + +func TestIssue64MergeSliceWithOverride(t *testing.T) { + for _, data := range testData { + err := Merge(&data.S2, data.S1, WithOverride) + if err != nil { + t.Errorf("Error while merging %s", err) + } + if len(data.S2.Books) != len(data.ExpectedSlice) { + t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) + } + for i, val := range data.S2.Books { + if val != data.ExpectedSlice[i] { + t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) + } + } + } +} diff --git a/vendor/github.com/imdario/mergo/issue66_test.go b/vendor/github.com/imdario/mergo/issue66_test.go new file mode 100644 index 00000000000..9e4bccedcb6 --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue66_test.go @@ -0,0 +1,48 @@ +package mergo + +import ( + "testing" +) + +type PrivateSliceTest66 struct { + PublicStrings []string + privateStrings []string +} + +func TestPrivateSlice(t *testing.T) { + p1 := PrivateSliceTest66{ + PublicStrings: []string{"one", "two", "three"}, + privateStrings: []string{"four", "five"}, + } + p2 := PrivateSliceTest66{ + PublicStrings: []string{"six", "seven"}, + } + if err := Merge(&p1, p2); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + if len(p1.PublicStrings) != 3 { + t.Error("5 elements should be in 'PublicStrings' field") + } + if len(p1.privateStrings) != 2 { + t.Error("2 elements should be in 'privateStrings' field") + } +} + +func TestPrivateSliceWithAppendSlice(t *testing.T) { + p1 := PrivateSliceTest66{ + PublicStrings: []string{"one", "two", "three"}, + privateStrings: []string{"four", "five"}, + } + p2 := PrivateSliceTest66{ + PublicStrings: []string{"six", "seven"}, + } + if err := Merge(&p1, p2, WithAppendSlice); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + if len(p1.PublicStrings) != 5 { + t.Error("5 elements should be in 'PublicStrings' field") + } + if len(p1.privateStrings) != 2 { + t.Error("2 elements should be in 'privateStrings' field") + } +} diff --git a/vendor/github.com/imdario/mergo/issue84_test.go b/vendor/github.com/imdario/mergo/issue84_test.go new file mode 100644 index 00000000000..aa605269b43 --- /dev/null +++ b/vendor/github.com/imdario/mergo/issue84_test.go @@ -0,0 +1,82 @@ +package mergo + +import ( + "testing" +) + +type DstStructIssue84 struct { + A int + B int + C int +} + +type DstNestedStructIssue84 struct { + A struct { + A int + B int + C int + } + B int + C int +} + +func TestIssue84MergeMapWithNilValueToStructWithOverride(t *testing.T) { + p1 := DstStructIssue84{ + A: 0, B: 1, C: 2, + } + p2 := map[string]interface{}{ + "A": 3, "B": 4, "C": 0, + } + if err := Map(&p1, p2, WithOverride); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + if p1.C != 0 { + t.Error("C field should become '0'") + } +} + +func TestIssue84MergeMapWithoutKeyExistsToStructWithOverride(t *testing.T) { + p1 := DstStructIssue84{ + A: 0, B: 1, C: 2, + } + p2 := map[string]interface{}{ + "A": 3, "B": 4, + } + if err := Map(&p1, p2, WithOverride); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + if p1.C != 2 { + t.Error("C field should be '2'") + } +} + +func TestIssue84MergeNestedMapWithNilValueToStructWithOverride(t *testing.T) { + p1 := DstNestedStructIssue84{ + A: struct { + A int + B int + C int + }{A: 1, B: 2, C: 0}, + B: 0, + C: 2, + } + p2 := map[string]interface{}{ + "A": map[string]interface{}{ + "A": 0, "B": 0, "C": 5, + }, "B": 4, "C": 0, + } + if err := Map(&p1, p2, WithOverride); err != nil { + t.Fatalf("Error during the merge: %v", err) + } + if p1.B != 4 { + t.Error("A.C field should become '4'") + } + + if p1.A.C != 5 { + t.Error("A.C field should become '5'") + } + + if p1.A.B != 0 || p1.A.A != 0 { + t.Error("A.A and A.B field should become '0'") + } +} diff --git a/vendor/github.com/imdario/mergo/map.go b/vendor/github.com/imdario/mergo/map.go index 8e8c4ba8ebe..3f5afa83a13 100644 --- a/vendor/github.com/imdario/mergo/map.go +++ b/vendor/github.com/imdario/mergo/map.go @@ -31,7 +31,8 @@ func isExported(field reflect.StructField) bool { // Traverses recursively both values, assigning src's fields values to dst. // The map argument tracks comparisons that have already been seen, which allows // short circuiting on recursive types. -func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) { +func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite if dst.CanAddr() { addr := dst.UnsafeAddr() h := 17 * addr @@ -61,9 +62,17 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, over dstMap[fieldName] = src.Field(i).Interface() } } + case reflect.Ptr: + if dst.IsNil() { + v := reflect.New(dst.Type().Elem()) + dst.Set(v) + } + dst = dst.Elem() + fallthrough case reflect.Struct: srcMap := src.Interface().(map[string]interface{}) for key := range srcMap { + config.overwriteWithEmptyValue = true srcValue := srcMap[key] fieldName := changeInitialCase(key, unicode.ToUpper) dstElement := dst.FieldByName(fieldName) @@ -85,21 +94,24 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, over srcKind = reflect.Ptr } } + if !srcElement.IsValid() { continue } if srcKind == dstKind { - if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { return } - } else { - if srcKind == reflect.Map { - if err = deepMap(dstElement, srcElement, visited, depth+1, overwrite); err != nil { - return - } - } else { - return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) + } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { + if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { + return + } + } else if srcKind == reflect.Map { + if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { + return } + } else { + return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) } } } @@ -117,28 +129,35 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, over // doesn't apply if dst is a map. // This is separated method from Merge because it is cleaner and it keeps sane // semantics: merging equal types, mapping different (restricted) types. -func Map(dst, src interface{}) error { - return _map(dst, src, false) +func Map(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, opts...) } -// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by +// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by // non-empty src attribute values. -func MapWithOverwrite(dst, src interface{}) error { - return _map(dst, src, true) +// Deprecated: Use Map(…) with WithOverride +func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return _map(dst, src, append(opts, WithOverride)...) } -func _map(dst, src interface{}, overwrite bool) error { +func _map(dst, src interface{}, opts ...func(*Config)) error { var ( vDst, vSrc reflect.Value err error ) + config := &Config{} + + for _, opt := range opts { + opt(config) + } + if vDst, vSrc, err = resolveValues(dst, src); err != nil { return err } // To be friction-less, we redirect equal-type arguments // to deepMerge. Only because arguments can be anything. if vSrc.Kind() == vDst.Kind() { - return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } switch vSrc.Kind() { case reflect.Struct: @@ -152,5 +171,5 @@ func _map(dst, src interface{}, overwrite bool) error { default: return ErrNotSupported } - return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) + return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) } diff --git a/vendor/github.com/imdario/mergo/merge.go b/vendor/github.com/imdario/mergo/merge.go index 513774f4c7d..f8de6c54305 100644 --- a/vendor/github.com/imdario/mergo/merge.go +++ b/vendor/github.com/imdario/mergo/merge.go @@ -9,13 +9,41 @@ package mergo import ( + "fmt" "reflect" ) +func hasExportedField(dst reflect.Value) (exported bool) { + for i, n := 0, dst.NumField(); i < n; i++ { + field := dst.Type().Field(i) + if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { + exported = exported || hasExportedField(dst.Field(i)) + } else { + exported = exported || len(field.PkgPath) == 0 + } + } + return +} + +type Config struct { + Overwrite bool + AppendSlice bool + Transformers Transformers + overwriteWithEmptyValue bool +} + +type Transformers interface { + Transformer(reflect.Type) func(dst, src reflect.Value) error +} + // Traverses recursively both values, assigning src's fields values to dst. // The map argument tracks comparisons that have already been seen, which allows // short circuiting on recursive types. -func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) { +func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { + overwrite := config.Overwrite + overwriteWithEmptySrc := config.overwriteWithEmptyValue + config.overwriteWithEmptyValue = false + if !src.IsValid() { return } @@ -32,14 +60,31 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov // Remember, remember... visited[h] = &visit{addr, typ, seen} } + + if config.Transformers != nil && !isEmptyValue(dst) { + if fn := config.Transformers.Transformer(dst.Type()); fn != nil { + err = fn(dst, src) + return + } + } + switch dst.Kind() { case reflect.Struct: - for i, n := 0, dst.NumField(); i < n; i++ { - if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, overwrite); err != nil { - return + if hasExportedField(dst) { + for i, n := 0, dst.NumField(); i < n; i++ { + if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { + return + } + } + } else { + if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) { + dst.Set(src) } } case reflect.Map: + if dst.IsNil() && !src.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } for _, key := range src.MapKeys() { srcElement := src.MapIndex(key) if !srcElement.IsValid() { @@ -47,7 +92,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov } dstElement := dst.MapIndex(key) switch srcElement.Kind() { - case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice: + case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: if srcElement.IsNil() { continue } @@ -62,32 +107,94 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov case reflect.Ptr: fallthrough case reflect.Map: - if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { + srcMapElm := srcElement + dstMapElm := dstElement + if srcMapElm.CanInterface() { + srcMapElm = reflect.ValueOf(srcMapElm.Interface()) + if dstMapElm.IsValid() { + dstMapElm = reflect.ValueOf(dstMapElm.Interface()) + } + } + if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil { return } + case reflect.Slice: + srcSlice := reflect.ValueOf(srcElement.Interface()) + + var dstSlice reflect.Value + if !dstElement.IsValid() || dstElement.IsNil() { + dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) + } else { + dstSlice = reflect.ValueOf(dstElement.Interface()) + } + + if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { + dstSlice = srcSlice + } else if config.AppendSlice { + if srcSlice.Type() != dstSlice.Type() { + return fmt.Errorf("cannot append two slice with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) + } + dstSlice = reflect.AppendSlice(dstSlice, srcSlice) + } + dst.SetMapIndex(key, dstSlice) } } - if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) { + if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) { + continue + } + + if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) { if dst.IsNil() { dst.Set(reflect.MakeMap(dst.Type())) } dst.SetMapIndex(key, srcElement) } } + case reflect.Slice: + if !dst.CanSet() { + break + } + if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice { + dst.Set(src) + } else if config.AppendSlice { + if src.Type() != dst.Type() { + return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type()) + } + dst.Set(reflect.AppendSlice(dst, src)) + } case reflect.Ptr: fallthrough case reflect.Interface: if src.IsNil() { break - } else if dst.IsNil() || overwrite { + } + if src.Kind() != reflect.Interface { + if dst.IsNil() || overwrite { + if dst.CanSet() && (overwrite || isEmptyValue(dst)) { + dst.Set(src) + } + } else if src.Kind() == reflect.Ptr { + if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { + return + } + } else if dst.Elem().Type() == src.Type() { + if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { + return + } + } else { + return ErrDifferentArgumentsTypes + } + break + } + if dst.IsNil() || overwrite { if dst.CanSet() && (overwrite || isEmptyValue(dst)) { dst.Set(src) } - } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil { + } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { return } default: - if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { + if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) { dst.Set(src) } } @@ -98,26 +205,51 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, ov // src attributes if they themselves are not empty. dst and src must be valid same-type structs // and dst must be a pointer to struct. // It won't merge unexported (private) fields and will do recursively any exported field. -func Merge(dst, src interface{}) error { - return merge(dst, src, false) +func Merge(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, opts...) } // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by // non-empty src attribute values. -func MergeWithOverwrite(dst, src interface{}) error { - return merge(dst, src, true) +// Deprecated: use Merge(…) with WithOverride +func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { + return merge(dst, src, append(opts, WithOverride)...) +} + +// WithTransformers adds transformers to merge, allowing to customize the merging of some types. +func WithTransformers(transformers Transformers) func(*Config) { + return func(config *Config) { + config.Transformers = transformers + } +} + +// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. +func WithOverride(config *Config) { + config.Overwrite = true } -func merge(dst, src interface{}, overwrite bool) error { +// WithAppendSlice will make merge append slices instead of overwriting it +func WithAppendSlice(config *Config) { + config.AppendSlice = true +} + +func merge(dst, src interface{}, opts ...func(*Config)) error { var ( vDst, vSrc reflect.Value err error ) + + config := &Config{} + + for _, opt := range opts { + opt(config) + } + if vDst, vSrc, err = resolveValues(dst, src); err != nil { return err } if vDst.Type() != vSrc.Type() { return ErrDifferentArgumentsTypes } - return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) + return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) } diff --git a/vendor/github.com/imdario/mergo/merge_appendslice_test.go b/vendor/github.com/imdario/mergo/merge_appendslice_test.go new file mode 100644 index 00000000000..a780f34a3c2 --- /dev/null +++ b/vendor/github.com/imdario/mergo/merge_appendslice_test.go @@ -0,0 +1,33 @@ +package mergo + +import ( + "testing" +) + +var testDataS = []struct { + S1 Student + S2 Student + ExpectedSlice []string +}{ + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"1", "a", "B"}}, + {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, + {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, +} + +func TestMergeSliceWithOverrideWithAppendSlice(t *testing.T) { + for _, data := range testDataS { + err := Merge(&data.S2, data.S1, WithOverride, WithAppendSlice) + if err != nil { + t.Errorf("Error while merging %s", err) + } + if len(data.S2.Books) != len(data.ExpectedSlice) { + t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) + } + for i, val := range data.S2.Books { + if val != data.ExpectedSlice[i] { + t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) + } + } + } +} diff --git a/vendor/github.com/imdario/mergo/merge_test.go b/vendor/github.com/imdario/mergo/merge_test.go new file mode 100644 index 00000000000..5bf808a786a --- /dev/null +++ b/vendor/github.com/imdario/mergo/merge_test.go @@ -0,0 +1,50 @@ +package mergo + +import ( + "reflect" + "testing" +) + +type transformer struct { + m map[reflect.Type]func(dst, src reflect.Value) error +} + +func (s *transformer) Transformer(t reflect.Type) func(dst, src reflect.Value) error { + if fn, ok := s.m[t]; ok { + return fn + } + return nil +} + +type foo struct { + s string + Bar *bar +} + +type bar struct { + i int + s map[string]string +} + +func TestMergeWithTransformerNilStruct(t *testing.T) { + a := foo{s: "foo"} + b := foo{Bar: &bar{i: 2, s: map[string]string{"foo": "bar"}}} + if err := Merge(&a, &b, WithOverride, WithTransformers(&transformer{ + m: map[reflect.Type]func(dst, src reflect.Value) error{ + reflect.TypeOf(&bar{}): func(dst, src reflect.Value) error { + // Do sthg with Elem + t.Log(dst.Elem().FieldByName("i")) + t.Log(src.Elem()) + return nil + }, + }, + })); err != nil { + t.Fatal(err) + } + if a.s != "foo" { + t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo") + } + if a.Bar == nil { + t.Fatalf("b not merged in properly: a.Bar shouldn't be nil") + } +} diff --git a/vendor/github.com/imdario/mergo/mergo.go b/vendor/github.com/imdario/mergo/mergo.go index f8a0991ec63..a82fea2fdcc 100644 --- a/vendor/github.com/imdario/mergo/mergo.go +++ b/vendor/github.com/imdario/mergo/mergo.go @@ -32,7 +32,7 @@ type visit struct { next *visit } -// From src/pkg/encoding/json. +// From src/pkg/encoding/json/encode.go. func isEmptyValue(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: @@ -46,7 +46,14 @@ func isEmptyValue(v reflect.Value) bool { case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Interface, reflect.Ptr: + if v.IsNil() { + return true + } + return isEmptyValue(v.Elem()) + case reflect.Func: return v.IsNil() + case reflect.Invalid: + return true } return false } diff --git a/vendor/github.com/imdario/mergo/mergo_test.go b/vendor/github.com/imdario/mergo/mergo_test.go index c43c61cdd60..1f2ab0bc0a3 100644 --- a/vendor/github.com/imdario/mergo/mergo_test.go +++ b/vendor/github.com/imdario/mergo/mergo_test.go @@ -10,7 +10,8 @@ import ( "reflect" "testing" "time" - "gopkg.in/yaml.v1" + + "gopkg.in/yaml.v2" ) type simpleTest struct { @@ -23,6 +24,14 @@ type complexTest struct { ID string } +type mapTest struct { + M map[int]int +} + +type ifcTest struct { + I interface{} +} + type moreComplextText struct { Ct complexTest St simpleTest @@ -217,29 +226,120 @@ func TestPointerStructNil(t *testing.T) { } } -func TestSliceStruct(t *testing.T) { - a := sliceTest{} - b := sliceTest{[]int{1, 2, 3}} - if err := Merge(&a, b); err != nil { +func testSlice(t *testing.T, a []int, b []int, e []int, opts ...func(*Config)) { + t.Helper() + bc := b + + sa := sliceTest{a} + sb := sliceTest{b} + if err := Merge(&sa, sb, opts...); err != nil { t.FailNow() } - if len(b.S) != 3 { + if !reflect.DeepEqual(sb.S, bc) { + t.Fatalf("Source slice was modified %d != %d", sb.S, bc) + } + if !reflect.DeepEqual(sa.S, e) { + t.Fatalf("b not merged in a proper way %d != %d", sa.S, e) + } + + ma := map[string][]int{"S": a} + mb := map[string][]int{"S": b} + if err := Merge(&ma, mb, opts...); err != nil { t.FailNow() } - if len(a.S) != len(b.S) { - t.Fatalf("b not merged in a proper way %d != %d", len(a.S), len(b.S)) + if !reflect.DeepEqual(mb["S"], bc) { + t.Fatalf("map value: Source slice was modified %d != %d", mb["S"], bc) + } + if !reflect.DeepEqual(ma["S"], e) { + t.Fatalf("map value: b not merged in a proper way %d != %d", ma["S"], e) } - a = sliceTest{[]int{1}} - b = sliceTest{[]int{1, 2, 3}} + if a == nil { + // test case with missing dst key + ma := map[string][]int{} + mb := map[string][]int{"S": b} + if err := Merge(&ma, mb); err != nil { + t.FailNow() + } + if !reflect.DeepEqual(mb["S"], bc) { + t.Fatalf("missing dst key: Source slice was modified %d != %d", mb["S"], bc) + } + if !reflect.DeepEqual(ma["S"], e) { + t.Fatalf("missing dst key: b not merged in a proper way %d != %d", ma["S"], e) + } + } + + if b == nil { + // test case with missing src key + ma := map[string][]int{"S": a} + mb := map[string][]int{} + if err := Merge(&ma, mb); err != nil { + t.FailNow() + } + if !reflect.DeepEqual(mb["S"], bc) { + t.Fatalf("missing src key: Source slice was modified %d != %d", mb["S"], bc) + } + if !reflect.DeepEqual(ma["S"], e) { + t.Fatalf("missing src key: b not merged in a proper way %d != %d", ma["S"], e) + } + } +} + +func TestSlice(t *testing.T) { + testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}) + testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}) + testSlice(t, []int{1}, []int{2, 3}, []int{1}) + testSlice(t, []int{1}, []int{}, []int{1}) + testSlice(t, []int{1}, nil, []int{1}) + testSlice(t, nil, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) + testSlice(t, []int{}, []int{1, 2, 3}, []int{1, 2, 3}, WithAppendSlice) + testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, WithAppendSlice) + testSlice(t, []int{1}, []int{2, 3}, []int{1, 2, 3}, WithAppendSlice, WithOverride) + testSlice(t, []int{1}, []int{}, []int{1}, WithAppendSlice) + testSlice(t, []int{1}, nil, []int{1}, WithAppendSlice) +} + +func TestEmptyMaps(t *testing.T) { + a := mapTest{} + b := mapTest{ + map[int]int{}, + } if err := Merge(&a, b); err != nil { + t.Fail() + } + if !reflect.DeepEqual(a, b) { t.FailNow() } - if len(a.S) != 1 { +} + +func TestEmptyToEmptyMaps(t *testing.T) { + a := mapTest{} + b := mapTest{} + if err := Merge(&a, b); err != nil { + t.Fail() + } + if !reflect.DeepEqual(a, b) { t.FailNow() } - if len(a.S) == len(b.S) { - t.Fatalf("b merged unexpectedly %d != %d", len(a.S), len(b.S)) +} + +func TestEmptyToNotEmptyMaps(t *testing.T) { + a := mapTest{map[int]int{ + 1: 2, + 3: 4, + }} + aa := mapTest{map[int]int{ + 1: 2, + 3: 4, + }} + b := mapTest{ + map[int]int{}, + } + if err := Merge(&a, b); err != nil { + t.Fail() + } + if !reflect.DeepEqual(a, aa) { + t.FailNow() } } @@ -312,12 +412,37 @@ func TestMaps(t *testing.T) { } } +func TestMapsWithNilPointer(t *testing.T) { + m := map[string]*simpleTest{ + "a": nil, + "b": nil, + } + n := map[string]*simpleTest{ + "b": nil, + "c": nil, + } + expect := map[string]*simpleTest{ + "a": nil, + "b": nil, + "c": nil, + } + + if err := Merge(&m, n, WithOverride); err != nil { + t.Fatalf(err.Error()) + } + + if !reflect.DeepEqual(m, expect) { + t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) + } +} + func TestYAMLMaps(t *testing.T) { thing := loadYAML("testdata/thing.yml") license := loadYAML("testdata/license.yml") ft := thing["fields"].(map[interface{}]interface{}) fl := license["fields"].(map[interface{}]interface{}) - expectedLength := len(ft) + len(fl) + // license has one extra field (site) and another already existing in thing (author) that Mergo won't override. + expectedLength := len(ft) + len(fl) - 1 if err := Merge(&license, thing); err != nil { t.Fatal(err.Error()) } @@ -392,6 +517,45 @@ func TestSimpleMap(t *testing.T) { } } +func TestIfcMap(t *testing.T) { + a := ifcTest{} + b := ifcTest{42} + if err := Map(&a, b); err != nil { + t.FailNow() + } + if a.I != 42 { + t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) + } + if !reflect.DeepEqual(a, b) { + t.FailNow() + } +} + +func TestIfcMapNoOverwrite(t *testing.T) { + a := ifcTest{13} + b := ifcTest{42} + if err := Map(&a, b); err != nil { + t.FailNow() + } + if a.I != 13 { + t.Fatalf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I) + } +} + +func TestIfcMapWithOverwrite(t *testing.T) { + a := ifcTest{13} + b := ifcTest{42} + if err := MapWithOverwrite(&a, b); err != nil { + t.FailNow() + } + if a.I != 42 { + t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) + } + if !reflect.DeepEqual(a, b) { + t.FailNow() + } +} + type pointerMapTest struct { A int hidden int @@ -433,6 +597,29 @@ func TestBackAndForth(t *testing.T) { } } +func TestEmbeddedPointerUnpacking(t *testing.T) { + tests := []struct{ input pointerMapTest }{ + {pointerMapTest{42, 1, nil}}, + {pointerMapTest{42, 1, &simpleTest{66}}}, + } + newValue := 77 + m := map[string]interface{}{ + "b": map[string]interface{}{ + "value": newValue, + }, + } + for _, test := range tests { + pt := test.input + if err := MapWithOverwrite(&pt, m); err != nil { + t.FailNow() + } + if pt.B.Value != newValue { + t.Fatalf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue) + } + + } +} + type structWithTimePointer struct { Birth *time.Time } @@ -510,10 +697,10 @@ type structWithUnexportedProperty struct { func TestUnexportedProperty(t *testing.T) { a := structWithMap{map[string]structWithUnexportedProperty{ - "key": structWithUnexportedProperty{"hello"}, + "key": {"hello"}, }} b := structWithMap{map[string]structWithUnexportedProperty{ - "key": structWithUnexportedProperty{"hi"}, + "key": {"hi"}, }} defer func() { if r := recover(); r != nil { @@ -545,3 +732,25 @@ func TestBooleanPointer(t *testing.T) { t.Fatalf("dst.C should be true") } } + +func TestMergeMapWithInnerSliceOfDifferentType(t *testing.T) { + src := map[string]interface{}{ + "foo": []string{"a", "b"}, + } + dst := map[string]interface{}{ + "foo": []int{1, 2}, + } + + if err := Merge(&src, &dst, WithOverride, WithAppendSlice); err == nil { + t.Fatal("expected an error, got nothing") + } +} + +func TestMergeSliceDifferentType(t *testing.T) { + src := []string{"a", "b"} + dst := []int{1, 2} + + if err := Merge(&src, &dst, WithOverride, WithAppendSlice); err == nil { + t.Fatal("expected an error, got nothing") + } +} diff --git a/vendor/github.com/imdario/mergo/pr80_test.go b/vendor/github.com/imdario/mergo/pr80_test.go new file mode 100644 index 00000000000..0b3220f3bcd --- /dev/null +++ b/vendor/github.com/imdario/mergo/pr80_test.go @@ -0,0 +1,18 @@ +package mergo + +import ( + "testing" +) + +type mapInterface map[string]interface{} + +func TestMergeMapsEmptyString(t *testing.T) { + a := mapInterface{"s": ""} + b := mapInterface{"s": "foo"} + if err := Merge(&a, b); err != nil { + t.Fatal(err) + } + if a["s"] != "foo" { + t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a["s"], "foo") + } +} diff --git a/vendor/github.com/imdario/mergo/pr81_test.go b/vendor/github.com/imdario/mergo/pr81_test.go new file mode 100644 index 00000000000..e90e923feb9 --- /dev/null +++ b/vendor/github.com/imdario/mergo/pr81_test.go @@ -0,0 +1,42 @@ +package mergo + +import ( + "testing" +) + +func TestMapInterfaceWithMultipleLayer(t *testing.T) { + m1 := map[string]interface{}{ + "k1": map[string]interface{}{ + "k1.1": "v1", + }, + } + + m2 := map[string]interface{}{ + "k1": map[string]interface{}{ + "k1.1": "v2", + "k1.2": "v3", + }, + } + + if err := Map(&m1, m2, WithOverride); err != nil { + t.Fatalf("Error merging: %v", err) + } + + // Check overwrite of sub map works + expected := "v2" + actual := m1["k1"].(map[string]interface{})["k1.1"].(string) + if actual != expected { + t.Fatalf("Expected %v but got %v", + expected, + actual) + } + + // Check new key is merged + expected = "v3" + actual = m1["k1"].(map[string]interface{})["k1.2"].(string) + if actual != expected { + t.Fatalf("Expected %v but got %v", + expected, + actual) + } +} diff --git a/vendor/github.com/imdario/mergo/testdata/license.yml b/vendor/github.com/imdario/mergo/testdata/license.yml index 62fdb61ec32..2f1ad0082bb 100644 --- a/vendor/github.com/imdario/mergo/testdata/license.yml +++ b/vendor/github.com/imdario/mergo/testdata/license.yml @@ -1,3 +1,4 @@ import: ../../../../fossene/db/schema/thing.yml fields: site: string + author: root -- GitLab