1
16
17 package typed
18
19 import (
20 "fmt"
21 "strings"
22
23 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
24 "sigs.k8s.io/structured-merge-diff/v4/schema"
25 "sigs.k8s.io/structured-merge-diff/v4/value"
26 )
27
28
29
30
31
32 type Comparison struct {
33
34
35 Removed *fieldpath.Set
36
37 Modified *fieldpath.Set
38
39 Added *fieldpath.Set
40 }
41
42
43
44 func (c *Comparison) IsSame() bool {
45 return c.Removed.Empty() && c.Modified.Empty() && c.Added.Empty()
46 }
47
48
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
64
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
75 type compareWalker struct {
76 lhs value.Value
77 rhs value.Value
78 schema *schema.Schema
79 typeRef schema.TypeRef
80
81
82 path fieldpath.Path
83
84
85 comparison *Comparison
86
87
88 inLeaf bool
89
90
91 spareWalkers *[]*compareWalker
92
93 allocator value.Allocator
94 }
95
96
97 func (w *compareWalker) compare(prefixFn func() string) (errs ValidationErrors) {
98 if w.lhs == nil && w.rhs == nil {
99
100 return errorf("at least one of lhs and rhs must be provided")
101 }
102 a, ok := w.schema.Resolve(w.typeRef)
103 if !ok {
104 return errorf("schema error: no type found matching: %v", *w.typeRef.NamedType)
105 }
106
107 alhs := deduceAtom(a, w.lhs)
108 arhs := deduceAtom(a, w.rhs)
109
110
111
112 if w.rhs == nil {
113 errs = append(errs, handleAtom(alhs, w.typeRef, w)...)
114 } else if w.lhs == nil || alhs.Equals(&arhs) {
115 errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
116 } else {
117 w2 := *w
118 errs = append(errs, handleAtom(alhs, w.typeRef, &w2)...)
119 errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
120 }
121
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 }
128 }
129 return errs.WithLazyPrefix(prefixFn)
130 }
131
132
133
134 func (w *compareWalker) doLeaf() {
135 if w.inLeaf {
136
137
138 return
139 }
140 w.inLeaf = true
141
142
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
149
150 w.comparison.Modified.Insert(w.path)
151 }
152 }
153
154 func (w *compareWalker) doScalar(t *schema.Scalar) ValidationErrors {
155
156 lerrs := validateScalar(t, w.lhs, "lhs: ")
157 rerrs := validateScalar(t, w.rhs, "rhs: ")
158 if len(lerrs) > 0 && len(rerrs) > 0 {
159 return append(lerrs, rerrs...)
160 }
161
162
163 w.doLeaf()
164
165 return nil
166 }
167
168 func (w *compareWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef, cmp *Comparison) *compareWalker {
169 if w.spareWalkers == nil {
170
171 w.spareWalkers = &[]*compareWalker{}
172 }
173 var w2 *compareWalker
174 if n := len(*w.spareWalkers); n > 0 {
175 w2, *w.spareWalkers = (*w.spareWalkers)[n-1], (*w.spareWalkers)[:n-1]
176 } else {
177 w2 = &compareWalker{}
178 }
179 *w2 = *w
180 w2.typeRef = tr
181 w2.path = append(w2.path, pe)
182 w2.lhs = nil
183 w2.rhs = nil
184 w2.comparison = cmp
185 return w2
186 }
187
188 func (w *compareWalker) finishDescent(w2 *compareWalker) {
189
190
191 w.path = w2.path[:len(w2.path)-1]
192 *w.spareWalkers = append(*w.spareWalkers, w2)
193 }
194
195 func (w *compareWalker) derefMap(prefix string, v value.Value) (value.Map, ValidationErrors) {
196 if v == nil {
197 return nil, nil
198 }
199 m, err := mapValue(w.allocator, v)
200 if err != nil {
201 return nil, errorf("%v: %v", prefix, err)
202 }
203 return m, nil
204 }
205
206 func (w *compareWalker) visitListItems(t *schema.List, lhs, rhs value.List) (errs ValidationErrors) {
207 rLen := 0
208 if rhs != nil {
209 rLen = rhs.Length()
210 }
211 lLen := 0
212 if lhs != nil {
213 lLen = lhs.Length()
214 }
215
216 maxLength := rLen
217 if lLen > maxLength {
218 maxLength = lLen
219 }
220
221
222 allPEs := make([]fieldpath.PathElement, 0, maxLength)
223
224
225 lValues := fieldpath.MakePathElementMap(lLen)
226 for i := 0; i < lLen; i++ {
227 child := lhs.At(i)
228 pe, err := listItemToPathElement(w.allocator, w.schema, t, child)
229 if err != nil {
230 errs = append(errs, errorf("element %v: %v", i, err.Error())...)
231
232
233
234 continue
235 }
236
237 if v, found := lValues.Get(pe); found {
238 list := v.([]value.Value)
239 lValues.Insert(pe, append(list, child))
240 } else {
241 lValues.Insert(pe, []value.Value{child})
242 allPEs = append(allPEs, pe)
243 }
244 }
245
246
247 rValues := fieldpath.MakePathElementMap(rLen)
248 for i := 0; i < rLen; i++ {
249 rValue := rhs.At(i)
250 pe, err := listItemToPathElement(w.allocator, w.schema, t, rValue)
251 if err != nil {
252 errs = append(errs, errorf("element %v: %v", i, err.Error())...)
253
254
255
256 continue
257 }
258 if v, found := rValues.Get(pe); found {
259 list := v.([]value.Value)
260 rValues.Insert(pe, append(list, rValue))
261 } else {
262 rValues.Insert(pe, []value.Value{rValue})
263 if _, found := lValues.Get(pe); !found {
264 allPEs = append(allPEs, pe)
265 }
266 }
267 }
268
269 for _, pe := range allPEs {
270 lList := []value.Value(nil)
271 if l, ok := lValues.Get(pe); ok {
272 lList = l.([]value.Value)
273 }
274 rList := []value.Value(nil)
275 if l, ok := rValues.Get(pe); ok {
276 rList = l.([]value.Value)
277 }
278
279 switch {
280 case len(lList) == 0 && len(rList) == 0:
281
282 return
283
284
285 case len(lList) <= 1 && len(rList) <= 1:
286 lValue := value.Value(nil)
287 if len(lList) != 0 {
288 lValue = lList[0]
289 }
290 rValue := value.Value(nil)
291 if len(rList) != 0 {
292 rValue = rList[0]
293 }
294 errs = append(errs, w.compareListItem(t, pe, lValue, rValue)...)
295
296
297 case len(lList) >= 2 && len(rList) >= 2:
298 listEqual := func(lList, rList []value.Value) bool {
299 if len(lList) != len(rList) {
300 return false
301 }
302 for i := range lList {
303 if !value.Equals(lList[i], rList[i]) {
304 return false
305 }
306 }
307 return true
308 }
309 if !listEqual(lList, rList) {
310 w.comparison.Modified.Insert(append(w.path, pe))
311 }
312
313
314 case len(lList) >= 2:
315 if len(rList) != 0 {
316 errs = append(errs, w.compareListItem(t, pe, nil, rList[0])...)
317 }
318 w.comparison.Removed.Insert(append(w.path, pe))
319
320
321 case len(rList) >= 2:
322 if len(lList) != 0 {
323 errs = append(errs, w.compareListItem(t, pe, lList[0], nil)...)
324 }
325 w.comparison.Added.Insert(append(w.path, pe))
326 }
327 }
328
329 return
330 }
331
332 func (w *compareWalker) indexListPathElements(t *schema.List, list value.List) ([]fieldpath.PathElement, fieldpath.PathElementValueMap, ValidationErrors) {
333 var errs ValidationErrors
334 length := 0
335 if list != nil {
336 length = list.Length()
337 }
338 observed := fieldpath.MakePathElementValueMap(length)
339 pes := make([]fieldpath.PathElement, 0, length)
340 for i := 0; i < length; i++ {
341 child := list.At(i)
342 pe, err := listItemToPathElement(w.allocator, w.schema, t, child)
343 if err != nil {
344 errs = append(errs, errorf("element %v: %v", i, err.Error())...)
345
346
347
348 continue
349 }
350
351 if _, found := observed.Get(pe); found {
352 continue
353 }
354 observed.Insert(pe, child)
355 pes = append(pes, pe)
356 }
357 return pes, observed, errs
358 }
359
360 func (w *compareWalker) compareListItem(t *schema.List, pe fieldpath.PathElement, lChild, rChild value.Value) ValidationErrors {
361 w2 := w.prepareDescent(pe, t.ElementType, w.comparison)
362 w2.lhs = lChild
363 w2.rhs = rChild
364 errs := w2.compare(pe.String)
365 w.finishDescent(w2)
366 return errs
367 }
368
369 func (w *compareWalker) derefList(prefix string, v value.Value) (value.List, ValidationErrors) {
370 if v == nil {
371 return nil, nil
372 }
373 l, err := listValue(w.allocator, v)
374 if err != nil {
375 return nil, errorf("%v: %v", prefix, err)
376 }
377 return l, nil
378 }
379
380 func (w *compareWalker) doList(t *schema.List) (errs ValidationErrors) {
381 lhs, _ := w.derefList("lhs: ", w.lhs)
382 if lhs != nil {
383 defer w.allocator.Free(lhs)
384 }
385 rhs, _ := w.derefList("rhs: ", w.rhs)
386 if rhs != nil {
387 defer w.allocator.Free(rhs)
388 }
389
390
391
392
393 emptyPromoteToLeaf := (lhs == nil || lhs.Length() == 0) && (rhs == nil || rhs.Length() == 0)
394
395 if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
396 w.doLeaf()
397 return nil
398 }
399
400 if lhs == nil && rhs == nil {
401 return nil
402 }
403
404 errs = w.visitListItems(t, lhs, rhs)
405
406 return errs
407 }
408
409 func (w *compareWalker) visitMapItem(t *schema.Map, out map[string]interface{}, key string, lhs, rhs value.Value) (errs ValidationErrors) {
410 fieldType := t.ElementType
411 if sf, ok := t.FindField(key); ok {
412 fieldType = sf.Type
413 }
414 pe := fieldpath.PathElement{FieldName: &key}
415 w2 := w.prepareDescent(pe, fieldType, w.comparison)
416 w2.lhs = lhs
417 w2.rhs = rhs
418 errs = append(errs, w2.compare(pe.String)...)
419 w.finishDescent(w2)
420 return errs
421 }
422
423 func (w *compareWalker) visitMapItems(t *schema.Map, lhs, rhs value.Map) (errs ValidationErrors) {
424 out := map[string]interface{}{}
425
426 value.MapZipUsing(w.allocator, lhs, rhs, value.Unordered, func(key string, lhsValue, rhsValue value.Value) bool {
427 errs = append(errs, w.visitMapItem(t, out, key, lhsValue, rhsValue)...)
428 return true
429 })
430
431 return errs
432 }
433
434 func (w *compareWalker) doMap(t *schema.Map) (errs ValidationErrors) {
435 lhs, _ := w.derefMap("lhs: ", w.lhs)
436 if lhs != nil {
437 defer w.allocator.Free(lhs)
438 }
439 rhs, _ := w.derefMap("rhs: ", w.rhs)
440 if rhs != nil {
441 defer w.allocator.Free(rhs)
442 }
443
444
445
446 emptyPromoteToLeaf := (lhs == nil || lhs.Empty()) && (rhs == nil || rhs.Empty())
447
448 if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
449 w.doLeaf()
450 return nil
451 }
452
453 if lhs == nil && rhs == nil {
454 return nil
455 }
456
457 errs = append(errs, w.visitMapItems(t, lhs, rhs)...)
458
459 return errs
460 }
461
View as plain text