/
diff.go
293 lines (275 loc) · 8.33 KB
/
diff.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
package tftypes
import (
"errors"
"fmt"
"math/big"
)
// ValueDiff expresses a subset of a Value that is different between two
// Values. The Path property indicates where the subset is located within the
// Value, and Value1 and Value2 indicate what the subset is in each of the
// Values. If the Value does not contain a subset at that AttributePath, its
// Value will be nil. This is distinct from a Value with a nil in it (a "null"
// value), which is present in the Value.
type ValueDiff struct {
// The Path these different subsets are located at in the original
// Values.
Path *AttributePath
// The subset of the first Value passed to Diff found at the
// AttributePath indicated by Path.
Value1 *Value
// The subset of the second Value passed to Diff found at the
// AttributePath indicated by Path.
Value2 *Value
}
func (v ValueDiff) String() string {
val1 := "{no value set}"
if v.Value1 != nil {
val1 = v.Value1.String()
}
val2 := "{no value set}"
if v.Value2 != nil {
val2 = v.Value2.String()
}
return fmt.Sprintf("%s: value1: %s, value2: %s",
v.Path.String(), val1, val2)
}
// Equal returns whether two ValueDiffs should be considered equal or not.
// ValueDiffs are consisdered equal when their Path, Value1, and Value2
// properties are considered equal.
func (v ValueDiff) Equal(o ValueDiff) bool {
if !v.Path.Equal(o.Path) {
return false
}
if v.Value1 == nil && o.Value1 != nil {
return false
}
if v.Value1 != nil && o.Value1 == nil {
return false
}
if v.Value1 != nil && o.Value1 != nil && !v.Value1.Equal(*o.Value1) {
return false
}
if v.Value2 == nil && o.Value2 != nil {
return false
}
if v.Value2 != nil && o.Value2 == nil {
return false
}
if v.Value2 != nil && o.Value2 != nil && !v.Value2.Equal(*o.Value2) {
return false
}
return true
}
// Diff computes the differences between `val1` and `val2` and surfaces them as
// a slice of ValueDiffs. The ValueDiffs in the struct will use `val1`'s values
// as Value1 and `val2`'s values as Value2. An empty or nil slice means the two
// Values can be considered equal. Values must be the same type when passed to
// Diff; passing in Values of two different types will result in an error. If
// both Values are empty, they are considered equal. If one Value is missing
// type, it will result in an error. val1.Type().Is(val2.Type()) is a safe way
// to check that Values can be compared with Diff.
func (val1 Value) Diff(val2 Value) ([]ValueDiff, error) {
var diffs []ValueDiff
if val1.Type() == nil && val2.Type() == nil && val1.value == nil && val2.value == nil {
return diffs, nil
}
if (val1.Type() == nil && val2.Type() != nil) || (val1.Type() != nil && val2.Type() == nil) {
return nil, errors.New("cannot diff value missing type")
}
if !val1.Type().Is(val2.Type()) {
return nil, errors.New("Can't diff values of different types")
}
// make sure everything in val2 is also in val1
err := Walk(val2, func(path *AttributePath, value2 Value) (bool, error) {
_, _, err := WalkAttributePath(val1, path)
if err != nil && err != ErrInvalidStep {
return false, fmt.Errorf("Error walking %q: %w", path, err)
} else if err == ErrInvalidStep {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: nil,
Value2: &value2,
})
return false, nil
}
return true, nil
})
if err != nil {
return nil, err
}
// make sure everything in val1 is also in val2 and also that it all matches
err = Walk(val1, func(path *AttributePath, value1 Value) (bool, error) {
// pull out the Value at the same path in val2
value2I, _, err := WalkAttributePath(val2, path)
if err != nil && err != ErrInvalidStep {
return false, fmt.Errorf("Error walking %q: %w", path, err)
} else if err == ErrInvalidStep {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: nil,
})
return true, nil
}
// convert from an interface{} to a Value
value2 := value2I.(Value)
// if they're both unknown, no need to continue
if !value1.IsKnown() && !value2.IsKnown() {
return false, nil
}
// if val1 is unknown and val2 not, we have a diff
// no need to continue to recurse into val1, no further to go
if !value1.IsKnown() && value2.IsKnown() {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return false, nil
}
// if val2 is unknown and val1 not, we have a diff
// continue to recurse though, so we can surface the elements of val1
// that are now "missing" as diffs
if value1.IsKnown() && !value2.IsKnown() {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return true, nil
}
// if they're both null, no need to continue
if value1.IsNull() && value2.IsNull() {
return false, nil
}
// if val1 is null and val2 not, we have a diff
// no need to continue to recurse into val1, no further to go
if value1.IsNull() && !value2.IsNull() {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return false, nil
}
// if val2 is null and val1 not, we have a diff
// continue to recurse though, so we can surface the elements of val1
// that are now "missing" as diffs
if !value1.IsNull() && value2.IsNull() {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return true, nil
}
// we know there are known, non-null values, time to compare them
switch {
case value1.Type().Is(String):
var s1, s2 string
err := value1.As(&s1)
if err != nil {
return false, fmt.Errorf("Error converting %s (value1) at %q: %w", value1, path, err)
}
err = value2.As(&s2)
if err != nil {
return false, fmt.Errorf("Error converting %s (value2) at %q: %w", value2, path, err)
}
if s1 != s2 {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
}
return false, nil
case value1.Type().Is(Number):
n1, n2 := big.NewFloat(0), big.NewFloat(0)
err := value1.As(&n1)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
err = value2.As(&n2)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
if n1.Cmp(n2) != 0 {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
}
return false, nil
case value1.Type().Is(Bool):
var b1, b2 bool
err := value1.As(&b1)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
err = value2.As(&b2)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
if b1 != b2 {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
}
return false, nil
case value1.Type().Is(List{}), value1.Type().Is(Set{}), value1.Type().Is(Tuple{}):
var s1, s2 []Value
err := value1.As(&s1)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
err = value2.As(&s2)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
// we only care about if the lengths match for lists,
// sets, and tuples. If any of the elements differ,
// the recursion of the walk will find them for us.
if len(s1) != len(s2) {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return true, nil
}
return true, nil
case value1.Type().Is(Map{}), value1.Type().Is(Object{}):
m1 := map[string]Value{}
m2 := map[string]Value{}
err := value1.As(&m1)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
err = value2.As(&m2)
if err != nil {
return false, fmt.Errorf("Error converting %q: %w", path, err)
}
// we need maps and objects to have the same exact keys
// as each other
if len(m1) != len(m2) {
diffs = append(diffs, ValueDiff{
Path: path,
Value1: &value1,
Value2: &value2,
})
return true, nil
}
// if we have the same keys, we can just let recursion
// from the walk check the sub-values match
return true, nil
case value1.Type().Is(DynamicPseudoType):
// Let recursion from the walk check the sub-values match
return true, nil
}
return false, fmt.Errorf("unexpected type %v in Diff at %s", value1.Type(), path)
})
return diffs, err
}