Vendor dependencies with dep
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
language: go
|
||||
install: go get -t
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
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.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Scope
|
||||
|
||||
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.
|
||||
|
||||
## Enforcement
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
# Mergo
|
||||
|
||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||
|
||||
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region Marche.
|
||||
|
||||

|
||||
|
||||
## Status
|
||||
|
||||
It is ready for production use. It works fine after extensive use in the wild.
|
||||
|
||||
[![Build Status][1]][2]
|
||||
[![GoDoc][3]][4]
|
||||
[![GoCard][5]][6]
|
||||
|
||||
[1]: https://travis-ci.org/imdario/mergo.png
|
||||
[2]: https://travis-ci.org/imdario/mergo
|
||||
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||
[4]: https://godoc.org/github.com/imdario/mergo
|
||||
[5]: https://goreportcard.com/badge/imdario/mergo
|
||||
[6]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||
|
||||
### Important note
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
### Mergo in the wild
|
||||
|
||||
- [docker/docker](https://github.com/docker/docker/)
|
||||
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||
- [imdario/zas](https://github.com/imdario/zas)
|
||||
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
|
||||
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
|
||||
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
|
||||
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
|
||||
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
|
||||
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
|
||||
- [divshot/gitling](https://github.com/divshot/gitling)
|
||||
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
|
||||
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
|
||||
- [elwinar/rambler](https://github.com/elwinar/rambler)
|
||||
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
|
||||
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
|
||||
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
|
||||
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
|
||||
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
|
||||
- [thoas/picfit](https://github.com/thoas/picfit)
|
||||
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||
- [Iris Web Framework](https://github.com/kataras/iris)
|
||||
|
||||
## Installation
|
||||
|
||||
go get github.com/imdario/mergo
|
||||
|
||||
// use in your .go code
|
||||
import (
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
## Usage
|
||||
|
||||
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).
|
||||
|
||||
```go
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Also, you can merge overwriting values using MergeWithOverwrite.
|
||||
|
||||
```go
|
||||
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```go
|
||||
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
|
||||
|
||||
### Nice example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Foo{
|
||||
A: "one",
|
||||
B: 2,
|
||||
}
|
||||
|
||||
dest := Foo{
|
||||
A: "two",
|
||||
}
|
||||
|
||||
mergo.Merge(&dest, src)
|
||||
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// {two 2}
|
||||
}
|
||||
```
|
||||
|
||||
Note: if test are failing due missing package, please execute:
|
||||
|
||||
go get gopkg.in/yaml.v1
|
||||
|
||||
## Contact me
|
||||
|
||||
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)
|
||||
|
||||
## About
|
||||
|
||||
Written by [Dario Castañé](http://dario.im).
|
||||
|
||||
## License
|
||||
|
||||
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package mergo merges same-type structs and maps by setting default values in zero-value fields.
|
||||
|
||||
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).
|
||||
|
||||
Usage
|
||||
|
||||
From my own work-in-progress project:
|
||||
|
||||
type networkConfig struct {
|
||||
Protocol string
|
||||
Address string
|
||||
ServerType string `json: "server_type"`
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type FssnConfig struct {
|
||||
Network networkConfig
|
||||
}
|
||||
|
||||
var fssnDefault = FssnConfig {
|
||||
networkConfig {
|
||||
"tcp",
|
||||
"127.0.0.1",
|
||||
"http",
|
||||
31560,
|
||||
},
|
||||
}
|
||||
|
||||
// Inside a function [...]
|
||||
|
||||
if err := mergo.Merge(&config, fssnDefault); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// More code [...]
|
||||
|
||||
*/
|
||||
package mergo
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
request = `{"timestamp":null, "name": "foo"}`
|
||||
maprequest = map[string]interface{}{
|
||||
"timestamp": nil,
|
||||
"name": "foo",
|
||||
"newStuff": "foo",
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
if err := MergeWithOverwrite(&something, maprequest); err != nil {
|
||||
t.Errorf("Error while merging %s", err)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type document struct {
|
||||
Created *time.Time
|
||||
}
|
||||
|
||||
func TestIssue23MergeWithOverwrite(t *testing.T) {
|
||||
now := time.Now()
|
||||
dst := document{
|
||||
&now,
|
||||
}
|
||||
expected := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
src := document{
|
||||
&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)
|
||||
}
|
||||
}
|
||||
+59
@@ -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)
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright 2014 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func changeInitialCase(s string, mapper func(rune) rune) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(mapper(r)) + s[n:]
|
||||
}
|
||||
|
||||
func isExported(field reflect.StructField) bool {
|
||||
r, _ := utf8.DecodeRuneInString(field.Name)
|
||||
return r >= 'A' && r <= 'Z'
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
zeroValue := reflect.Value{}
|
||||
switch dst.Kind() {
|
||||
case reflect.Map:
|
||||
dstMap := dst.Interface().(map[string]interface{})
|
||||
for i, n := 0, src.NumField(); i < n; i++ {
|
||||
srcType := src.Type()
|
||||
field := srcType.Field(i)
|
||||
if !isExported(field) {
|
||||
continue
|
||||
}
|
||||
fieldName := field.Name
|
||||
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) {
|
||||
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 {
|
||||
srcValue := srcMap[key]
|
||||
fieldName := changeInitialCase(key, unicode.ToUpper)
|
||||
dstElement := dst.FieldByName(fieldName)
|
||||
if dstElement == zeroValue {
|
||||
// We discard it because the field doesn't exist.
|
||||
continue
|
||||
}
|
||||
srcElement := reflect.ValueOf(srcValue)
|
||||
dstKind := dstElement.Kind()
|
||||
srcKind := srcElement.Kind()
|
||||
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
|
||||
srcElement = srcElement.Elem()
|
||||
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
|
||||
} else if dstKind == reflect.Ptr {
|
||||
// Can this work? I guess it can't.
|
||||
if srcKind != reflect.Ptr && srcElement.CanAddr() {
|
||||
srcPtr := srcElement.Addr()
|
||||
srcElement = reflect.ValueOf(srcPtr)
|
||||
srcKind = reflect.Ptr
|
||||
}
|
||||
}
|
||||
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
if srcKind == dstKind {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Map sets fields' values in dst from src.
|
||||
// src can be a map with string keys or a struct. dst must be the opposite:
|
||||
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
|
||||
// dst must be map[string]interface{}.
|
||||
// It won't merge unexported (private) fields and will do recursively
|
||||
// any exported field.
|
||||
// If dst is a map, keys will be src fields' names in lower camel case.
|
||||
// Missing key in src that doesn't match a field in dst will be skipped. This
|
||||
// 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)
|
||||
}
|
||||
|
||||
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by
|
||||
// non-empty src attribute values.
|
||||
func MapWithOverwrite(dst, src interface{}) error {
|
||||
return _map(dst, src, true)
|
||||
}
|
||||
|
||||
func _map(dst, src interface{}, overwrite bool) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
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)
|
||||
}
|
||||
switch vSrc.Kind() {
|
||||
case reflect.Struct:
|
||||
if vDst.Kind() != reflect.Map {
|
||||
return ErrExpectedMapAsDestination
|
||||
}
|
||||
case reflect.Map:
|
||||
if vDst.Kind() != reflect.Struct {
|
||||
return ErrExpectedStructAsDestination
|
||||
}
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, overwrite)
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"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 {
|
||||
exported = exported || hasExportedField(dst.Field(i))
|
||||
} else {
|
||||
exported = exported || len(field.PkgPath) == 0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
switch dst.Kind() {
|
||||
case reflect.Struct:
|
||||
if hasExportedField(dst) {
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if len(src.MapKeys()) == 0 && !src.IsNil() && len(dst.MapKeys()) == 0 {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
return
|
||||
}
|
||||
for _, key := range src.MapKeys() {
|
||||
srcElement := src.MapIndex(key)
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
dstElement := dst.MapIndex(key)
|
||||
switch srcElement.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
||||
if srcElement.IsNil() {
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
if !srcElement.CanInterface() {
|
||||
continue
|
||||
}
|
||||
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Map:
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, overwrite); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if dstElement.IsValid() && reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
dst.SetMapIndex(key, srcElement)
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
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, overwrite); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dst.Elem().Type() == src.Type() {
|
||||
if err = deepMerge(dst.Elem(), src, visited, depth+1, overwrite); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
break
|
||||
}
|
||||
if src.IsNil() {
|
||||
break
|
||||
} else 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 {
|
||||
return
|
||||
}
|
||||
default:
|
||||
if dst.CanSet() && !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func merge(dst, src interface{}, overwrite bool) error {
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
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)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Errors reported by Mergo when it finds invalid arguments.
|
||||
var (
|
||||
ErrNilArguments = errors.New("src and dst must not be nil")
|
||||
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
|
||||
ErrNotSupported = errors.New("only structs and maps are supported")
|
||||
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||
)
|
||||
|
||||
// During deepMerge, must keep track of checks that are
|
||||
// in progress. The comparison algorithm assumes that all
|
||||
// checks in progress are true when it reencounters them.
|
||||
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||
type visit struct {
|
||||
ptr uintptr
|
||||
typ reflect.Type
|
||||
next *visit
|
||||
}
|
||||
|
||||
// From src/pkg/encoding/json.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr, reflect.Func:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||
if dst == nil || src == nil {
|
||||
err = ErrNilArguments
|
||||
return
|
||||
}
|
||||
vDst = reflect.ValueOf(dst).Elem()
|
||||
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
|
||||
err = ErrNotSupported
|
||||
return
|
||||
}
|
||||
vSrc = reflect.ValueOf(src)
|
||||
// We check if vSrc is a pointer to dereference it.
|
||||
if vSrc.Kind() == reflect.Ptr {
|
||||
vSrc = vSrc.Elem()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
}
|
||||
return // TODO refactor
|
||||
}
|
||||
+662
@@ -0,0 +1,662 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type simpleTest struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
type complexTest struct {
|
||||
St simpleTest
|
||||
sz int
|
||||
ID string
|
||||
}
|
||||
|
||||
type mapTest struct {
|
||||
M map[int]int
|
||||
}
|
||||
|
||||
type ifcTest struct {
|
||||
I interface{}
|
||||
}
|
||||
|
||||
type moreComplextText struct {
|
||||
Ct complexTest
|
||||
St simpleTest
|
||||
Nt simpleTest
|
||||
}
|
||||
|
||||
type pointerTest struct {
|
||||
C *simpleTest
|
||||
}
|
||||
|
||||
type sliceTest struct {
|
||||
S []int
|
||||
}
|
||||
|
||||
func TestKb(t *testing.T) {
|
||||
type testStruct struct {
|
||||
Name string
|
||||
KeyValue map[string]interface{}
|
||||
}
|
||||
|
||||
akv := make(map[string]interface{})
|
||||
akv["Key1"] = "not value 1"
|
||||
akv["Key2"] = "value2"
|
||||
a := testStruct{}
|
||||
a.Name = "A"
|
||||
a.KeyValue = akv
|
||||
|
||||
bkv := make(map[string]interface{})
|
||||
bkv["Key1"] = "value1"
|
||||
bkv["Key3"] = "value3"
|
||||
b := testStruct{}
|
||||
b.Name = "B"
|
||||
b.KeyValue = bkv
|
||||
|
||||
ekv := make(map[string]interface{})
|
||||
ekv["Key1"] = "value1"
|
||||
ekv["Key2"] = "value2"
|
||||
ekv["Key3"] = "value3"
|
||||
expected := testStruct{}
|
||||
expected.Name = "B"
|
||||
expected.KeyValue = ekv
|
||||
|
||||
Merge(&b, a)
|
||||
|
||||
if !reflect.DeepEqual(b, expected) {
|
||||
t.Errorf("Actual: %#v did not match \nExpected: %#v", b, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNil(t *testing.T) {
|
||||
if err := Merge(nil, nil); err != ErrNilArguments {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferentTypes(t *testing.T) {
|
||||
a := simpleTest{42}
|
||||
b := 42
|
||||
if err := Merge(&a, b); err != ErrDifferentArgumentsTypes {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleStruct(t *testing.T) {
|
||||
a := simpleTest{}
|
||||
b := simpleTest{42}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if a.Value != 42 {
|
||||
t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%d)", a.Value, b.Value)
|
||||
}
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplexStruct(t *testing.T) {
|
||||
a := complexTest{}
|
||||
a.ID = "athing"
|
||||
b := complexTest{simpleTest{42}, 1, "bthing"}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if a.St.Value != 42 {
|
||||
t.Fatalf("b not merged in properly: a.St.Value(%d) != b.St.Value(%d)", a.St.Value, b.St.Value)
|
||||
}
|
||||
if a.sz == 1 {
|
||||
t.Fatalf("a's private field sz not preserved from merge: a.sz(%d) == b.sz(%d)", a.sz, b.sz)
|
||||
}
|
||||
if a.ID == b.ID {
|
||||
t.Fatalf("a's field ID merged unexpectedly: a.ID(%s) == b.ID(%s)", a.ID, b.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplexStructWithOverwrite(t *testing.T) {
|
||||
a := complexTest{simpleTest{1}, 1, "do-not-overwrite-with-empty-value"}
|
||||
b := complexTest{simpleTest{42}, 2, ""}
|
||||
|
||||
expect := complexTest{simpleTest{42}, 1, "do-not-overwrite-with-empty-value"}
|
||||
if err := MergeWithOverwrite(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(a, expect) {
|
||||
t.Fatalf("Test failed:\ngot :\n%#v\n\nwant :\n%#v\n\n", a, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointerStruct(t *testing.T) {
|
||||
s1 := simpleTest{}
|
||||
s2 := simpleTest{19}
|
||||
a := pointerTest{&s1}
|
||||
b := pointerTest{&s2}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if a.C.Value != b.C.Value {
|
||||
t.Fatalf("b not merged in properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
|
||||
}
|
||||
}
|
||||
|
||||
type embeddingStruct struct {
|
||||
embeddedStruct
|
||||
}
|
||||
|
||||
type embeddedStruct struct {
|
||||
A string
|
||||
}
|
||||
|
||||
func TestEmbeddedStruct(t *testing.T) {
|
||||
tests := []struct {
|
||||
src embeddingStruct
|
||||
dst embeddingStruct
|
||||
expected embeddingStruct
|
||||
}{
|
||||
{
|
||||
src: embeddingStruct{
|
||||
embeddedStruct{"foo"},
|
||||
},
|
||||
dst: embeddingStruct{
|
||||
embeddedStruct{""},
|
||||
},
|
||||
expected: embeddingStruct{
|
||||
embeddedStruct{"foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
src: embeddingStruct{
|
||||
embeddedStruct{""},
|
||||
},
|
||||
dst: embeddingStruct{
|
||||
embeddedStruct{"bar"},
|
||||
},
|
||||
expected: embeddingStruct{
|
||||
embeddedStruct{"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
src: embeddingStruct{
|
||||
embeddedStruct{"foo"},
|
||||
},
|
||||
dst: embeddingStruct{
|
||||
embeddedStruct{"bar"},
|
||||
},
|
||||
expected: embeddingStruct{
|
||||
embeddedStruct{"bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := Merge(&test.dst, test.src)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(test.dst, test.expected) {
|
||||
t.Errorf("unexpected output\nexpected:\n%+v\nsaw:\n%+v\n", test.expected, test.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointerStructNil(t *testing.T) {
|
||||
a := pointerTest{nil}
|
||||
b := pointerTest{&simpleTest{19}}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if a.C.Value != b.C.Value {
|
||||
t.Fatalf("b not merged in a properly: a.C.Value(%d) != b.C.Value(%d)", a.C.Value, b.C.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceStruct(t *testing.T) {
|
||||
a := sliceTest{}
|
||||
b := sliceTest{[]int{1, 2, 3}}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(b.S) != 3 {
|
||||
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))
|
||||
}
|
||||
|
||||
a = sliceTest{[]int{1}}
|
||||
b = sliceTest{[]int{1, 2, 3}}
|
||||
if err := Merge(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(a.S) != 1 {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(a.S) == len(b.S) {
|
||||
t.Fatalf("b merged unexpectedly %d != %d", len(a.S), len(b.S))
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapsWithOverwrite(t *testing.T) {
|
||||
m := map[string]simpleTest{
|
||||
"a": {}, // overwritten by 16
|
||||
"b": {42}, // not overwritten by empty value
|
||||
"c": {13}, // overwritten by 12
|
||||
"d": {61},
|
||||
}
|
||||
n := map[string]simpleTest{
|
||||
"a": {16},
|
||||
"b": {},
|
||||
"c": {12},
|
||||
"e": {14},
|
||||
}
|
||||
expect := map[string]simpleTest{
|
||||
"a": {16},
|
||||
"b": {},
|
||||
"c": {12},
|
||||
"d": {61},
|
||||
"e": {14},
|
||||
}
|
||||
|
||||
if err := MergeWithOverwrite(&m, n); 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 TestMaps(t *testing.T) {
|
||||
m := map[string]simpleTest{
|
||||
"a": {},
|
||||
"b": {42},
|
||||
"c": {13},
|
||||
"d": {61},
|
||||
}
|
||||
n := map[string]simpleTest{
|
||||
"a": {16},
|
||||
"b": {},
|
||||
"c": {12},
|
||||
"e": {14},
|
||||
}
|
||||
expect := map[string]simpleTest{
|
||||
"a": {0},
|
||||
"b": {42},
|
||||
"c": {13},
|
||||
"d": {61},
|
||||
"e": {14},
|
||||
}
|
||||
|
||||
if err := Merge(&m, n); 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)
|
||||
}
|
||||
if m["a"].Value != 0 {
|
||||
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)
|
||||
}
|
||||
if m["b"].Value != 42 {
|
||||
t.Fatalf(`n wrongly merged in m: m["b"].Value(%d) != n["b"].Value(%d)`, m["b"].Value, n["b"].Value)
|
||||
}
|
||||
if m["c"].Value != 13 {
|
||||
t.Fatalf(`n overwritten in m: m["c"].Value(%d) != n["c"].Value(%d)`, m["c"].Value, n["c"].Value)
|
||||
}
|
||||
}
|
||||
|
||||
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{})
|
||||
// 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())
|
||||
}
|
||||
currentLength := len(license["fields"].(map[interface{}]interface{}))
|
||||
if currentLength != expectedLength {
|
||||
t.Fatalf(`thing not merged in license properly, license must have %d elements instead of %d`, expectedLength, currentLength)
|
||||
}
|
||||
fields := license["fields"].(map[interface{}]interface{})
|
||||
if _, ok := fields["id"]; !ok {
|
||||
t.Fatalf(`thing not merged in license properly, license must have a new id field from thing`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoPointerValues(t *testing.T) {
|
||||
a := &simpleTest{}
|
||||
b := &simpleTest{42}
|
||||
if err := Merge(a, b); err != nil {
|
||||
t.Fatalf(`Boom. You crossed the streams: %s`, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
a := complexTest{}
|
||||
a.ID = "athing"
|
||||
c := moreComplextText{a, simpleTest{}, simpleTest{}}
|
||||
b := map[string]interface{}{
|
||||
"ct": map[string]interface{}{
|
||||
"st": map[string]interface{}{
|
||||
"value": 42,
|
||||
},
|
||||
"sz": 1,
|
||||
"id": "bthing",
|
||||
},
|
||||
"st": &simpleTest{144}, // Mapping a reference
|
||||
"zt": simpleTest{299}, // Mapping a missing field (zt doesn't exist)
|
||||
"nt": simpleTest{3},
|
||||
}
|
||||
if err := Map(&c, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
m := b["ct"].(map[string]interface{})
|
||||
n := m["st"].(map[string]interface{})
|
||||
o := b["st"].(*simpleTest)
|
||||
p := b["nt"].(simpleTest)
|
||||
if c.Ct.St.Value != 42 {
|
||||
t.Fatalf("b not merged in properly: c.Ct.St.Value(%d) != b.Ct.St.Value(%d)", c.Ct.St.Value, n["value"])
|
||||
}
|
||||
if c.St.Value != 144 {
|
||||
t.Fatalf("b not merged in properly: c.St.Value(%d) != b.St.Value(%d)", c.St.Value, o.Value)
|
||||
}
|
||||
if c.Nt.Value != 3 {
|
||||
t.Fatalf("b not merged in properly: c.Nt.Value(%d) != b.Nt.Value(%d)", c.St.Value, p.Value)
|
||||
}
|
||||
if c.Ct.sz == 1 {
|
||||
t.Fatalf("a's private field sz not preserved from merge: c.Ct.sz(%d) == b.Ct.sz(%d)", c.Ct.sz, m["sz"])
|
||||
}
|
||||
if c.Ct.ID == m["id"] {
|
||||
t.Fatalf("a's field ID merged unexpectedly: c.Ct.ID(%s) == b.Ct.ID(%s)", c.Ct.ID, m["id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleMap(t *testing.T) {
|
||||
a := simpleTest{}
|
||||
b := map[string]interface{}{
|
||||
"value": 42,
|
||||
}
|
||||
if err := Map(&a, b); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if a.Value != 42 {
|
||||
t.Fatalf("b not merged in properly: a.Value(%d) != b.Value(%v)", a.Value, b["value"])
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
B *simpleTest
|
||||
}
|
||||
|
||||
func TestBackAndForth(t *testing.T) {
|
||||
pt := pointerMapTest{42, 1, &simpleTest{66}}
|
||||
m := make(map[string]interface{})
|
||||
if err := Map(&m, pt); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
var (
|
||||
v interface{}
|
||||
ok bool
|
||||
)
|
||||
if v, ok = m["a"]; v.(int) != pt.A || !ok {
|
||||
t.Fatalf("pt not merged in properly: m[`a`](%d) != pt.A(%d)", v, pt.A)
|
||||
}
|
||||
if v, ok = m["b"]; !ok {
|
||||
t.Fatalf("pt not merged in properly: B is missing in m")
|
||||
}
|
||||
var st *simpleTest
|
||||
if st = v.(*simpleTest); st.Value != 66 {
|
||||
t.Fatalf("something went wrong while mapping pt on m, B wasn't copied")
|
||||
}
|
||||
bpt := pointerMapTest{}
|
||||
if err := Map(&bpt, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bpt.A != pt.A {
|
||||
t.Fatalf("pt not merged in properly: bpt.A(%d) != pt.A(%d)", bpt.A, pt.A)
|
||||
}
|
||||
if bpt.hidden == pt.hidden {
|
||||
t.Fatalf("pt unexpectedly merged: bpt.hidden(%d) == pt.hidden(%d)", bpt.hidden, pt.hidden)
|
||||
}
|
||||
if bpt.B.Value != pt.B.Value {
|
||||
t.Fatalf("pt not merged in properly: bpt.B.Value(%d) != pt.B.Value(%d)", bpt.B.Value, pt.B.Value)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
dataStruct := structWithTimePointer{
|
||||
Birth: &now,
|
||||
}
|
||||
dataMap := map[string]interface{}{
|
||||
"Birth": &now,
|
||||
}
|
||||
b := structWithTimePointer{}
|
||||
if err := Merge(&b, dataStruct); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if b.Birth.IsZero() {
|
||||
t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth)
|
||||
}
|
||||
if b.Birth != dataStruct.Birth {
|
||||
t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataStruct['Birth'](%v)", b.Birth, dataStruct.Birth)
|
||||
}
|
||||
b = structWithTimePointer{}
|
||||
if err := Map(&b, dataMap); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if b.Birth.IsZero() {
|
||||
t.Fatalf("time.Time not merged in properly: b.Birth(%v) != dataMap['Birth'](%v)", b.Birth, dataMap["Birth"])
|
||||
}
|
||||
}
|
||||
|
||||
type simpleNested struct {
|
||||
A int
|
||||
}
|
||||
|
||||
type structWithNestedPtrValueMap struct {
|
||||
NestedPtrValue map[string]*simpleNested
|
||||
}
|
||||
|
||||
func TestNestedPtrValueInMap(t *testing.T) {
|
||||
src := &structWithNestedPtrValueMap{
|
||||
NestedPtrValue: map[string]*simpleNested{
|
||||
"x": {
|
||||
A: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
dst := &structWithNestedPtrValueMap{
|
||||
NestedPtrValue: map[string]*simpleNested{
|
||||
"x": {},
|
||||
},
|
||||
}
|
||||
if err := Map(dst, src); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if dst.NestedPtrValue["x"].A == 0 {
|
||||
t.Fatalf("Nested Ptr value not merged in properly: dst.NestedPtrValue[\"x\"].A(%v) != src.NestedPtrValue[\"x\"].A(%v)", dst.NestedPtrValue["x"].A, src.NestedPtrValue["x"].A)
|
||||
}
|
||||
}
|
||||
|
||||
func loadYAML(path string) (m map[string]interface{}) {
|
||||
m = make(map[string]interface{})
|
||||
raw, _ := ioutil.ReadFile(path)
|
||||
_ = yaml.Unmarshal(raw, &m)
|
||||
return
|
||||
}
|
||||
|
||||
type structWithMap struct {
|
||||
m map[string]structWithUnexportedProperty
|
||||
}
|
||||
|
||||
type structWithUnexportedProperty struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func TestUnexportedProperty(t *testing.T) {
|
||||
a := structWithMap{map[string]structWithUnexportedProperty{
|
||||
"key": structWithUnexportedProperty{"hello"},
|
||||
}}
|
||||
b := structWithMap{map[string]structWithUnexportedProperty{
|
||||
"key": structWithUnexportedProperty{"hi"},
|
||||
}}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Should not have panicked")
|
||||
}
|
||||
}()
|
||||
Merge(&a, b)
|
||||
}
|
||||
|
||||
type structWithBoolPointer struct {
|
||||
C *bool
|
||||
}
|
||||
|
||||
func TestBooleanPointer(t *testing.T) {
|
||||
bt, bf := true, false
|
||||
src := structWithBoolPointer{
|
||||
&bt,
|
||||
}
|
||||
dst := structWithBoolPointer{
|
||||
&bf,
|
||||
}
|
||||
if err := Merge(&dst, src); err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if dst.C == src.C {
|
||||
t.Fatalf("dst.C should be a different pointer than src.C")
|
||||
}
|
||||
if *dst.C != *src.C {
|
||||
t.Fatalf("dst.C should be true")
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import: ../../../../fossene/db/schema/thing.yml
|
||||
fields:
|
||||
site: string
|
||||
author: root
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fields:
|
||||
id: int
|
||||
name: string
|
||||
parent: ref "datu:thing"
|
||||
status: enum(draft, public, private)
|
||||
author: updater
|
||||
Reference in New Issue
Block a user