mirror of
https://github.com/rocky-linux/peridot.git
synced 2024-11-27 15:36:25 +00:00
410 lines
12 KiB
Go
410 lines
12 KiB
Go
// 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 (
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
func hasMergeableFields(dst reflect.Value) (exported bool) {
|
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
|
field := dst.Type().Field(i)
|
|
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
|
exported = exported || hasMergeableFields(dst.Field(i))
|
|
} else if isExportedComponent(&field) {
|
|
exported = exported || len(field.PkgPath) == 0
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func isExportedComponent(field *reflect.StructField) bool {
|
|
pkgPath := field.PkgPath
|
|
if len(pkgPath) > 0 {
|
|
return false
|
|
}
|
|
c := field.Name[0]
|
|
if 'a' <= c && c <= 'z' || c == '_' {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type Config struct {
|
|
Transformers Transformers
|
|
Overwrite bool
|
|
ShouldNotDereference bool
|
|
AppendSlice bool
|
|
TypeCheck bool
|
|
overwriteWithEmptyValue bool
|
|
overwriteSliceWithEmptyValue bool
|
|
sliceDeepCopy bool
|
|
debug bool
|
|
}
|
|
|
|
type Transformers interface {
|
|
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
|
}
|
|
|
|
// Traverses recursively both values, assigning src's fields values to dst.
|
|
// The map argument tracks comparisons that have already been seen, which allows
|
|
// short circuiting on recursive types.
|
|
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
|
overwrite := config.Overwrite
|
|
typeCheck := config.TypeCheck
|
|
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
|
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
|
sliceDeepCopy := config.sliceDeepCopy
|
|
|
|
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{typ, seen, addr}
|
|
}
|
|
|
|
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
|
|
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
|
err = fn(dst, src)
|
|
return
|
|
}
|
|
}
|
|
|
|
switch dst.Kind() {
|
|
case reflect.Struct:
|
|
if hasMergeableFields(dst) {
|
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
|
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
|
|
dst.Set(src)
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
if dst.IsNil() && !src.IsNil() {
|
|
if dst.CanSet() {
|
|
dst.Set(reflect.MakeMap(dst.Type()))
|
|
} else {
|
|
dst = src
|
|
return
|
|
}
|
|
}
|
|
|
|
if src.Kind() != reflect.Map {
|
|
if overwrite && dst.CanSet() {
|
|
dst.Set(src)
|
|
}
|
|
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.Interface, reflect.Slice:
|
|
if srcElement.IsNil() {
|
|
if overwrite {
|
|
dst.SetMapIndex(key, srcElement)
|
|
}
|
|
continue
|
|
}
|
|
fallthrough
|
|
default:
|
|
if !srcElement.CanInterface() {
|
|
continue
|
|
}
|
|
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
|
case reflect.Struct:
|
|
fallthrough
|
|
case reflect.Ptr:
|
|
fallthrough
|
|
case reflect.Map:
|
|
srcMapElm := srcElement
|
|
dstMapElm := dstElement
|
|
if srcMapElm.CanInterface() {
|
|
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
|
if dstMapElm.IsValid() {
|
|
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
|
}
|
|
}
|
|
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
case reflect.Slice:
|
|
srcSlice := reflect.ValueOf(srcElement.Interface())
|
|
|
|
var dstSlice reflect.Value
|
|
if !dstElement.IsValid() || dstElement.IsNil() {
|
|
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
|
} else {
|
|
dstSlice = reflect.ValueOf(dstElement.Interface())
|
|
}
|
|
|
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
|
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
|
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
|
}
|
|
dstSlice = srcSlice
|
|
} else if config.AppendSlice {
|
|
if srcSlice.Type() != dstSlice.Type() {
|
|
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
|
}
|
|
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
|
} else if sliceDeepCopy {
|
|
i := 0
|
|
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
|
|
srcElement := srcSlice.Index(i)
|
|
dstElement := dstSlice.Index(i)
|
|
|
|
if srcElement.CanInterface() {
|
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
|
}
|
|
if dstElement.CanInterface() {
|
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
|
}
|
|
|
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
}
|
|
dst.SetMapIndex(key, dstSlice)
|
|
}
|
|
}
|
|
|
|
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
|
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
|
|
continue
|
|
}
|
|
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
|
|
if dst.IsNil() {
|
|
dst.Set(reflect.MakeMap(dst.Type()))
|
|
}
|
|
dst.SetMapIndex(key, srcElement)
|
|
}
|
|
}
|
|
|
|
// Ensure that all keys in dst are deleted if they are not in src.
|
|
if overwriteWithEmptySrc {
|
|
for _, key := range dst.MapKeys() {
|
|
srcElement := src.MapIndex(key)
|
|
if !srcElement.IsValid() {
|
|
dst.SetMapIndex(key, reflect.Value{})
|
|
}
|
|
}
|
|
}
|
|
case reflect.Slice:
|
|
if !dst.CanSet() {
|
|
break
|
|
}
|
|
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
|
dst.Set(src)
|
|
} else if config.AppendSlice {
|
|
if src.Type() != dst.Type() {
|
|
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
|
}
|
|
dst.Set(reflect.AppendSlice(dst, src))
|
|
} else if sliceDeepCopy {
|
|
for i := 0; i < src.Len() && i < dst.Len(); i++ {
|
|
srcElement := src.Index(i)
|
|
dstElement := dst.Index(i)
|
|
if srcElement.CanInterface() {
|
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
|
}
|
|
if dstElement.CanInterface() {
|
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
|
}
|
|
|
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
case reflect.Ptr:
|
|
fallthrough
|
|
case reflect.Interface:
|
|
if isReflectNil(src) {
|
|
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
|
|
dst.Set(src)
|
|
}
|
|
break
|
|
}
|
|
|
|
if src.Kind() != reflect.Interface {
|
|
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
|
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
|
dst.Set(src)
|
|
}
|
|
} else if src.Kind() == reflect.Ptr {
|
|
if !config.ShouldNotDereference {
|
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
|
|
dst.Set(src)
|
|
}
|
|
}
|
|
} else if dst.Elem().Type() == src.Type() {
|
|
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
return ErrDifferentArgumentsTypes
|
|
}
|
|
break
|
|
}
|
|
|
|
if dst.IsNil() || overwrite {
|
|
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
|
dst.Set(src)
|
|
}
|
|
break
|
|
}
|
|
|
|
if dst.Elem().Kind() == src.Elem().Kind() {
|
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
|
return
|
|
}
|
|
break
|
|
}
|
|
default:
|
|
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
|
|
if mustSet {
|
|
if dst.CanSet() {
|
|
dst.Set(src)
|
|
} else {
|
|
dst = 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{}, opts ...func(*Config)) error {
|
|
return merge(dst, src, opts...)
|
|
}
|
|
|
|
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
|
|
// non-empty src attribute values.
|
|
// Deprecated: use Merge(…) with WithOverride
|
|
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
|
return merge(dst, src, append(opts, WithOverride)...)
|
|
}
|
|
|
|
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
|
func WithTransformers(transformers Transformers) func(*Config) {
|
|
return func(config *Config) {
|
|
config.Transformers = transformers
|
|
}
|
|
}
|
|
|
|
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
|
func WithOverride(config *Config) {
|
|
config.Overwrite = true
|
|
}
|
|
|
|
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
|
|
func WithOverwriteWithEmptyValue(config *Config) {
|
|
config.Overwrite = true
|
|
config.overwriteWithEmptyValue = true
|
|
}
|
|
|
|
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
|
|
func WithOverrideEmptySlice(config *Config) {
|
|
config.overwriteSliceWithEmptyValue = true
|
|
}
|
|
|
|
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
|
|
// (i.e. a non-nil pointer is never considered empty).
|
|
func WithoutDereference(config *Config) {
|
|
config.ShouldNotDereference = true
|
|
}
|
|
|
|
// WithAppendSlice will make merge append slices instead of overwriting it.
|
|
func WithAppendSlice(config *Config) {
|
|
config.AppendSlice = true
|
|
}
|
|
|
|
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
|
|
func WithTypeCheck(config *Config) {
|
|
config.TypeCheck = true
|
|
}
|
|
|
|
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
|
|
func WithSliceDeepCopy(config *Config) {
|
|
config.sliceDeepCopy = true
|
|
config.Overwrite = true
|
|
}
|
|
|
|
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
|
return ErrNonPointerArgument
|
|
}
|
|
var (
|
|
vDst, vSrc reflect.Value
|
|
err error
|
|
)
|
|
|
|
config := &Config{}
|
|
|
|
for _, opt := range opts {
|
|
opt(config)
|
|
}
|
|
|
|
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
|
return err
|
|
}
|
|
if vDst.Type() != vSrc.Type() {
|
|
return ErrDifferentArgumentsTypes
|
|
}
|
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
|
}
|
|
|
|
// IsReflectNil is the reflect value provided nil
|
|
func isReflectNil(v reflect.Value) bool {
|
|
k := v.Kind()
|
|
switch k {
|
|
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
|
|
// Both interface and slice are nil if first word is 0.
|
|
// Both are always bigger than a word; assume flagIndir.
|
|
return v.IsNil()
|
|
default:
|
|
return false
|
|
}
|
|
}
|