-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathutil.go
202 lines (169 loc) · 5.62 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package structconf
import (
"fmt"
"reflect"
"github.com/hashicorp/go-multierror"
)
// MergeMaps merges the passed maps
// The resulting map contains all keys that existed in either of the passed maps.
// For keys that exist in both maps, the value from the "b" map takes precedence,
// iff it is not a zero-value.
// Keys that are present in both maps are expected to be of the same type. If this is not the case,
// an error will be returned.
func MergeMaps(a, b map[string]interface{}) (map[string]interface{}, error) {
merged, err := mergeMaps(reflect.ValueOf(a), reflect.ValueOf(b))
if err != nil {
return nil, err
}
// As the inputs are map[string]interface{} values, the result will always
// be a map[string]interface{}, which means we can cast here without any checks
return merged.(map[string]interface{}), nil
}
// reflect.Kind values which cannot be merged
var mergeUnsupportedKinds = []reflect.Kind{
reflect.Invalid,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.UnsafePointer,
}
// isMergeUnsupportedKind checks if a given kind is unsupported for merging
func isMergeUnsupportedKind(k reflect.Kind) error {
for _, unsupportedKind := range mergeUnsupportedKinds {
if unsupportedKind == k {
return fmt.Errorf("Kind %s unsupported for merging", k.String())
}
}
return nil
}
// MergeValues "merges" two values
//
// The internal logic is as follows:
//
// - If b is zero, return a
// - If a is zero, return b
// - If both values are non-zero and scalar types, convert b with a's type, if possible.
// - If both values are non-zero and slices or arrays, return b
// - If both values are non-zero and maps, merge each element of the map using the above logic
func MergeValues(a, b interface{}) (interface{}, error) {
// Simple case: a is nil
if a == nil {
return b, nil
}
// Simple case: b is nil
if b == nil {
return a, nil
}
valueB := reflect.ValueOf(b)
// Simple case: b is zero
if reflect.DeepEqual(b, reflect.Zero(valueB.Type()).Interface()) {
return a, nil
}
valueA := reflect.ValueOf(a)
// At this point both a and b are non-nil and non-zero
kindA := valueA.Kind()
kindB := valueB.Kind()
// Special case: both values are pointers
if kindA == reflect.Ptr && kindA == kindB {
return mergePointers(valueA, valueB)
}
// Check if both kinds are supported for merging
if err := isMergeUnsupportedKind(kindA); err != nil {
return nil, err
}
if err := isMergeUnsupportedKind(kindB); err != nil {
return nil, err
}
// Special case: both are maps
if kindA == reflect.Map && kindA == kindB {
// Special case: maps need merging
return mergeMaps(valueA, valueB)
}
// Special case: both are slices or arrays
if kindA == kindB && (kindA == reflect.Slice || kindA == reflect.Array) {
return mergeSlices(valueA, valueB)
}
// Finally: try merging scalar values
return convertScalarValues(valueA, valueB)
}
// mergePointers handles merging of pointer values
func mergePointers(a reflect.Value, b reflect.Value) (interface{}, error) {
merged, err := MergeValues(a.Elem().Interface(), b.Elem().Interface())
if err != nil {
return nil, err
} else if merged == nil {
return nil, nil
}
// Ensure we do return a pointer again
res := reflect.New(a.Type().Elem())
res.Elem().Set(reflect.ValueOf(merged))
return res.Interface(), nil
}
// mergeMaps handles merging of map values
func mergeMaps(a reflect.Value, b reflect.Value) (m interface{}, err error) {
mValue := reflect.MakeMap(a.Type())
var sampleKeyValue reflect.Value
// Initialize result map with all values from a
for _, k := range a.MapKeys() {
mValue.SetMapIndex(k, a.MapIndex(k))
sampleKeyValue = k
}
// Now iterate over all keys from b and set them on our result map
for _, k := range b.MapKeys() {
// Convert key
convertedKeyIntf, convertErr := convertScalarValues(sampleKeyValue, k)
if convertErr != nil {
err = multierror.Append(err, multierror.Prefix(convertErr, fmt.Sprintf("key %v:", k.Interface())))
continue
}
key := reflect.ValueOf(convertedKeyIntf)
v := b.MapIndex(k)
// Check if key exists
existingV := mValue.MapIndex(key)
if !existingV.IsValid() {
// Key does not exist.
mValue.SetMapIndex(key, v)
continue
}
// The key does already exist, in which case we need to merge the existing value and the
// value from b
mergedV, mergeErr := MergeValues(existingV.Interface(), v.Interface())
if mergeErr != nil {
err = multierror.Append(err, multierror.Prefix(mergeErr, fmt.Sprintf("key %v:", convertedKeyIntf)))
continue
}
mValue.SetMapIndex(key, reflect.ValueOf(mergedV))
}
if err != nil {
return
}
m = mValue.Interface()
return
}
// mergeSlices merges two slices
//
// The logic is very simple: if b is not empty return b, otherwise, return a
func mergeSlices(a reflect.Value, b reflect.Value) (interface{}, error) {
if b.Len() != 0 {
return b.Interface(), nil
}
return a.Interface(), nil
}
// mergeScalarValues merges two scalar values
//
// This function guarantees that the returned value is of the same type as a, so conversion is attempted.
// If conversion fails, an error is returned
func convertScalarValues(a reflect.Value, b reflect.Value) (interface{}, error) {
// Detect the actual types of both a and b
aType := reflect.TypeOf(a.Interface())
bType := reflect.TypeOf(b.Interface())
// Simple case: both values are of the same type, so simply return b, as b always overrides a
if aType == bType {
return b.Interface(), nil
}
// If b can be converted to a, just convert the value
if bType.ConvertibleTo(aType) {
return reflect.ValueOf(b.Interface()).Convert(aType).Interface(), nil
}
return nil, fmt.Errorf("Kind mismatch: %s != %s", aType.Kind(), bType.Kind())
}