@@ -17,11 +17,61 @@ limitations under the License.
17
17
package typed
18
18
19
19
import (
20
+ "fmt"
21
+ "strings"
22
+
20
23
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
21
24
"sigs.k8s.io/structured-merge-diff/v4/schema"
22
25
"sigs.k8s.io/structured-merge-diff/v4/value"
23
26
)
24
27
28
+ // Comparison is the return value of a TypedValue.Compare() operation.
29
+ //
30
+ // No field will appear in more than one of the three fieldsets. If all of the
31
+ // fieldsets are empty, then the objects must have been equal.
32
+ type Comparison struct {
33
+ // Removed contains any fields removed by rhs (the right-hand-side
34
+ // object in the comparison).
35
+ Removed * fieldpath.Set
36
+ // Modified contains fields present in both objects but different.
37
+ Modified * fieldpath.Set
38
+ // Added contains any fields added by rhs.
39
+ Added * fieldpath.Set
40
+ }
41
+
42
+ // IsSame returns true if the comparison returned no changes (the two
43
+ // compared objects are similar).
44
+ func (c * Comparison ) IsSame () bool {
45
+ return c .Removed .Empty () && c .Modified .Empty () && c .Added .Empty ()
46
+ }
47
+
48
+ // String returns a human readable version of the comparison.
49
+ func (c * Comparison ) String () string {
50
+ bld := strings.Builder {}
51
+ if ! c .Modified .Empty () {
52
+ bld .WriteString (fmt .Sprintf ("- Modified Fields:\n %v\n " , c .Modified ))
53
+ }
54
+ if ! c .Added .Empty () {
55
+ bld .WriteString (fmt .Sprintf ("- Added Fields:\n %v\n " , c .Added ))
56
+ }
57
+ if ! c .Removed .Empty () {
58
+ bld .WriteString (fmt .Sprintf ("- Removed Fields:\n %v\n " , c .Removed ))
59
+ }
60
+ return bld .String ()
61
+ }
62
+
63
+ // ExcludeFields fields from the compare recursively removes the fields
64
+ // from the entire comparison
65
+ func (c * Comparison ) ExcludeFields (fields * fieldpath.Set ) * Comparison {
66
+ if fields == nil || fields .Empty () {
67
+ return c
68
+ }
69
+ c .Removed = c .Removed .RecursiveDifference (fields )
70
+ c .Modified = c .Modified .RecursiveDifference (fields )
71
+ c .Added = c .Added .RecursiveDifference (fields )
72
+ return c
73
+ }
74
+
25
75
type compareWalker struct {
26
76
lhs value.Value
27
77
rhs value.Value
@@ -31,15 +81,8 @@ type compareWalker struct {
31
81
// Current path that we are comparing
32
82
path fieldpath.Path
33
83
34
- // How to compare. Called after schema validation for all leaf fields.
35
- rule compareRule
36
-
37
- // If set, called after non-leaf items have been compared. (`out` is
38
- // probably already set.)
39
- postItemHook compareRule
40
-
41
- // output of the merge operation (nil if none)
42
- out * interface {}
84
+ // Resulting comparison.
85
+ comparison * Comparison
43
86
44
87
// internal housekeeping--don't set when constructing.
45
88
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
@@ -50,11 +93,6 @@ type compareWalker struct {
50
93
allocator value.Allocator
51
94
}
52
95
53
- // compareRule examine w.lhs and w.rhs (up to one of which may be nil) and
54
- // optionally set w.out. If lhs and rhs are both set, they will be of
55
- // comparable type.
56
- type compareRule func (w * compareWalker )
57
-
58
96
// compare compares stuff.
59
97
func (w * compareWalker ) compare (prefixFn func () string ) (errs ValidationErrors ) {
60
98
if w .lhs == nil && w .rhs == nil {
@@ -81,8 +119,12 @@ func (w *compareWalker) compare(prefixFn func() string) (errs ValidationErrors)
81
119
errs = append (errs , handleAtom (arhs , w .typeRef , w )... )
82
120
}
83
121
84
- if ! w .inLeaf && w .postItemHook != nil {
85
- w .postItemHook (w )
122
+ if ! w .inLeaf {
123
+ if w .lhs == nil {
124
+ w .comparison .Added .Insert (w .path )
125
+ } else if w .rhs == nil {
126
+ w .comparison .Removed .Insert (w .path )
127
+ }
86
128
}
87
129
return errs .WithLazyPrefix (prefixFn )
88
130
}
@@ -98,7 +140,15 @@ func (w *compareWalker) doLeaf() {
98
140
w .inLeaf = true
99
141
100
142
// We don't recurse into leaf fields for merging.
101
- w .rule (w )
143
+ if w .lhs == nil {
144
+ w .comparison .Added .Insert (w .path )
145
+ } else if w .rhs == nil {
146
+ w .comparison .Removed .Insert (w .path )
147
+ } else if ! value .EqualsUsing (w .allocator , w .rhs , w .lhs ) {
148
+ // TODO: Equality is not sufficient for this.
149
+ // Need to implement equality check on the value type.
150
+ w .comparison .Modified .Insert (w .path )
151
+ }
102
152
}
103
153
104
154
func (w * compareWalker ) doScalar (t * schema.Scalar ) ValidationErrors {
@@ -115,7 +165,7 @@ func (w *compareWalker) doScalar(t *schema.Scalar) ValidationErrors {
115
165
return nil
116
166
}
117
167
118
- func (w * compareWalker ) prepareDescent (pe fieldpath.PathElement , tr schema.TypeRef ) * compareWalker {
168
+ func (w * compareWalker ) prepareDescent (pe fieldpath.PathElement , tr schema.TypeRef , cmp * Comparison ) * compareWalker {
119
169
if w .spareWalkers == nil {
120
170
// first descent.
121
171
w .spareWalkers = & []* compareWalker {}
@@ -131,7 +181,7 @@ func (w *compareWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeR
131
181
w2 .path = append (w2 .path , pe )
132
182
w2 .lhs = nil
133
183
w2 .rhs = nil
134
- w2 .out = nil
184
+ w2 .comparison = cmp
135
185
return w2
136
186
}
137
187
@@ -162,102 +212,64 @@ func (w *compareWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
162
212
if lhs != nil {
163
213
lLen = lhs .Length ()
164
214
}
165
- outLen := lLen
166
- if outLen < rLen {
167
- outLen = rLen
168
- }
169
- out := make ([]interface {}, 0 , outLen )
170
-
171
- rhsOrder , observedRHS , rhsErrs := w .indexListPathElements (t , rhs )
172
- errs = append (errs , rhsErrs ... )
173
- lhsOrder , observedLHS , lhsErrs := w .indexListPathElements (t , lhs )
174
- errs = append (errs , lhsErrs ... )
175
215
176
- sharedOrder := make ([]* fieldpath.PathElement , 0 , rLen )
177
- for i := range rhsOrder {
178
- pe := & rhsOrder [i ]
179
- if _ , ok := observedLHS .Get (* pe ); ok {
180
- sharedOrder = append (sharedOrder , pe )
216
+ lValues := fieldpath .MakePathElementValueMap (lLen )
217
+ for i := 0 ; i < lLen ; i ++ {
218
+ child := lhs .At (i )
219
+ pe , err := listItemToPathElement (w .allocator , w .schema , t , i , child )
220
+ if err != nil {
221
+ errs = append (errs , errorf ("element %v: %v" , i , err .Error ())... )
222
+ // If we can't construct the path element, we can't
223
+ // even report errors deeper in the schema, so bail on
224
+ // this element.
225
+ continue
226
+ }
227
+ // Ignore repeated occurences of `pe`.
228
+ if _ , found := lValues .Get (pe ); found {
229
+ continue
181
230
}
231
+ lValues .Insert (pe , child )
182
232
}
183
233
184
- var nextShared * fieldpath.PathElement
185
- if len (sharedOrder ) > 0 {
186
- nextShared = sharedOrder [0 ]
187
- sharedOrder = sharedOrder [1 :]
188
- }
189
-
190
- lLen , rLen = len (lhsOrder ), len (rhsOrder )
191
- for lI , rI := 0 , 0 ; lI < lLen || rI < rLen ; {
192
- if lI < lLen && rI < rLen {
193
- pe := lhsOrder [lI ]
194
- if pe .Equals (rhsOrder [rI ]) {
195
- // merge LHS & RHS items
196
- lChild , _ := observedLHS .Get (pe )
197
- rChild , _ := observedRHS .Get (pe )
198
- mergeOut , errs := w .mergeListItem (t , pe , lChild , rChild )
199
- errs = append (errs , errs ... )
200
- if mergeOut != nil {
201
- out = append (out , * mergeOut )
202
- }
203
- lI ++
204
- rI ++
205
-
206
- nextShared = nil
207
- if len (sharedOrder ) > 0 {
208
- nextShared = sharedOrder [0 ]
209
- sharedOrder = sharedOrder [1 :]
210
- }
211
- continue
212
- }
213
- if _ , ok := observedRHS .Get (pe ); ok && nextShared != nil && ! nextShared .Equals (lhsOrder [lI ]) {
214
- // shared item, but not the one we want in this round
215
- lI ++
216
- continue
217
- }
218
- }
219
- if lI < lLen {
220
- pe := lhsOrder [lI ]
221
- if _ , ok := observedRHS .Get (pe ); ! ok {
222
- // take LHS item
223
- lChild , _ := observedLHS .Get (pe )
224
- mergeOut , errs := w .mergeListItem (t , pe , lChild , nil )
225
- errs = append (errs , errs ... )
226
- if mergeOut != nil {
227
- out = append (out , * mergeOut )
228
- }
229
- lI ++
230
- continue
231
- }
234
+ rValues := fieldpath .MakePathElementValueMap (rLen )
235
+ for i := 0 ; i < rLen ; i ++ {
236
+ rValue := rhs .At (i )
237
+ pe , err := listItemToPathElement (w .allocator , w .schema , t , i , rValue )
238
+ if err != nil {
239
+ errs = append (errs , errorf ("element %v: %v" , i , err .Error ())... )
240
+ // If we can't construct the path element, we can't
241
+ // even report errors deeper in the schema, so bail on
242
+ // this element.
243
+ continue
232
244
}
233
- if rI < rLen {
234
- // Take the RHS item, merge with matching LHS item if possible
235
- pe := rhsOrder [rI ]
236
- lChild , _ := observedLHS .Get (pe ) // may be nil
237
- rChild , _ := observedRHS .Get (pe )
238
- mergeOut , errs := w .mergeListItem (t , pe , lChild , rChild )
239
- errs = append (errs , errs ... )
240
- if mergeOut != nil {
241
- out = append (out , * mergeOut )
242
- }
243
- rI ++
244
- // Advance nextShared, if we are merging nextShared.
245
- if nextShared != nil && nextShared .Equals (pe ) {
246
- nextShared = nil
247
- if len (sharedOrder ) > 0 {
248
- nextShared = sharedOrder [0 ]
249
- sharedOrder = sharedOrder [1 :]
250
- }
251
- }
245
+ // Ignore repeated occurences of `pe`.
246
+ if _ , found := rValues .Get (pe ); found {
247
+ continue
252
248
}
249
+ rValues .Insert (pe , rValue )
250
+ // We can merge with nil if lValue is not present.
251
+ lValue , _ := lValues .Get (pe )
252
+ errs = append (errs , w .mergeListItem (t , pe , lValue , rValue )... )
253
253
}
254
254
255
- if len (out ) > 0 {
256
- i := interface {}(out )
257
- w .out = & i
255
+ // Add items from left that are not in right.
256
+ for i := 0 ; i < lLen ; i ++ {
257
+ lValue := lhs .At (i )
258
+ pe , err := listItemToPathElement (w .allocator , w .schema , t , i , lValue )
259
+ if err != nil {
260
+ errs = append (errs , errorf ("element %v: %v" , i , err .Error ())... )
261
+ // If we can't construct the path element, we can't
262
+ // even report errors deeper in the schema, so bail on
263
+ // this element.
264
+ continue
265
+ }
266
+ if _ , found := rValues .Get (pe ); found {
267
+ continue
268
+ }
269
+ errs = append (errs , w .mergeListItem (t , pe , lValue , nil )... )
258
270
}
259
271
260
- return errs
272
+ return
261
273
}
262
274
263
275
func (w * compareWalker ) indexListPathElements (t * schema.List , list value.List ) ([]fieldpath.PathElement , fieldpath.PathElementValueMap , ValidationErrors ) {
@@ -288,16 +300,13 @@ func (w *compareWalker) indexListPathElements(t *schema.List, list value.List) (
288
300
return pes , observed , errs
289
301
}
290
302
291
- func (w * compareWalker ) mergeListItem (t * schema.List , pe fieldpath.PathElement , lChild , rChild value.Value ) ( out * interface {}, errs ValidationErrors ) {
292
- w2 := w .prepareDescent (pe , t .ElementType )
303
+ func (w * compareWalker ) mergeListItem (t * schema.List , pe fieldpath.PathElement , lChild , rChild value.Value ) ValidationErrors {
304
+ w2 := w .prepareDescent (pe , t .ElementType , w . comparison )
293
305
w2 .lhs = lChild
294
306
w2 .rhs = rChild
295
- errs = append (errs , w2 .compare (pe .String )... )
296
- if w2 .out != nil {
297
- out = w2 .out
298
- }
307
+ errs := w2 .compare (pe .String )
299
308
w .finishDescent (w2 )
300
- return
309
+ return errs
301
310
}
302
311
303
312
func (w * compareWalker ) derefList (prefix string , v value.Value ) (value.List , ValidationErrors ) {
@@ -346,13 +355,10 @@ func (w *compareWalker) visitMapItem(t *schema.Map, out map[string]interface{},
346
355
fieldType = sf .Type
347
356
}
348
357
pe := fieldpath.PathElement {FieldName : & key }
349
- w2 := w .prepareDescent (pe , fieldType )
358
+ w2 := w .prepareDescent (pe , fieldType , w . comparison )
350
359
w2 .lhs = lhs
351
360
w2 .rhs = rhs
352
361
errs = append (errs , w2 .compare (pe .String )... )
353
- if w2 .out != nil {
354
- out [key ] = * w2 .out
355
- }
356
362
w .finishDescent (w2 )
357
363
return errs
358
364
}
@@ -364,10 +370,6 @@ func (w *compareWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs V
364
370
errs = append (errs , w .visitMapItem (t , out , key , lhsValue , rhsValue )... )
365
371
return true
366
372
})
367
- if len (out ) > 0 {
368
- i := interface {}(out )
369
- w .out = & i
370
- }
371
373
372
374
return errs
373
375
}
0 commit comments