Codebase list golang-github-imdario-mergo / 702a0a4
Imported Upstream version 0.1.2+git20150606.5.61a5285 Tim Potter 8 years ago
10 changed file(s) with 999 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 language: go
1 install: go get -t
0 Copyright (c) 2013 Dario Castañé. All rights reserved.
1 Copyright (c) 2012 The Go Authors. All rights reserved.
2
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions are
5 met:
6
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above
10 copyright notice, this list of conditions and the following disclaimer
11 in the documentation and/or other materials provided with the
12 distribution.
13 * Neither the name of Google Inc. nor the names of its
14 contributors may be used to endorse or promote products derived from
15 this software without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 # Mergo
1
2 A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
3
4 Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche.
5
6 ![Mergo dall'alto](http://www.comune.mergo.an.it/Siti/Mergo/Immagini/Foto/mergo_dall_alto.jpg)
7
8 ## Status
9
10 It is ready for production use. It works fine after extensive use in the wild.
11
12 [![Build Status][1]][2]
13 [![GoDoc](https://godoc.org/github.com/imdario/mergo?status.svg)](https://godoc.org/github.com/imdario/mergo)
14
15 [1]: https://travis-ci.org/imdario/mergo.png
16 [2]: https://travis-ci.org/imdario/mergo
17
18 ### Important note
19
20 Mergo is intended to assign **only** zero value fields on destination with source value. Since April 6th it works like this. Before it didn't work properly, causing some random overwrites. After some issues and PRs I found it didn't merge as I designed it. Thanks to [imdario/mergo#8](https://github.com/imdario/mergo/pull/8) overwriting functions were added and the wrong behavior was clearly detected.
21
22 If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0).
23
24 ### Mergo in the wild
25
26 - [imdario/zas](https://github.com/imdario/zas)
27 - [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
28 - [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
29 - [EagerIO/Stout](https://github.com/EagerIO/Stout)
30 - [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
31 - [russross/canvasassignments](https://github.com/russross/canvasassignments)
32 - [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
33 - [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
34 - [divshot/gitling](https://github.com/divshot/gitling)
35 - [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
36 - [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
37 - [elwinar/rambler](https://github.com/elwinar/rambler)
38 - [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
39 - [jfbus/impressionist](https://github.com/jfbus/impressionist)
40 - [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
41 - [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
42 - [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
43 - [thoas/picfit](https://github.com/thoas/picfit)
44 - [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
45 - [jnuthong/item_search](https://github.com/jnuthong/item_search)
46
47 ## Installation
48
49 go get github.com/imdario/mergo
50
51 // use in your .go code
52 import (
53 "github.com/imdario/mergo"
54 )
55
56 ## Usage
57
58 You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
59
60 if err := mergo.Merge(&dst, src); err != nil {
61 // ...
62 }
63
64 Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
65
66 if err := mergo.Map(&dst, srcMap); err != nil {
67 // ...
68 }
69
70 Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
71
72 More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
73
74 ### Nice example
75
76 ```go
77 package main
78
79 import (
80 "fmt"
81 "github.com/imdario/mergo"
82 )
83
84 type Foo struct {
85 A string
86 B int64
87 }
88
89 func main() {
90 src := Foo{
91 A: "one",
92 }
93
94 dest := Foo{
95 A: "two",
96 B: 2,
97 }
98
99 mergo.Merge(&dest, src)
100
101 fmt.Println(dest)
102 // Will print
103 // {two 2}
104 }
105 ```
106
107 Note: if test are failing due missing package, please execute:
108
109 go get gopkg.in/yaml.v1
110
111 ## Contact me
112
113 If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
114
115 ## About
116
117 Written by [Dario Castañé](http://dario.im).
118
119 ## License
120
121 [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
0 // Copyright 2013 Dario Castañé. All rights reserved.
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 /*
6 Package mergo merges same-type structs and maps by setting default values in zero-value fields.
7
8 Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
9
10 Usage
11
12 From my own work-in-progress project:
13
14 type networkConfig struct {
15 Protocol string
16 Address string
17 ServerType string `json: "server_type"`
18 Port uint16
19 }
20
21 type FssnConfig struct {
22 Network networkConfig
23 }
24
25 var fssnDefault = FssnConfig {
26 networkConfig {
27 "tcp",
28 "127.0.0.1",
29 "http",
30 31560,
31 },
32 }
33
34 // Inside a function [...]
35
36 if err := mergo.Merge(&config, fssnDefault); err != nil {
37 log.Fatal(err)
38 }
39
40 // More code [...]
41
42 */
43 package mergo
0 // Copyright 2014 Dario Castañé. All rights reserved.
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Based on src/pkg/reflect/deepequal.go from official
6 // golang's stdlib.
7
8 package mergo
9
10 import (
11 "fmt"
12 "reflect"
13 "unicode"
14 "unicode/utf8"
15 )
16
17 func changeInitialCase(s string, mapper func(rune) rune) string {
18 if s == "" {
19 return s
20 }
21 r, n := utf8.DecodeRuneInString(s)
22 return string(mapper(r)) + s[n:]
23 }
24
25 func isExported(field reflect.StructField) bool {
26 r, _ := utf8.DecodeRuneInString(field.Name)
27 return r >= 'A' && r <= 'Z'
28 }
29
30 // Traverses recursively both values, assigning src's fields values to dst.
31 // The map argument tracks comparisons that have already been seen, which allows
32 // short circuiting on recursive types.
33 func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
34 if dst.CanAddr() {
35 addr := dst.UnsafeAddr()
36 h := 17 * addr
37 seen := visited[h]
38 typ := dst.Type()
39 for p := seen; p != nil; p = p.next {
40 if p.ptr == addr && p.typ == typ {
41 return nil
42 }
43 }
44 // Remember, remember...
45 visited[h] = &visit{addr, typ, seen}
46 }
47 zeroValue := reflect.Value{}
48 switch dst.Kind() {
49 case reflect.Map:
50 dstMap := dst.Interface().(map[string]interface{})
51 for i, n := 0, src.NumField(); i < n; i++ {
52 srcType := src.Type()
53 field := srcType.Field(i)
54 if !isExported(field) {
55 continue
56 }
57 fieldName := field.Name
58 fieldName = changeInitialCase(fieldName, unicode.ToLower)
59 if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
60 dstMap[fieldName] = src.Field(i).Interface()
61 }
62 }
63 case reflect.Struct:
64 srcMap := src.Interface().(map[string]interface{})
65 for key := range srcMap {
66 srcValue := srcMap[key]
67 fieldName := changeInitialCase(key, unicode.ToUpper)
68 dstElement := dst.FieldByName(fieldName)
69 if dstElement == zeroValue {
70 // We discard it because the field doesn't exist.
71 continue
72 }
73 srcElement := reflect.ValueOf(srcValue)
74 dstKind := dstElement.Kind()
75 srcKind := srcElement.Kind()
76 if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
77 srcElement = srcElement.Elem()
78 srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
79 } else if dstKind == reflect.Ptr {
80 // Can this work? I guess it can't.
81 if srcKind != reflect.Ptr && srcElement.CanAddr() {
82 srcPtr := srcElement.Addr()
83 srcElement = reflect.ValueOf(srcPtr)
84 srcKind = reflect.Ptr
85 }
86 }
87 if !srcElement.IsValid() {
88 continue
89 }
90 if srcKind == dstKind {
91 if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
92 return
93 }
94 } else {
95 if srcKind == reflect.Map {
96 if err = deepMap(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
97 return
98 }
99 } else {
100 return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
101 }
102 }
103 }
104 }
105 return
106 }
107
108 // Map sets fields' values in dst from src.
109 // src can be a map with string keys or a struct. dst must be the opposite:
110 // if src is a map, dst must be a valid pointer to struct. If src is a struct,
111 // dst must be map[string]interface{}.
112 // It won't merge unexported (private) fields and will do recursively
113 // any exported field.
114 // If dst is a map, keys will be src fields' names in lower camel case.
115 // Missing key in src that doesn't match a field in dst will be skipped. This
116 // doesn't apply if dst is a map.
117 // This is separated method from Merge because it is cleaner and it keeps sane
118 // semantics: merging equal types, mapping different (restricted) types.
119 func Map(dst, src interface{}) error {
120 return _map(dst, src, false)
121 }
122
123 func MapWithOverwrite(dst, src interface{}) error {
124 return _map(dst, src, true)
125 }
126
127 func _map(dst, src interface{}, overwrite bool) error {
128 var (
129 vDst, vSrc reflect.Value
130 err error
131 )
132 if vDst, vSrc, err = resolveValues(dst, src); err != nil {
133 return err
134 }
135 // To be friction-less, we redirect equal-type arguments
136 // to deepMerge. Only because arguments can be anything.
137 if vSrc.Kind() == vDst.Kind() {
138 return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
139 }
140 switch vSrc.Kind() {
141 case reflect.Struct:
142 if vDst.Kind() != reflect.Map {
143 return ErrExpectedMapAsDestination
144 }
145 case reflect.Map:
146 if vDst.Kind() != reflect.Struct {
147 return ErrExpectedStructAsDestination
148 }
149 default:
150 return ErrNotSupported
151 }
152 return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
153 }
0 // Copyright 2013 Dario Castañé. All rights reserved.
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Based on src/pkg/reflect/deepequal.go from official
6 // golang's stdlib.
7
8 package mergo
9
10 import (
11 "reflect"
12 )
13
14 // Traverses recursively both values, assigning src's fields values to dst.
15 // The map argument tracks comparisons that have already been seen, which allows
16 // short circuiting on recursive types.
17 func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) {
18 if !src.IsValid() {
19 return
20 }
21 if dst.CanAddr() {
22 addr := dst.UnsafeAddr()
23 h := 17 * addr
24 seen := visited[h]
25 typ := dst.Type()
26 for p := seen; p != nil; p = p.next {
27 if p.ptr == addr && p.typ == typ {
28 return nil
29 }
30 }
31 // Remember, remember...
32 visited[h] = &visit{addr, typ, seen}
33 }
34 switch dst.Kind() {
35 case reflect.Struct:
36 for i, n := 0, dst.NumField(); i < n; i++ {
37 if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, overwrite); err != nil {
38 return
39 }
40 }
41 case reflect.Map:
42 for _, key := range src.MapKeys() {
43 srcElement := src.MapIndex(key)
44 if !srcElement.IsValid() {
45 continue
46 }
47 dstElement := dst.MapIndex(key)
48 switch reflect.TypeOf(srcElement.Interface()).Kind() {
49 case reflect.Struct:
50 fallthrough
51 case reflect.Map:
52 if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
53 return
54 }
55 }
56 if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
57 if dst.IsNil() {
58 dst.Set(reflect.MakeMap(dst.Type()))
59 }
60 dst.SetMapIndex(key, srcElement)
61 }
62 }
63 case reflect.Ptr:
64 fallthrough
65 case reflect.Interface:
66 if src.IsNil() {
67 break
68 } else if dst.IsNil() {
69 if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
70 dst.Set(src)
71 }
72 } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil {
73 return
74 }
75 default:
76 if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
77 dst.Set(src)
78 }
79 }
80 return
81 }
82
83 // Merge sets fields' values in dst from src if they have a zero
84 // value of their type.
85 // dst and src must be valid same-type structs and dst must be
86 // a pointer to struct.
87 // It won't merge unexported (private) fields and will do recursively
88 // any exported field.
89 func Merge(dst, src interface{}) error {
90 return merge(dst, src, false)
91 }
92
93 func MergeWithOverwrite(dst, src interface{}) error {
94 return merge(dst, src, true)
95 }
96
97 func merge(dst, src interface{}, overwrite bool) error {
98 var (
99 vDst, vSrc reflect.Value
100 err error
101 )
102 if vDst, vSrc, err = resolveValues(dst, src); err != nil {
103 return err
104 }
105 if vDst.Type() != vSrc.Type() {
106 return ErrDifferentArgumentsTypes
107 }
108 return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
109 }
0 // Copyright 2013 Dario Castañé. All rights reserved.
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Based on src/pkg/reflect/deepequal.go from official
6 // golang's stdlib.
7
8 package mergo
9
10 import (
11 "errors"
12 "reflect"
13 )
14
15 // Errors reported by Mergo when it finds invalid arguments.
16 var (
17 ErrNilArguments = errors.New("src and dst must not be nil")
18 ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
19 ErrNotSupported = errors.New("only structs and maps are supported")
20 ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
21 ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
22 )
23
24 // During deepMerge, must keep track of checks that are
25 // in progress. The comparison algorithm assumes that all
26 // checks in progress are true when it reencounters them.
27 // Visited are stored in a map indexed by 17 * a1 + a2;
28 type visit struct {
29 ptr uintptr
30 typ reflect.Type
31 next *visit
32 }
33
34 // From src/pkg/encoding/json.
35 func isEmptyValue(v reflect.Value) bool {
36 switch v.Kind() {
37 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
38 return v.Len() == 0
39 case reflect.Bool:
40 return !v.Bool()
41 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
42 return v.Int() == 0
43 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
44 return v.Uint() == 0
45 case reflect.Float32, reflect.Float64:
46 return v.Float() == 0
47 case reflect.Interface, reflect.Ptr:
48 return v.IsNil()
49 }
50 return false
51 }
52
53 func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
54 if dst == nil || src == nil {
55 err = ErrNilArguments
56 return
57 }
58 vDst = reflect.ValueOf(dst).Elem()
59 if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
60 err = ErrNotSupported
61 return
62 }
63 vSrc = reflect.ValueOf(src)
64 // We check if vSrc is a pointer to dereference it.
65 if vSrc.Kind() == reflect.Ptr {
66 vSrc = vSrc.Elem()
67 }
68 return
69 }
70
71 // Traverses recursively both values, assigning src's fields values to dst.
72 // The map argument tracks comparisons that have already been seen, which allows
73 // short circuiting on recursive types.
74 func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
75 if dst.CanAddr() {
76 addr := dst.UnsafeAddr()
77 h := 17 * addr
78 seen := visited[h]
79 typ := dst.Type()
80 for p := seen; p != nil; p = p.next {
81 if p.ptr == addr && p.typ == typ {
82 return nil
83 }
84 }
85 // Remember, remember...
86 visited[h] = &visit{addr, typ, seen}
87 }
88 return // TODO refactor
89 }
0 // Copyright 2013 Dario Castañé. All rights reserved.
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package mergo
6
7 import (
8 "io/ioutil"
9 "reflect"
10 "testing"
11
12 "gopkg.in/yaml.v1"
13 )
14
15 type simpleTest struct {
16 Value int
17 }
18
19 type complexTest struct {
20 St simpleTest
21 sz int
22 Id string
23 }
24
25 type moreComplextText struct {
26 Ct complexTest
27 St simpleTest
28 Nt simpleTest
29 }
30
31 type pointerTest struct {
32 C *simpleTest
33 }
34
35 type sliceTest struct {
36 S []int
37 }
38
39 func TestKb(t *testing.T) {
40 type testStruct struct {
41 Name string
42 KeyValue map[string]interface{}
43 }
44
45 akv := make(map[string]interface{})
46 akv["Key1"] = "not value 1"
47 akv["Key2"] = "value2"
48 a := testStruct{}
49 a.Name = "A"
50 a.KeyValue = akv
51
52 bkv := make(map[string]interface{})
53 bkv["Key1"] = "value1"
54 bkv["Key3"] = "value3"
55 b := testStruct{}
56 b.Name = "B"
57 b.KeyValue = bkv
58
59 ekv := make(map[string]interface{})
60 ekv["Key1"] = "value1"
61 ekv["Key2"] = "value2"
62 ekv["Key3"] = "value3"
63 expected := testStruct{}
64 expected.Name = "B"
65 expected.KeyValue = ekv
66
67 Merge(&b, a)
68
69 if !reflect.DeepEqual(b, expected) {
70 t.Errorf("Actual: %#v did not match \nExpected: %#v", b, expected)
71 }
72 }
73
74 func TestNil(t *testing.T) {
75 if err := Merge(nil, nil); err != ErrNilArguments {
76 t.Fail()
77 }
78 }
79
80 func TestDifferentTypes(t *testing.T) {
81 a := simpleTest{42}
82 b := 42
83 if err := Merge(&a, b); err != ErrDifferentArgumentsTypes {
84 t.Fail()
85 }
86 }
87
88 func TestSimpleStruct(t *testing.T) {
89 a := simpleTest{}
90 b := simpleTest{42}
91 if err := Merge(&a, b); err != nil {
92 t.FailNow()
93 }
94 if a.Value != 42 {
95 t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value)
96 }
97 if !reflect.DeepEqual(a, b) {
98 t.FailNow()
99 }
100 }
101
102 func TestComplexStruct(t *testing.T) {
103 a := complexTest{}
104 a.Id = "athing"
105 b := complexTest{simpleTest{42}, 1, "bthing"}
106 if err := Merge(&a, b); err != nil {
107 t.FailNow()
108 }
109 if a.St.Value != 42 {
110 t.Fatalf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value)
111 }
112 if a.sz == 1 {
113 t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz)
114 }
115 if a.Id == b.Id {
116 t.Fatalf("a's field Id merged unexpectedly: a.Id(%s) == b.Id(%s)", a.Id, b.Id)
117 }
118 }
119
120 func TestComplexStructWithOverwrite(t *testing.T) {
121 a := complexTest{simpleTest{1}, 1, "do-not-overwrite-with-empty-value"}
122 b := complexTest{simpleTest{42}, 2, ""}
123
124 expect := complexTest{simpleTest{42}, 1, "do-not-overwrite-with-empty-value"}
125 if err := MergeWithOverwrite(&a, b); err != nil {
126 t.FailNow()
127 }
128
129 if !reflect.DeepEqual(a, expect) {
130 t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect)
131 }
132 }
133
134 func TestPointerStruct(t *testing.T) {
135 s1 := simpleTest{}
136 s2 := simpleTest{19}
137 a := pointerTest{&s1}
138 b := pointerTest{&s2}
139 if err := Merge(&a, b); err != nil {
140 t.FailNow()
141 }
142 if a.C.Value != b.C.Value {
143 t.Fatalf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
144 }
145 }
146
147 type embeddingStruct struct {
148 embeddedStruct
149 }
150
151 type embeddedStruct struct {
152 A string
153 }
154
155 func TestEmbeddedStruct(t *testing.T) {
156 tests := []struct {
157 src embeddingStruct
158 dst embeddingStruct
159 expected embeddingStruct
160 }{
161 {
162 src: embeddingStruct{
163 embeddedStruct{"foo"},
164 },
165 dst: embeddingStruct{
166 embeddedStruct{""},
167 },
168 expected: embeddingStruct{
169 embeddedStruct{"foo"},
170 },
171 },
172 {
173 src: embeddingStruct{
174 embeddedStruct{""},
175 },
176 dst: embeddingStruct{
177 embeddedStruct{"bar"},
178 },
179 expected: embeddingStruct{
180 embeddedStruct{"bar"},
181 },
182 },
183 {
184 src: embeddingStruct{
185 embeddedStruct{"foo"},
186 },
187 dst: embeddingStruct{
188 embeddedStruct{"bar"},
189 },
190 expected: embeddingStruct{
191 embeddedStruct{"bar"},
192 },
193 },
194 }
195
196 for _, test := range tests {
197 err := Merge(&test.dst, test.src)
198 if err != nil {
199 t.Errorf("unexpected error: %v", err)
200 continue
201 }
202 if !reflect.DeepEqual(test.dst, test.expected) {
203 t.Errorf("unexpected output\nexpected:\n%+v\nsaw:\n%+v\n", test.expected, test.dst)
204 }
205 }
206 }
207
208 func TestPointerStructNil(t *testing.T) {
209 a := pointerTest{nil}
210 b := pointerTest{&simpleTest{19}}
211 if err := Merge(&a, b); err != nil {
212 t.FailNow()
213 }
214 if a.C.Value != b.C.Value {
215 t.Fatalf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
216 }
217 }
218
219 func TestSliceStruct(t *testing.T) {
220 a := sliceTest{}
221 b := sliceTest{[]int{1, 2, 3}}
222 if err := Merge(&a, b); err != nil {
223 t.FailNow()
224 }
225 if len(b.S) != 3 {
226 t.FailNow()
227 }
228 if len(a.S) != len(b.S) {
229 t.Fatalf("b not merged in a proper way %d != %d", len(a.S), len(b.S))
230 }
231
232 a = sliceTest{[]int{1}}
233 b = sliceTest{[]int{1, 2, 3}}
234 if err := Merge(&a, b); err != nil {
235 t.FailNow()
236 }
237 if len(a.S) != 1 {
238 t.FailNow()
239 }
240 if len(a.S) == len(b.S) {
241 t.Fatalf("b merged unexpectedly %d != %d", len(a.S), len(b.S))
242 }
243 }
244
245 func TestMapsWithOverwrite(t *testing.T) {
246 m := map[string]simpleTest{
247 "a": simpleTest{}, // overwritten by 16
248 "b": simpleTest{42}, // not overwritten by empty value
249 "c": simpleTest{13}, // overwritten by 12
250 "d": simpleTest{61},
251 }
252 n := map[string]simpleTest{
253 "a": simpleTest{16},
254 "b": simpleTest{},
255 "c": simpleTest{12},
256 "e": simpleTest{14},
257 }
258 expect := map[string]simpleTest{
259 "a": simpleTest{16},
260 "b": simpleTest{},
261 "c": simpleTest{12},
262 "d": simpleTest{61},
263 "e": simpleTest{14},
264 }
265
266 if err := MergeWithOverwrite(&m, n); err != nil {
267 t.Fatalf(err.Error())
268 }
269
270 if !reflect.DeepEqual(m, expect) {
271 t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
272 }
273 }
274
275 func TestMaps(t *testing.T) {
276 m := map[string]simpleTest{
277 "a": simpleTest{},
278 "b": simpleTest{42},
279 "c": simpleTest{13},
280 "d": simpleTest{61},
281 }
282 n := map[string]simpleTest{
283 "a": simpleTest{16},
284 "b": simpleTest{},
285 "c": simpleTest{12},
286 "e": simpleTest{14},
287 }
288 expect := map[string]simpleTest{
289 "a": simpleTest{0},
290 "b": simpleTest{42},
291 "c": simpleTest{13},
292 "d": simpleTest{61},
293 "e": simpleTest{14},
294 }
295
296 if err := Merge(&m, n); err != nil {
297 t.Fatalf(err.Error())
298 }
299
300 if !reflect.DeepEqual(m, expect) {
301 t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect)
302 }
303 if m["a"].Value != 0 {
304 t.Fatalf(`n merged in m because I solved non-addressable map values TODO: m["a"].Value(%d) != n["a"].Value(%d)`, m["a"].Value, n["a"].Value)
305 }
306 if m["b"].Value != 42 {
307 t.Fatalf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value)
308 }
309 if m["c"].Value != 13 {
310 t.Fatalf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value)
311 }
312 }
313
314 func TestYAMLMaps(t *testing.T) {
315 thing := loadYAML("testdata/thing.yml")
316 license := loadYAML("testdata/license.yml")
317 ft := thing["fields"].(map[interface{}]interface{})
318 fl := license["fields"].(map[interface{}]interface{})
319 expectedLength := len(ft) + len(fl)
320 if err := Merge(&license, thing); err != nil {
321 t.Fatal(err.Error())
322 }
323 currentLength := len(license["fields"].(map[interface{}]interface{}))
324 if currentLength != expectedLength {
325 t.Fatalf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength)
326 }
327 fields := license["fields"].(map[interface{}]interface{})
328 if _, ok := fields["id"]; !ok {
329 t.Fatalf(`thing not merged in license properly, license must have a new id field from thing`)
330 }
331 }
332
333 func TestTwoPointerValues(t *testing.T) {
334 a := &simpleTest{}
335 b := &simpleTest{42}
336 if err := Merge(a, b); err != nil {
337 t.Fatalf(`Boom. You crossed the streams: %s`, err)
338 }
339 }
340
341 func TestMap(t *testing.T) {
342 a := complexTest{}
343 a.Id = "athing"
344 c := moreComplextText{a, simpleTest{}, simpleTest{}}
345 b := map[string]interface{}{
346 "ct": map[string]interface{}{
347 "st": map[string]interface{}{
348 "value": 42,
349 },
350 "sz": 1,
351 "id": "bthing",
352 },
353 "st": &simpleTest{144}, // Mapping a reference
354 "zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist)
355 "nt": simpleTest{3},
356 }
357 if err := Map(&c, b); err != nil {
358 t.FailNow()
359 }
360 m := b["ct"].(map[string]interface{})
361 n := m["st"].(map[string]interface{})
362 o := b["st"].(*simpleTest)
363 p := b["nt"].(simpleTest)
364 if c.Ct.St.Value != 42 {
365 t.Fatalf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"])
366 }
367 if c.St.Value != 144 {
368 t.Fatalf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value)
369 }
370 if c.Nt.Value != 3 {
371 t.Fatalf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value)
372 }
373 if c.Ct.sz == 1 {
374 t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"])
375 }
376 if c.Ct.Id == m["id"] {
377 t.Fatalf("a's field Id merged unexpectedly: c.Ct.Id(%s) == b.Ct.Id(%s)", c.Ct.Id, m["id"])
378 }
379 }
380
381 func TestSimpleMap(t *testing.T) {
382 a := simpleTest{}
383 b := map[string]interface{}{
384 "value": 42,
385 }
386 if err := Map(&a, b); err != nil {
387 t.FailNow()
388 }
389 if a.Value != 42 {
390 t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"])
391 }
392 }
393
394 type pointerMapTest struct {
395 A int
396 hidden int
397 B *simpleTest
398 }
399
400 func TestBackAndForth(t *testing.T) {
401 pt := pointerMapTest{42, 1, &simpleTest{66}}
402 m := make(map[string]interface{})
403 if err := Map(&m, pt); err != nil {
404 t.FailNow()
405 }
406 var (
407 v interface{}
408 ok bool
409 )
410 if v, ok = m["a"]; v.(int) != pt.A || !ok {
411 t.Fatalf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A)
412 }
413 if v, ok = m["b"]; !ok {
414 t.Fatalf("pt not merged in properly: B is missing in m")
415 }
416 var st *simpleTest
417 if st = v.(*simpleTest); st.Value != 66 {
418 t.Fatalf("something went wrong while mapping pt on m, B wasn't copied")
419 }
420 bpt := pointerMapTest{}
421 if err := Map(&bpt, m); err != nil {
422 t.Fatal(err)
423 }
424 if bpt.A != pt.A {
425 t.Fatalf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A)
426 }
427 if bpt.hidden == pt.hidden {
428 t.Fatalf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden)
429 }
430 if bpt.B.Value != pt.B.Value {
431 t.Fatalf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value)
432 }
433 }
434
435 func loadYAML(path string) (m map[string]interface{}) {
436 m = make(map[string]interface{})
437 raw, _ := ioutil.ReadFile(path)
438 _ = yaml.Unmarshal(raw, &m)
439 return
440 }
0 import: ../../../../fossene/db/schema/thing.yml
1 fields:
2 site: string
0 fields:
1 id: int
2 name: string
3 parent: ref "datu:thing"
4 status: enum(draft, public, private)