Update upstream source from tag 'upstream/0.3.3'
Update to upstream version '0.3.3'
with Debian dir 680815a8f88162489d1f28728ef17f173a02b074
Arnaud Rebillout
5 years ago
0 | .pc | |
0 | #### joe made this: http://goel.io/joe | |
1 | ||
2 | #### go #### | |
3 | # Binaries for programs and plugins | |
4 | *.exe | |
5 | *.dll | |
6 | *.so | |
7 | *.dylib | |
8 | ||
9 | # Test binary, build with `go test -c` | |
10 | *.test | |
11 | ||
12 | # Output of the go coverage tool, specifically when used with LiteIDE | |
13 | *.out | |
14 | ||
15 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 | |
16 | .glide/ | |
17 | ||
18 | #### vim #### | |
19 | # Swap | |
20 | [._]*.s[a-v][a-z] | |
21 | [._]*.sw[a-p] | |
22 | [._]s[a-v][a-z] | |
23 | [._]sw[a-p] | |
24 | ||
25 | # Session | |
26 | Session.vim | |
27 | ||
28 | # Temporary | |
29 | .netrwhist | |
30 | *~ | |
31 | # Auto-generated tag files | |
32 | tags |
0 | 0 | language: go |
1 | install: go get -t | |
1 | install: | |
2 | - go get -t | |
3 | - go get golang.org/x/tools/cmd/cover | |
4 | - go get github.com/mattn/goveralls | |
5 | script: | |
6 | - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN |
0 | # Contributor Covenant Code of Conduct | |
1 | ||
2 | ## Our Pledge | |
3 | ||
4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. | |
5 | ||
6 | ## Our Standards | |
7 | ||
8 | Examples of behavior that contributes to creating a positive environment include: | |
9 | ||
10 | * Using welcoming and inclusive language | |
11 | * Being respectful of differing viewpoints and experiences | |
12 | * Gracefully accepting constructive criticism | |
13 | * Focusing on what is best for the community | |
14 | * Showing empathy towards other community members | |
15 | ||
16 | Examples of unacceptable behavior by participants include: | |
17 | ||
18 | * The use of sexualized language or imagery and unwelcome sexual attention or advances | |
19 | * Trolling, insulting/derogatory comments, and personal or political attacks | |
20 | * Public or private harassment | |
21 | * Publishing others' private information, such as a physical or electronic address, without explicit permission | |
22 | * Other conduct which could reasonably be considered inappropriate in a professional setting | |
23 | ||
24 | ## Our Responsibilities | |
25 | ||
26 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. | |
27 | ||
28 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. | |
29 | ||
30 | ## Scope | |
31 | ||
32 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. | |
33 | ||
34 | ## Enforcement | |
35 | ||
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. | |
37 | ||
38 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. | |
39 | ||
40 | ## Attribution | |
41 | ||
42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] | |
43 | ||
44 | [homepage]: http://contributor-covenant.org | |
45 | [version]: http://contributor-covenant.org/version/1/4/ |
1 | 1 | |
2 | 2 | A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. |
3 | 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 |  | |
4 | Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche. | |
7 | 5 | |
8 | 6 | ## Status |
9 | 7 | |
10 | It is ready for production use. It works fine after extensive use in the wild. | |
11 | ||
12 | [![Build Status][1]][2] | |
8 | It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild). | |
9 | ||
13 | 10 | [![GoDoc][3]][4] |
14 | 11 | [![GoCard][5]][6] |
12 | [![Build Status][1]][2] | |
13 | [![Coverage Status][7]][8] | |
14 | [![Sourcegraph][9]][10] | |
15 | 15 | |
16 | 16 | [1]: https://travis-ci.org/imdario/mergo.png |
17 | 17 | [2]: https://travis-ci.org/imdario/mergo |
19 | 19 | [4]: https://godoc.org/github.com/imdario/mergo |
20 | 20 | [5]: https://goreportcard.com/badge/imdario/mergo |
21 | 21 | [6]: https://goreportcard.com/report/github.com/imdario/mergo |
22 | [7]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master | |
23 | [8]: https://coveralls.io/github/imdario/mergo?branch=master | |
24 | [9]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg | |
25 | [10]: https://sourcegraph.com/github.com/imdario/mergo?badge | |
26 | ||
27 | ### Latest release | |
28 | ||
29 | [Release v0.3.3](https://github.com/imdario/mergo/releases/tag/v0.3.3). | |
22 | 30 | |
23 | 31 | ### Important note |
24 | 32 | |
25 | 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. | |
33 | Please keep in mind that in [v0.3.2](//github.com/imdario/mergo/releases/tag/v0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code. | |
26 | 34 | |
27 | 35 | 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). |
28 | 36 | |
37 | ### Donations | |
38 | ||
39 | If Mergo is useful to you, consider buying me a coffee, a beer or making a monthly donation so I can keep building great free software. :heart_eyes: | |
40 | ||
41 | <a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> | |
42 | [](https://beerpay.io/imdario/mergo) | |
43 | [](https://beerpay.io/imdario/mergo) | |
44 | <a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a> | |
45 | ||
29 | 46 | ### Mergo in the wild |
30 | 47 | |
31 | - [docker/docker](https://github.com/docker/docker/) | |
32 | - [GoogleCloudPlatform/kubernetes](https://github.com/GoogleCloudPlatform/kubernetes) | |
48 | - [moby/moby](https://github.com/moby/moby) | |
49 | - [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) | |
50 | - [vmware/dispatch](https://github.com/vmware/dispatch) | |
51 | - [Shopify/themekit](https://github.com/Shopify/themekit) | |
33 | 52 | - [imdario/zas](https://github.com/imdario/zas) |
53 | - [matcornic/hermes](https://github.com/matcornic/hermes) | |
54 | - [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go) | |
55 | - [kataras/iris](https://github.com/kataras/iris) | |
56 | - [michaelsauter/crane](https://github.com/michaelsauter/crane) | |
57 | - [go-task/task](https://github.com/go-task/task) | |
58 | - [sensu/uchiwa](https://github.com/sensu/uchiwa) | |
59 | - [ory/hydra](https://github.com/ory/hydra) | |
60 | - [sisatech/vcli](https://github.com/sisatech/vcli) | |
61 | - [dairycart/dairycart](https://github.com/dairycart/dairycart) | |
62 | - [projectcalico/felix](https://github.com/projectcalico/felix) | |
63 | - [resin-os/balena](https://github.com/resin-os/balena) | |
64 | - [go-kivik/kivik](https://github.com/go-kivik/kivik) | |
65 | - [Telefonica/govice](https://github.com/Telefonica/govice) | |
66 | - [supergiant/supergiant](supergiant/supergiant) | |
67 | - [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce) | |
34 | 68 | - [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy) |
69 | - [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel) | |
35 | 70 | - [EagerIO/Stout](https://github.com/EagerIO/Stout) |
36 | 71 | - [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api) |
37 | 72 | - [russross/canvasassignments](https://github.com/russross/canvasassignments) |
49 | 84 | - [thoas/picfit](https://github.com/thoas/picfit) |
50 | 85 | - [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server) |
51 | 86 | - [jnuthong/item_search](https://github.com/jnuthong/item_search) |
87 | - [bukalapak/snowboard](https://github.com/bukalapak/snowboard) | |
52 | 88 | |
53 | 89 | ## Installation |
54 | 90 | |
61 | 97 | |
62 | 98 | ## Usage |
63 | 99 | |
64 | 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). | |
65 | ||
66 | if err := mergo.Merge(&dst, src); err != nil { | |
67 | // ... | |
68 | } | |
69 | ||
70 | 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. | |
71 | ||
72 | if err := mergo.Map(&dst, srcMap); err != nil { | |
73 | // ... | |
74 | } | |
75 | ||
76 | 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. | |
100 | 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. It won't merge empty structs value as [they are not considered zero values](https://golang.org/ref/spec#The_zero_value) either. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). | |
101 | ||
102 | ```go | |
103 | if err := mergo.Merge(&dst, src); err != nil { | |
104 | // ... | |
105 | } | |
106 | ``` | |
107 | ||
108 | Also, you can merge overwriting values using the transformer `WithOverride`. | |
109 | ||
110 | ```go | |
111 | if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil { | |
112 | // ... | |
113 | } | |
114 | ``` | |
115 | ||
116 | 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. | |
117 | ||
118 | ```go | |
119 | if err := mergo.Map(&dst, srcMap); err != nil { | |
120 | // ... | |
121 | } | |
122 | ``` | |
123 | ||
124 | 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. | |
77 | 125 | |
78 | 126 | More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo). |
79 | 127 | |
95 | 143 | func main() { |
96 | 144 | src := Foo{ |
97 | 145 | A: "one", |
146 | B: 2, | |
98 | 147 | } |
99 | ||
100 | 148 | dest := Foo{ |
101 | 149 | A: "two", |
102 | B: 2, | |
103 | 150 | } |
104 | ||
105 | 151 | mergo.Merge(&dest, src) |
106 | ||
107 | 152 | fmt.Println(dest) |
108 | 153 | // Will print |
109 | 154 | // {two 2} |
112 | 157 | |
113 | 158 | Note: if test are failing due missing package, please execute: |
114 | 159 | |
115 | go get gopkg.in/yaml.v1 | |
160 | go get gopkg.in/yaml.v2 | |
161 | ||
162 | ### Transformers | |
163 | ||
164 | Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`? | |
165 | ||
166 | ```go | |
167 | package main | |
168 | ||
169 | import ( | |
170 | "fmt" | |
171 | "github.com/imdario/mergo" | |
172 | "reflect" | |
173 | "time" | |
174 | ) | |
175 | ||
176 | type timeTransfomer struct { | |
177 | } | |
178 | ||
179 | func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { | |
180 | if typ == reflect.TypeOf(time.Time{}) { | |
181 | return func(dst, src reflect.Value) error { | |
182 | if dst.CanSet() { | |
183 | isZero := dst.MethodByName("IsZero") | |
184 | result := isZero.Call([]reflect.Value{}) | |
185 | if result[0].Bool() { | |
186 | dst.Set(src) | |
187 | } | |
188 | } | |
189 | return nil | |
190 | } | |
191 | } | |
192 | return nil | |
193 | } | |
194 | ||
195 | type Snapshot struct { | |
196 | Time time.Time | |
197 | // ... | |
198 | } | |
199 | ||
200 | func main() { | |
201 | src := Snapshot{time.Now()} | |
202 | dest := Snapshot{} | |
203 | mergo.Merge(&dest, src, mergo.WithTransformers(timeTransfomer{})) | |
204 | fmt.Println(dest) | |
205 | // Will print | |
206 | // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } | |
207 | } | |
208 | ``` | |
209 | ||
116 | 210 | |
117 | 211 | ## Contact me |
118 | 212 |
16 | 16 | func TestIssue17MergeWithOverwrite(t *testing.T) { |
17 | 17 | var something map[string]interface{} |
18 | 18 | if err := json.Unmarshal([]byte(request), &something); err != nil { |
19 | t.Errorf("Error while Unmarshalling maprequest %s", err) | |
19 | t.Errorf("Error while Unmarshalling maprequest: %s", err) | |
20 | 20 | } |
21 | 21 | if err := MergeWithOverwrite(&something, maprequest); err != nil { |
22 | t.Errorf("Error while merging %s", err) | |
22 | t.Errorf("Error while merging: %s", err) | |
23 | 23 | } |
24 | 24 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | type document struct { | |
8 | Created *time.Time | |
9 | } | |
10 | ||
11 | func TestIssue23MergeWithOverwrite(t *testing.T) { | |
12 | now := time.Now() | |
13 | dst := document{ | |
14 | &now, | |
15 | } | |
16 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) | |
17 | src := document{ | |
18 | &expected, | |
19 | } | |
20 | if err := MergeWithOverwrite(&dst, src); err != nil { | |
21 | t.Errorf("Error while merging %s", err) | |
22 | } | |
23 | if !dst.Created.Equal(*src.Created) { //--> https://golang.org/pkg/time/#pkg-overview | |
24 | t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) | |
25 | } | |
26 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | type Foo struct { | |
7 | Str string | |
8 | Bslice []byte | |
9 | } | |
10 | ||
11 | func TestIssue33Merge(t *testing.T) { | |
12 | dest := Foo{Str: "a"} | |
13 | toMerge := Foo{ | |
14 | Str: "b", | |
15 | Bslice: []byte{1, 2}, | |
16 | } | |
17 | if err := Merge(&dest, toMerge); err != nil { | |
18 | t.Errorf("Error while merging: %s", err) | |
19 | } | |
20 | // Merge doesn't overwrite an attribute if in destination it doesn't have a zero value. | |
21 | // In this case, Str isn't a zero value string. | |
22 | if dest.Str != "a" { | |
23 | t.Errorf("dest.Str should have not been override as it has a non-zero value: dest.Str(%v) != 'a'", dest.Str) | |
24 | } | |
25 | // If we want to override, we must use MergeWithOverwrite or Merge using WithOverride. | |
26 | if err := Merge(&dest, toMerge, WithOverride); err != nil { | |
27 | t.Errorf("Error while merging: %s", err) | |
28 | } | |
29 | if dest.Str != toMerge.Str { | |
30 | t.Errorf("dest.Str should have been override: dest.Str(%v) != toMerge.Str(%v)", dest.Str, toMerge.Str) | |
31 | } | |
32 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | type structWithoutTimePointer struct { | |
8 | Created time.Time | |
9 | } | |
10 | ||
11 | func TestIssue38Merge(t *testing.T) { | |
12 | dst := structWithoutTimePointer{ | |
13 | time.Now(), | |
14 | } | |
15 | ||
16 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) | |
17 | src := structWithoutTimePointer{ | |
18 | expected, | |
19 | } | |
20 | if err := Merge(&dst, src); err != nil { | |
21 | t.Errorf("Error while merging %s", err) | |
22 | } | |
23 | if dst.Created == src.Created { | |
24 | t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) | |
25 | } | |
26 | } | |
27 | ||
28 | func TestIssue38MergeEmptyStruct(t *testing.T) { | |
29 | dst := structWithoutTimePointer{} | |
30 | ||
31 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) | |
32 | src := structWithoutTimePointer{ | |
33 | expected, | |
34 | } | |
35 | if err := Merge(&dst, src); err != nil { | |
36 | t.Errorf("Error while merging %s", err) | |
37 | } | |
38 | if dst.Created == src.Created { | |
39 | t.Fatalf("Created merged unexpectedly: dst.Created(%v) == src.Created(%v)", dst.Created, src.Created) | |
40 | } | |
41 | } | |
42 | ||
43 | func TestIssue38MergeWithOverwrite(t *testing.T) { | |
44 | dst := structWithoutTimePointer{ | |
45 | time.Now(), | |
46 | } | |
47 | ||
48 | expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) | |
49 | src := structWithoutTimePointer{ | |
50 | expected, | |
51 | } | |
52 | if err := MergeWithOverwrite(&dst, src); err != nil { | |
53 | t.Errorf("Error while merging %s", err) | |
54 | } | |
55 | if dst.Created != src.Created { | |
56 | t.Fatalf("Created not merged in properly: dst.Created(%v) != src.Created(%v)", dst.Created, src.Created) | |
57 | } | |
58 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | "time" | |
5 | ) | |
6 | ||
7 | type testStruct struct { | |
8 | time.Duration | |
9 | } | |
10 | ||
11 | func TestIssue50Merge(t *testing.T) { | |
12 | to := testStruct{} | |
13 | from := testStruct{} | |
14 | if err := Merge(&to, from); err != nil { | |
15 | t.Fail() | |
16 | } | |
17 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | "time" | |
6 | ) | |
7 | ||
8 | type structWithTime struct { | |
9 | Birth time.Time | |
10 | } | |
11 | ||
12 | type timeTransfomer struct { | |
13 | overwrite bool | |
14 | } | |
15 | ||
16 | func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { | |
17 | if typ == reflect.TypeOf(time.Time{}) { | |
18 | return func(dst, src reflect.Value) error { | |
19 | if dst.CanSet() { | |
20 | if t.overwrite { | |
21 | isZero := src.MethodByName("IsZero") | |
22 | result := isZero.Call([]reflect.Value{}) | |
23 | if !result[0].Bool() { | |
24 | dst.Set(src) | |
25 | } | |
26 | } else { | |
27 | isZero := dst.MethodByName("IsZero") | |
28 | result := isZero.Call([]reflect.Value{}) | |
29 | if result[0].Bool() { | |
30 | dst.Set(src) | |
31 | } | |
32 | } | |
33 | } | |
34 | return nil | |
35 | } | |
36 | } | |
37 | return nil | |
38 | } | |
39 | ||
40 | func TestOverwriteZeroSrcTime(t *testing.T) { | |
41 | now := time.Now() | |
42 | dst := structWithTime{now} | |
43 | src := structWithTime{} | |
44 | if err := MergeWithOverwrite(&dst, src); err != nil { | |
45 | t.FailNow() | |
46 | } | |
47 | if !dst.Birth.IsZero() { | |
48 | t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) | |
49 | } | |
50 | } | |
51 | ||
52 | func TestOverwriteZeroSrcTimeWithTransformer(t *testing.T) { | |
53 | now := time.Now() | |
54 | dst := structWithTime{now} | |
55 | src := structWithTime{} | |
56 | if err := MergeWithOverwrite(&dst, src, WithTransformers(timeTransfomer{true})); err != nil { | |
57 | t.FailNow() | |
58 | } | |
59 | if dst.Birth.IsZero() { | |
60 | t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) | |
61 | } | |
62 | } | |
63 | ||
64 | func TestOverwriteZeroDstTime(t *testing.T) { | |
65 | now := time.Now() | |
66 | dst := structWithTime{} | |
67 | src := structWithTime{now} | |
68 | if err := MergeWithOverwrite(&dst, src); err != nil { | |
69 | t.FailNow() | |
70 | } | |
71 | if dst.Birth.IsZero() { | |
72 | t.Fatalf("dst should have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) | |
73 | } | |
74 | } | |
75 | ||
76 | func TestZeroDstTime(t *testing.T) { | |
77 | now := time.Now() | |
78 | dst := structWithTime{} | |
79 | src := structWithTime{now} | |
80 | if err := Merge(&dst, src); err != nil { | |
81 | t.FailNow() | |
82 | } | |
83 | if !dst.Birth.IsZero() { | |
84 | t.Fatalf("dst should not have been overwritten: dst.Birth(%v) != zero(%v)", dst.Birth, time.Time{}) | |
85 | } | |
86 | } | |
87 | ||
88 | func TestZeroDstTimeWithTransformer(t *testing.T) { | |
89 | now := time.Now() | |
90 | dst := structWithTime{} | |
91 | src := structWithTime{now} | |
92 | if err := Merge(&dst, src, WithTransformers(timeTransfomer{})); err != nil { | |
93 | t.FailNow() | |
94 | } | |
95 | if dst.Birth.IsZero() { | |
96 | t.Fatalf("dst should have been overwritten: dst.Birth(%v) != now(%v)", dst.Birth, now) | |
97 | } | |
98 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | func TestIssue61MergeNilMap(t *testing.T) { | |
8 | type T struct { | |
9 | I map[string][]string | |
10 | } | |
11 | t1 := T{} | |
12 | t2 := T{I: map[string][]string{"hi": {"there"}}} | |
13 | if err := Merge(&t1, t2); err != nil { | |
14 | t.Fail() | |
15 | } | |
16 | if !reflect.DeepEqual(t2, T{I: map[string][]string{"hi": {"there"}}}) { | |
17 | t.FailNow() | |
18 | } | |
19 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | type Student struct { | |
7 | Name string | |
8 | Books []string | |
9 | } | |
10 | ||
11 | var testData = []struct { | |
12 | S1 Student | |
13 | S2 Student | |
14 | ExpectedSlice []string | |
15 | }{ | |
16 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{"1"}}, []string{"a", "B"}}, | |
17 | {Student{"Jack", []string{"a", "B"}}, Student{"Tom", []string{}}, []string{"a", "B"}}, | |
18 | {Student{"Jack", []string{}}, Student{"Tom", []string{"1"}}, []string{"1"}}, | |
19 | {Student{"Jack", []string{}}, Student{"Tom", []string{}}, []string{}}, | |
20 | } | |
21 | ||
22 | func TestIssue64MergeSliceWithOverride(t *testing.T) { | |
23 | for _, data := range testData { | |
24 | err := Merge(&data.S2, data.S1, WithOverride) | |
25 | if err != nil { | |
26 | t.Errorf("Error while merging %s", err) | |
27 | } | |
28 | if len(data.S2.Books) != len(data.ExpectedSlice) { | |
29 | t.Fatalf("Got %d elements in slice, but expected %d", len(data.S2.Books), len(data.ExpectedSlice)) | |
30 | } | |
31 | for i, val := range data.S2.Books { | |
32 | if val != data.ExpectedSlice[i] { | |
33 | t.Fatalf("Expected %s, but got %s while merging slice with override", data.ExpectedSlice[i], val) | |
34 | } | |
35 | } | |
36 | } | |
37 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | type PrivateSliceTest66 struct { | |
7 | PublicStrings []string | |
8 | privateStrings []string | |
9 | } | |
10 | ||
11 | func TestPrivateSlice(t *testing.T) { | |
12 | p1 := PrivateSliceTest66{ | |
13 | PublicStrings: []string{"one", "two", "three"}, | |
14 | privateStrings: []string{"four", "five"}, | |
15 | } | |
16 | p2 := PrivateSliceTest66{ | |
17 | PublicStrings: []string{"six", "seven"}, | |
18 | } | |
19 | if err := Merge(&p1, p2); err != nil { | |
20 | t.Fatalf("Error during the merge: %v", err) | |
21 | } | |
22 | if len(p1.PublicStrings) != 5 { | |
23 | t.Error("5 elements should be in 'PublicStrings' field") | |
24 | } | |
25 | if len(p1.privateStrings) != 2 { | |
26 | t.Error("2 elements should be in 'privateStrings' field") | |
27 | } | |
28 | } |
30 | 30 | // Traverses recursively both values, assigning src's fields values to dst. |
31 | 31 | // The map argument tracks comparisons that have already been seen, which allows |
32 | 32 | // short circuiting on recursive types. |
33 | func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) { | |
33 | func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { | |
34 | overwrite := config.Overwrite | |
34 | 35 | if dst.CanAddr() { |
35 | 36 | addr := dst.UnsafeAddr() |
36 | 37 | h := 17 * addr |
60 | 61 | dstMap[fieldName] = src.Field(i).Interface() |
61 | 62 | } |
62 | 63 | } |
64 | case reflect.Ptr: | |
65 | if dst.IsNil() { | |
66 | v := reflect.New(dst.Type().Elem()) | |
67 | dst.Set(v) | |
68 | } | |
69 | dst = dst.Elem() | |
70 | fallthrough | |
63 | 71 | case reflect.Struct: |
64 | 72 | srcMap := src.Interface().(map[string]interface{}) |
65 | 73 | for key := range srcMap { |
84 | 92 | srcKind = reflect.Ptr |
85 | 93 | } |
86 | 94 | } |
95 | ||
87 | 96 | if !srcElement.IsValid() { |
88 | 97 | continue |
89 | 98 | } |
90 | 99 | if srcKind == dstKind { |
91 | if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { | |
100 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { | |
101 | return | |
102 | } | |
103 | } else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface { | |
104 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { | |
105 | return | |
106 | } | |
107 | } else if srcKind == reflect.Map { | |
108 | if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil { | |
92 | 109 | return |
93 | 110 | } |
94 | 111 | } 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 | } | |
112 | return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind) | |
102 | 113 | } |
103 | 114 | } |
104 | 115 | } |
116 | 127 | // doesn't apply if dst is a map. |
117 | 128 | // This is separated method from Merge because it is cleaner and it keeps sane |
118 | 129 | // semantics: merging equal types, mapping different (restricted) types. |
119 | func Map(dst, src interface{}) error { | |
120 | return _map(dst, src, false) | |
130 | func Map(dst, src interface{}, opts ...func(*Config)) error { | |
131 | return _map(dst, src, opts...) | |
121 | 132 | } |
122 | 133 | |
123 | // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by | |
134 | // MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by | |
124 | 135 | // non-empty src attribute values. |
125 | func MapWithOverwrite(dst, src interface{}) error { | |
126 | return _map(dst, src, true) | |
136 | // Deprecated: Use Map(…) with WithOverride | |
137 | func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { | |
138 | return _map(dst, src, append(opts, WithOverride)...) | |
127 | 139 | } |
128 | 140 | |
129 | func _map(dst, src interface{}, overwrite bool) error { | |
141 | func _map(dst, src interface{}, opts ...func(*Config)) error { | |
130 | 142 | var ( |
131 | 143 | vDst, vSrc reflect.Value |
132 | 144 | err error |
133 | 145 | ) |
146 | config := &Config{} | |
147 | ||
148 | for _, opt := range opts { | |
149 | opt(config) | |
150 | } | |
151 | ||
134 | 152 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { |
135 | 153 | return err |
136 | 154 | } |
137 | 155 | // To be friction-less, we redirect equal-type arguments |
138 | 156 | // to deepMerge. Only because arguments can be anything. |
139 | 157 | if vSrc.Kind() == vDst.Kind() { |
140 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) | |
158 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) | |
141 | 159 | } |
142 | 160 | switch vSrc.Kind() { |
143 | 161 | case reflect.Struct: |
151 | 169 | default: |
152 | 170 | return ErrNotSupported |
153 | 171 | } |
154 | return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) | |
172 | return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config) | |
155 | 173 | } |
11 | 11 | "reflect" |
12 | 12 | ) |
13 | 13 | |
14 | func hasExportedField(dst reflect.Value) (exported bool) { | |
15 | for i, n := 0, dst.NumField(); i < n; i++ { | |
16 | field := dst.Type().Field(i) | |
17 | if field.Anonymous && dst.Field(i).Kind() == reflect.Struct { | |
18 | exported = exported || hasExportedField(dst.Field(i)) | |
19 | } else { | |
20 | exported = exported || len(field.PkgPath) == 0 | |
21 | } | |
22 | } | |
23 | return | |
24 | } | |
25 | ||
26 | type Config struct { | |
27 | Overwrite bool | |
28 | Transformers Transformers | |
29 | } | |
30 | ||
31 | type Transformers interface { | |
32 | Transformer(reflect.Type) func(dst, src reflect.Value) error | |
33 | } | |
34 | ||
14 | 35 | // Traverses recursively both values, assigning src's fields values to dst. |
15 | 36 | // The map argument tracks comparisons that have already been seen, which allows |
16 | 37 | // short circuiting on recursive types. |
17 | func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, overwrite bool) (err error) { | |
38 | func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) { | |
39 | overwrite := config.Overwrite | |
40 | ||
18 | 41 | if !src.IsValid() { |
19 | 42 | return |
20 | 43 | } |
31 | 54 | // Remember, remember... |
32 | 55 | visited[h] = &visit{addr, typ, seen} |
33 | 56 | } |
57 | ||
58 | if config.Transformers != nil && !isEmptyValue(dst) { | |
59 | if fn := config.Transformers.Transformer(dst.Type()); fn != nil { | |
60 | err = fn(dst, src) | |
61 | return | |
62 | } | |
63 | } | |
64 | ||
34 | 65 | switch dst.Kind() { |
35 | 66 | 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 | |
67 | if hasExportedField(dst) { | |
68 | for i, n := 0, dst.NumField(); i < n; i++ { | |
69 | if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil { | |
70 | return | |
71 | } | |
72 | } | |
73 | } else { | |
74 | if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { | |
75 | dst.Set(src) | |
39 | 76 | } |
40 | 77 | } |
41 | 78 | case reflect.Map: |
79 | if dst.IsNil() && !src.IsNil() { | |
80 | dst.Set(reflect.MakeMap(dst.Type())) | |
81 | } | |
42 | 82 | for _, key := range src.MapKeys() { |
43 | 83 | srcElement := src.MapIndex(key) |
44 | 84 | if !srcElement.IsValid() { |
46 | 86 | } |
47 | 87 | dstElement := dst.MapIndex(key) |
48 | 88 | switch srcElement.Kind() { |
49 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice: | |
89 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice: | |
50 | 90 | if srcElement.IsNil() { |
51 | 91 | continue |
52 | 92 | } |
61 | 101 | case reflect.Ptr: |
62 | 102 | fallthrough |
63 | 103 | case reflect.Map: |
64 | if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil { | |
104 | if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil { | |
65 | 105 | return |
66 | 106 | } |
67 | } | |
68 | } | |
69 | if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) { | |
107 | case reflect.Slice: | |
108 | srcSlice := reflect.ValueOf(srcElement.Interface()) | |
109 | ||
110 | var dstSlice reflect.Value | |
111 | if !dstElement.IsValid() || dstElement.IsNil() { | |
112 | dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len()) | |
113 | } else { | |
114 | dstSlice = reflect.ValueOf(dstElement.Interface()) | |
115 | } | |
116 | ||
117 | dstSlice = reflect.AppendSlice(dstSlice, srcSlice) | |
118 | dst.SetMapIndex(key, dstSlice) | |
119 | } | |
120 | } | |
121 | if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map { | |
122 | continue | |
123 | } | |
124 | ||
125 | if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) { | |
70 | 126 | if dst.IsNil() { |
71 | 127 | dst.Set(reflect.MakeMap(dst.Type())) |
72 | 128 | } |
73 | 129 | dst.SetMapIndex(key, srcElement) |
74 | 130 | } |
131 | } | |
132 | case reflect.Slice: | |
133 | if !dst.CanSet() { | |
134 | break | |
135 | } | |
136 | if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) { | |
137 | dst.Set(src) | |
138 | } else { | |
139 | dst.Set(reflect.AppendSlice(dst, src)) | |
75 | 140 | } |
76 | 141 | case reflect.Ptr: |
77 | 142 | fallthrough |
78 | 143 | case reflect.Interface: |
79 | 144 | if src.IsNil() { |
80 | 145 | break |
81 | } else if dst.IsNil() { | |
146 | } | |
147 | if src.Kind() != reflect.Interface { | |
148 | if dst.IsNil() || overwrite { | |
149 | if dst.CanSet() && (overwrite || isEmptyValue(dst)) { | |
150 | dst.Set(src) | |
151 | } | |
152 | } else if src.Kind() == reflect.Ptr { | |
153 | if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { | |
154 | return | |
155 | } | |
156 | } else if dst.Elem().Type() == src.Type() { | |
157 | if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { | |
158 | return | |
159 | } | |
160 | } else { | |
161 | return ErrDifferentArgumentsTypes | |
162 | } | |
163 | break | |
164 | } | |
165 | if dst.IsNil() || overwrite { | |
82 | 166 | if dst.CanSet() && (overwrite || isEmptyValue(dst)) { |
83 | 167 | dst.Set(src) |
84 | 168 | } |
85 | } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, overwrite); err != nil { | |
169 | } else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { | |
86 | 170 | return |
87 | 171 | } |
88 | 172 | default: |
97 | 181 | // src attributes if they themselves are not empty. dst and src must be valid same-type structs |
98 | 182 | // and dst must be a pointer to struct. |
99 | 183 | // It won't merge unexported (private) fields and will do recursively any exported field. |
100 | func Merge(dst, src interface{}) error { | |
101 | return merge(dst, src, false) | |
184 | func Merge(dst, src interface{}, opts ...func(*Config)) error { | |
185 | return merge(dst, src, opts...) | |
102 | 186 | } |
103 | 187 | |
104 | 188 | // MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by |
105 | 189 | // non-empty src attribute values. |
106 | func MergeWithOverwrite(dst, src interface{}) error { | |
107 | return merge(dst, src, true) | |
108 | } | |
109 | ||
110 | func merge(dst, src interface{}, overwrite bool) error { | |
190 | // Deprecated: use Merge(…) with WithOverride | |
191 | func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error { | |
192 | return merge(dst, src, append(opts, WithOverride)...) | |
193 | } | |
194 | ||
195 | // WithTransformers adds transformers to merge, allowing to customize the merging of some types. | |
196 | func WithTransformers(transformers Transformers) func(*Config) { | |
197 | return func(config *Config) { | |
198 | config.Transformers = transformers | |
199 | } | |
200 | } | |
201 | ||
202 | // WithOverride will make merge override non-empty dst attributes with non-empty src attributes values. | |
203 | func WithOverride(config *Config) { | |
204 | config.Overwrite = true | |
205 | } | |
206 | ||
207 | func merge(dst, src interface{}, opts ...func(*Config)) error { | |
111 | 208 | var ( |
112 | 209 | vDst, vSrc reflect.Value |
113 | 210 | err error |
114 | 211 | ) |
212 | ||
213 | config := &Config{} | |
214 | ||
215 | for _, opt := range opts { | |
216 | opt(config) | |
217 | } | |
218 | ||
115 | 219 | if vDst, vSrc, err = resolveValues(dst, src); err != nil { |
116 | 220 | return err |
117 | 221 | } |
118 | 222 | if vDst.Type() != vSrc.Type() { |
119 | 223 | return ErrDifferentArgumentsTypes |
120 | 224 | } |
121 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite) | |
122 | } | |
225 | return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config) | |
226 | } |
0 | package mergo | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | type transformer struct { | |
8 | m map[reflect.Type]func(dst, src reflect.Value) error | |
9 | } | |
10 | ||
11 | func (s *transformer) Transformer(t reflect.Type) func(dst, src reflect.Value) error { | |
12 | if fn, ok := s.m[t]; ok { | |
13 | return fn | |
14 | } | |
15 | return nil | |
16 | } | |
17 | ||
18 | type foo struct { | |
19 | s string | |
20 | Bar *bar | |
21 | } | |
22 | ||
23 | type bar struct { | |
24 | i int | |
25 | s map[string]string | |
26 | } | |
27 | ||
28 | func TestMergeWithTransformerNilStruct(t *testing.T) { | |
29 | a := foo{s: "foo"} | |
30 | b := foo{Bar: &bar{i: 2, s: map[string]string{"foo": "bar"}}} | |
31 | if err := Merge(&a, &b, WithOverride, WithTransformers(&transformer{ | |
32 | m: map[reflect.Type]func(dst, src reflect.Value) error{ | |
33 | reflect.TypeOf(&bar{}): func(dst, src reflect.Value) error { | |
34 | // Do sthg with Elem | |
35 | t.Log(dst.Elem().FieldByName("i")) | |
36 | t.Log(src.Elem()) | |
37 | return nil | |
38 | }, | |
39 | }, | |
40 | })); err != nil { | |
41 | t.Fatal(err) | |
42 | } | |
43 | if a.s != "foo" { | |
44 | t.Fatalf("b not merged in properly: a.s.Value(%s) != expected(%s)", a.s, "foo") | |
45 | } | |
46 | if a.Bar == nil { | |
47 | t.Fatalf("b not merged in properly: a.Bar shouldn't be nil") | |
48 | } | |
49 | } |
31 | 31 | next *visit |
32 | 32 | } |
33 | 33 | |
34 | // From src/pkg/encoding/json. | |
34 | // From src/pkg/encoding/json/encode.go. | |
35 | 35 | func isEmptyValue(v reflect.Value) bool { |
36 | 36 | switch v.Kind() { |
37 | 37 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: |
44 | 44 | return v.Uint() == 0 |
45 | 45 | case reflect.Float32, reflect.Float64: |
46 | 46 | return v.Float() == 0 |
47 | case reflect.Interface, reflect.Ptr: | |
47 | case reflect.Interface, reflect.Ptr, reflect.Func: | |
48 | 48 | return v.IsNil() |
49 | case reflect.Invalid: | |
50 | return true | |
49 | 51 | } |
50 | 52 | return false |
51 | 53 | } |
5 | 5 | package mergo |
6 | 6 | |
7 | 7 | import ( |
8 | "gopkg.in/yaml.v2" | |
8 | 9 | "io/ioutil" |
9 | 10 | "reflect" |
10 | 11 | "testing" |
11 | 12 | "time" |
12 | ||
13 | "gopkg.in/yaml.v1" | |
14 | 13 | ) |
15 | 14 | |
16 | 15 | type simpleTest struct { |
21 | 20 | St simpleTest |
22 | 21 | sz int |
23 | 22 | ID string |
23 | } | |
24 | ||
25 | type mapTest struct { | |
26 | M map[int]int | |
27 | } | |
28 | ||
29 | type ifcTest struct { | |
30 | I interface{} | |
24 | 31 | } |
25 | 32 | |
26 | 33 | type moreComplextText struct { |
217 | 224 | } |
218 | 225 | } |
219 | 226 | |
220 | func TestSliceStruct(t *testing.T) { | |
221 | a := sliceTest{} | |
222 | b := sliceTest{[]int{1, 2, 3}} | |
227 | func testSlice(t *testing.T, a []int, b []int) { | |
228 | bc := b | |
229 | e := append(a, b...) | |
230 | ||
231 | sa := sliceTest{a} | |
232 | sb := sliceTest{b} | |
233 | if err := Merge(&sa, sb); err != nil { | |
234 | t.FailNow() | |
235 | } | |
236 | if !reflect.DeepEqual(sb.S, bc) { | |
237 | t.Fatalf("Source slice was modified %d != %d", sb.S, bc) | |
238 | } | |
239 | if !reflect.DeepEqual(sa.S, e) { | |
240 | t.Fatalf("b not merged in a proper way %d != %d", sa.S, e) | |
241 | } | |
242 | ||
243 | ma := map[string][]int{"S": a} | |
244 | mb := map[string][]int{"S": b} | |
245 | if err := Merge(&ma, mb); err != nil { | |
246 | t.FailNow() | |
247 | } | |
248 | if !reflect.DeepEqual(mb["S"], bc) { | |
249 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
250 | } | |
251 | if !reflect.DeepEqual(ma["S"], e) { | |
252 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
253 | } | |
254 | ||
255 | if a == nil { | |
256 | // test case with missing dst key | |
257 | ma := map[string][]int{} | |
258 | mb := map[string][]int{"S": b} | |
259 | if err := Merge(&ma, mb); err != nil { | |
260 | t.FailNow() | |
261 | } | |
262 | if !reflect.DeepEqual(mb["S"], bc) { | |
263 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
264 | } | |
265 | if !reflect.DeepEqual(ma["S"], e) { | |
266 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
267 | } | |
268 | } | |
269 | ||
270 | if b == nil { | |
271 | // test case with missing src key | |
272 | ma := map[string][]int{"S": a} | |
273 | mb := map[string][]int{} | |
274 | if err := Merge(&ma, mb); err != nil { | |
275 | t.FailNow() | |
276 | } | |
277 | if !reflect.DeepEqual(mb["S"], bc) { | |
278 | t.Fatalf("Source slice was modified %d != %d", mb["S"], bc) | |
279 | } | |
280 | if !reflect.DeepEqual(ma["S"], e) { | |
281 | t.Fatalf("b not merged in a proper way %d != %d", ma["S"], e) | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | func TestSlice(t *testing.T) { | |
287 | testSlice(t, nil, []int{1, 2, 3}) | |
288 | testSlice(t, []int{}, []int{1, 2, 3}) | |
289 | testSlice(t, []int{1}, []int{2, 3}) | |
290 | testSlice(t, []int{1}, []int{}) | |
291 | testSlice(t, []int{1}, nil) | |
292 | } | |
293 | ||
294 | func TestEmptyMaps(t *testing.T) { | |
295 | a := mapTest{} | |
296 | b := mapTest{ | |
297 | map[int]int{}, | |
298 | } | |
223 | 299 | if err := Merge(&a, b); err != nil { |
224 | t.FailNow() | |
225 | } | |
226 | if len(b.S) != 3 { | |
227 | t.FailNow() | |
228 | } | |
229 | if len(a.S) != len(b.S) { | |
230 | t.Fatalf("b not merged in a proper way %d != %d", len(a.S), len(b.S)) | |
231 | } | |
232 | ||
233 | a = sliceTest{[]int{1}} | |
234 | b = sliceTest{[]int{1, 2, 3}} | |
300 | t.Fail() | |
301 | } | |
302 | if !reflect.DeepEqual(a, b) { | |
303 | t.FailNow() | |
304 | } | |
305 | } | |
306 | ||
307 | func TestEmptyToEmptyMaps(t *testing.T) { | |
308 | a := mapTest{} | |
309 | b := mapTest{} | |
235 | 310 | if err := Merge(&a, b); err != nil { |
236 | t.FailNow() | |
237 | } | |
238 | if len(a.S) != 1 { | |
239 | t.FailNow() | |
240 | } | |
241 | if len(a.S) == len(b.S) { | |
242 | t.Fatalf("b merged unexpectedly %d != %d", len(a.S), len(b.S)) | |
311 | t.Fail() | |
312 | } | |
313 | if !reflect.DeepEqual(a, b) { | |
314 | t.FailNow() | |
315 | } | |
316 | } | |
317 | ||
318 | func TestEmptyToNotEmptyMaps(t *testing.T) { | |
319 | a := mapTest{map[int]int{ | |
320 | 1: 2, | |
321 | 3: 4, | |
322 | }} | |
323 | aa := mapTest{map[int]int{ | |
324 | 1: 2, | |
325 | 3: 4, | |
326 | }} | |
327 | b := mapTest{ | |
328 | map[int]int{}, | |
329 | } | |
330 | if err := Merge(&a, b); err != nil { | |
331 | t.Fail() | |
332 | } | |
333 | if !reflect.DeepEqual(a, aa) { | |
334 | t.FailNow() | |
243 | 335 | } |
244 | 336 | } |
245 | 337 | |
312 | 404 | } |
313 | 405 | } |
314 | 406 | |
407 | func TestMapsWithNilPointer(t *testing.T) { | |
408 | m := map[string]*simpleTest{ | |
409 | "a": nil, | |
410 | "b": nil, | |
411 | } | |
412 | n := map[string]*simpleTest{ | |
413 | "b": nil, | |
414 | "c": nil, | |
415 | } | |
416 | expect := map[string]*simpleTest{ | |
417 | "a": nil, | |
418 | "b": nil, | |
419 | "c": nil, | |
420 | } | |
421 | ||
422 | if err := Merge(&m, n, WithOverride); err != nil { | |
423 | t.Fatalf(err.Error()) | |
424 | } | |
425 | ||
426 | if !reflect.DeepEqual(m, expect) { | |
427 | t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", m, expect) | |
428 | } | |
429 | } | |
430 | ||
315 | 431 | func TestYAMLMaps(t *testing.T) { |
316 | 432 | thing := loadYAML("testdata/thing.yml") |
317 | 433 | license := loadYAML("testdata/license.yml") |
318 | 434 | ft := thing["fields"].(map[interface{}]interface{}) |
319 | 435 | fl := license["fields"].(map[interface{}]interface{}) |
320 | expectedLength := len(ft) + len(fl) | |
436 | // license has one extra field (site) and another already existing in thing (author) that Mergo won't override. | |
437 | expectedLength := len(ft) + len(fl) - 1 | |
321 | 438 | if err := Merge(&license, thing); err != nil { |
322 | 439 | t.Fatal(err.Error()) |
323 | 440 | } |
392 | 509 | } |
393 | 510 | } |
394 | 511 | |
512 | func TestIfcMap(t *testing.T) { | |
513 | a := ifcTest{} | |
514 | b := ifcTest{42} | |
515 | if err := Map(&a, b); err != nil { | |
516 | t.FailNow() | |
517 | } | |
518 | if a.I != 42 { | |
519 | t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) | |
520 | } | |
521 | if !reflect.DeepEqual(a, b) { | |
522 | t.FailNow() | |
523 | } | |
524 | } | |
525 | ||
526 | func TestIfcMapNoOverwrite(t *testing.T) { | |
527 | a := ifcTest{13} | |
528 | b := ifcTest{42} | |
529 | if err := Map(&a, b); err != nil { | |
530 | t.FailNow() | |
531 | } | |
532 | if a.I != 13 { | |
533 | t.Fatalf("a not left alone: a.I(%d) == b.I(%d)", a.I, b.I) | |
534 | } | |
535 | } | |
536 | ||
537 | func TestIfcMapWithOverwrite(t *testing.T) { | |
538 | a := ifcTest{13} | |
539 | b := ifcTest{42} | |
540 | if err := MapWithOverwrite(&a, b); err != nil { | |
541 | t.FailNow() | |
542 | } | |
543 | if a.I != 42 { | |
544 | t.Fatalf("b not merged in properly: a.I(%d) != b.I(%d)", a.I, b.I) | |
545 | } | |
546 | if !reflect.DeepEqual(a, b) { | |
547 | t.FailNow() | |
548 | } | |
549 | } | |
550 | ||
395 | 551 | type pointerMapTest struct { |
396 | 552 | A int |
397 | 553 | hidden int |
433 | 589 | } |
434 | 590 | } |
435 | 591 | |
592 | func TestEmbeddedPointerUnpacking(t *testing.T) { | |
593 | tests := []struct{ input pointerMapTest }{ | |
594 | {pointerMapTest{42, 1, nil}}, | |
595 | {pointerMapTest{42, 1, &simpleTest{66}}}, | |
596 | } | |
597 | newValue := 77 | |
598 | m := map[string]interface{}{ | |
599 | "b": map[string]interface{}{ | |
600 | "value": newValue, | |
601 | }, | |
602 | } | |
603 | for _, test := range tests { | |
604 | pt := test.input | |
605 | if err := MapWithOverwrite(&pt, m); err != nil { | |
606 | t.FailNow() | |
607 | } | |
608 | if pt.B.Value != newValue { | |
609 | t.Fatalf("pt not mapped properly: pt.A.Value(%d) != m[`b`][`value`](%d)", pt.B.Value, newValue) | |
610 | } | |
611 | ||
612 | } | |
613 | } | |
614 | ||
436 | 615 | type structWithTimePointer struct { |
437 | 616 | Birth *time.Time |
438 | 617 | } |
510 | 689 | |
511 | 690 | func TestUnexportedProperty(t *testing.T) { |
512 | 691 | a := structWithMap{map[string]structWithUnexportedProperty{ |
513 | "key": structWithUnexportedProperty{"hello"}, | |
692 | "key": {"hello"}, | |
514 | 693 | }} |
515 | 694 | b := structWithMap{map[string]structWithUnexportedProperty{ |
516 | "key": structWithUnexportedProperty{"hi"}, | |
695 | "key": {"hi"}, | |
517 | 696 | }} |
518 | 697 | defer func() { |
519 | 698 | if r := recover(); r != nil { |
522 | 701 | }() |
523 | 702 | Merge(&a, b) |
524 | 703 | } |
704 | ||
705 | type structWithBoolPointer struct { | |
706 | C *bool | |
707 | } | |
708 | ||
709 | func TestBooleanPointer(t *testing.T) { | |
710 | bt, bf := true, false | |
711 | src := structWithBoolPointer{ | |
712 | &bt, | |
713 | } | |
714 | dst := structWithBoolPointer{ | |
715 | &bf, | |
716 | } | |
717 | if err := Merge(&dst, src); err != nil { | |
718 | t.FailNow() | |
719 | } | |
720 | if dst.C == src.C { | |
721 | t.Fatalf("dst.C should be a different pointer than src.C") | |
722 | } | |
723 | if *dst.C != *src.C { | |
724 | t.Fatalf("dst.C should be true") | |
725 | } | |
726 | } |