1
16
17 package typed
18
19 import (
20 "fmt"
21 "sync"
22
23 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
24 "sigs.k8s.io/structured-merge-diff/v4/schema"
25 )
26
27 var fmPool = sync.Pool{
28 New: func() interface{} { return &reconcileWithSchemaWalker{} },
29 }
30
31 func (v *reconcileWithSchemaWalker) finished() {
32 v.fieldSet = nil
33 v.schema = nil
34 v.value = nil
35 v.typeRef = schema.TypeRef{}
36 v.path = nil
37 v.toRemove = nil
38 v.toAdd = nil
39 fmPool.Put(v)
40 }
41
42 type reconcileWithSchemaWalker struct {
43 value *TypedValue
44 schema *schema.Schema
45
46
47 fieldSet *fieldpath.Set
48 typeRef schema.TypeRef
49 path fieldpath.Path
50 isAtomic bool
51
52
53 toRemove *fieldpath.Set
54 toAdd *fieldpath.Set
55
56
57 spareWalkers *[]*reconcileWithSchemaWalker
58 }
59
60 func (v *reconcileWithSchemaWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *reconcileWithSchemaWalker {
61 if v.spareWalkers == nil {
62
63 v.spareWalkers = &[]*reconcileWithSchemaWalker{}
64 }
65 var v2 *reconcileWithSchemaWalker
66 if n := len(*v.spareWalkers); n > 0 {
67 v2, *v.spareWalkers = (*v.spareWalkers)[n-1], (*v.spareWalkers)[:n-1]
68 } else {
69 v2 = &reconcileWithSchemaWalker{}
70 }
71 *v2 = *v
72 v2.typeRef = tr
73 v2.path = append(v.path, pe)
74 v2.value = v.value
75 return v2
76 }
77
78 func (v *reconcileWithSchemaWalker) finishDescent(v2 *reconcileWithSchemaWalker) {
79 v2.fieldSet = nil
80 v2.schema = nil
81 v2.value = nil
82 v2.typeRef = schema.TypeRef{}
83 if cap(v2.path) < 20 {
84 v2.path = v2.path[:0]
85 } else {
86 v2.path = nil
87 }
88
89
90 if v2.toRemove != nil {
91 if v.toRemove == nil {
92 v.toRemove = v2.toRemove
93 } else {
94 v.toRemove = v.toRemove.Union(v2.toRemove)
95 }
96 }
97 if v2.toAdd != nil {
98 if v.toAdd == nil {
99 v.toAdd = v2.toAdd
100 } else {
101 v.toAdd = v.toAdd.Union(v2.toAdd)
102 }
103 }
104 v2.toRemove = nil
105 v2.toAdd = nil
106
107
108
109 *v.spareWalkers = append(*v.spareWalkers, v2)
110 }
111
112
113
114
115
116
117
118
119 func ReconcileFieldSetWithSchema(fieldset *fieldpath.Set, tv *TypedValue) (*fieldpath.Set, error) {
120 v := fmPool.Get().(*reconcileWithSchemaWalker)
121 v.fieldSet = fieldset
122 v.value = tv
123
124 v.schema = tv.schema
125 v.typeRef = tv.typeRef
126
127 defer v.finished()
128 errs := v.reconcile()
129
130 if len(errs) > 0 {
131 return nil, fmt.Errorf("errors reconciling field set with schema: %s", errs.Error())
132 }
133
134
135 if v.toAdd != nil || v.toRemove != nil {
136 out := v.fieldSet
137 if v.toRemove != nil {
138 out = out.RecursiveDifference(v.toRemove)
139 }
140 if v.toAdd != nil {
141 out = out.Union(v.toAdd)
142 }
143 return out, nil
144 }
145 return nil, nil
146 }
147
148 func (v *reconcileWithSchemaWalker) reconcile() (errs ValidationErrors) {
149 a, ok := v.schema.Resolve(v.typeRef)
150 if !ok {
151 errs = append(errs, errorf("could not resolve %v", v.typeRef)...)
152 return
153 }
154 return handleAtom(a, v.typeRef, v)
155 }
156
157 func (v *reconcileWithSchemaWalker) doScalar(_ *schema.Scalar) (errs ValidationErrors) {
158 return errs
159 }
160
161 func (v *reconcileWithSchemaWalker) visitListItems(t *schema.List, element *fieldpath.Set) (errs ValidationErrors) {
162 handleElement := func(pe fieldpath.PathElement, isMember bool) {
163 var hasChildren bool
164 v2 := v.prepareDescent(pe, t.ElementType)
165 v2.fieldSet, hasChildren = element.Children.Get(pe)
166 v2.isAtomic = isMember && !hasChildren
167 errs = append(errs, v2.reconcile()...)
168 v.finishDescent(v2)
169 }
170 element.Children.Iterate(func(pe fieldpath.PathElement) {
171 if element.Members.Has(pe) {
172 return
173 }
174 handleElement(pe, false)
175 })
176 element.Members.Iterate(func(pe fieldpath.PathElement) {
177 handleElement(pe, true)
178 })
179 return errs
180 }
181
182 func (v *reconcileWithSchemaWalker) doList(t *schema.List) (errs ValidationErrors) {
183
184
185
186
187
188
189 if !v.isAtomic && t.ElementRelationship == schema.Atomic {
190 v.toRemove = fieldpath.NewSet(v.path)
191 v.toAdd = fieldpath.NewSet(v.path)
192 return errs
193 }
194 if v.fieldSet != nil {
195 errs = v.visitListItems(t, v.fieldSet)
196 }
197 return errs
198 }
199
200 func (v *reconcileWithSchemaWalker) visitMapItems(t *schema.Map, element *fieldpath.Set) (errs ValidationErrors) {
201 handleElement := func(pe fieldpath.PathElement, isMember bool) {
202 var hasChildren bool
203 if tr, ok := typeRefAtPath(t, pe); ok {
204 v2 := v.prepareDescent(pe, tr)
205 v2.fieldSet, hasChildren = element.Children.Get(pe)
206 v2.isAtomic = isMember && !hasChildren
207 errs = append(errs, v2.reconcile()...)
208 v.finishDescent(v2)
209 }
210 }
211 element.Children.Iterate(func(pe fieldpath.PathElement) {
212 if element.Members.Has(pe) {
213 return
214 }
215 handleElement(pe, false)
216 })
217 element.Members.Iterate(func(pe fieldpath.PathElement) {
218 handleElement(pe, true)
219 })
220
221 return errs
222 }
223
224 func (v *reconcileWithSchemaWalker) doMap(t *schema.Map) (errs ValidationErrors) {
225
226
227 if isUntypedDeducedMap(t) {
228 return errs
229 }
230
231
232
233
234
235
236
237 if !v.isAtomic && t.ElementRelationship == schema.Atomic {
238 if v.fieldSet != nil && v.fieldSet.Size() > 0 {
239 v.toRemove = fieldpath.NewSet(v.path)
240 v.toAdd = fieldpath.NewSet(v.path)
241 }
242 return errs
243 }
244 if v.fieldSet != nil {
245 errs = v.visitMapItems(t, v.fieldSet)
246 }
247 return errs
248 }
249
250 func fieldSetAtPath(node *fieldpath.Set, path fieldpath.Path) (*fieldpath.Set, bool) {
251 ok := true
252 for _, pe := range path {
253 if node, ok = node.Children.Get(pe); !ok {
254 break
255 }
256 }
257 return node, ok
258 }
259
260 func descendToPath(node *fieldpath.Set, path fieldpath.Path) *fieldpath.Set {
261 for _, pe := range path {
262 node = node.Children.Descend(pe)
263 }
264 return node
265 }
266
267 func typeRefAtPath(t *schema.Map, pe fieldpath.PathElement) (schema.TypeRef, bool) {
268 tr := t.ElementType
269 if pe.FieldName != nil {
270 if sf, ok := t.FindField(*pe.FieldName); ok {
271 tr = sf.Type
272 }
273 }
274 return tr, tr != schema.TypeRef{}
275 }
276
277
278
279
280 func isUntypedDeducedMap(m *schema.Map) bool {
281 return isUntypedDeducedRef(m.ElementType) && m.Fields == nil
282 }
283
284 func isUntypedDeducedRef(t schema.TypeRef) bool {
285 if t.NamedType != nil {
286 return *t.NamedType == "__untyped_deduced_"
287 }
288 atom := t.Inlined
289 return atom.Scalar != nil && *atom.Scalar == "untyped"
290 }
291
View as plain text