1
16
17 package typed_test
18
19 import (
20 "fmt"
21 "strings"
22 "testing"
23
24 "sigs.k8s.io/structured-merge-diff/v4/schema"
25 "sigs.k8s.io/structured-merge-diff/v4/typed"
26 )
27
28 type validationTestCase struct {
29 name string
30 rootTypeName string
31 schema typed.YAMLObject
32 validObjects []typed.YAMLObject
33 invalidObjects []typed.YAMLObject
34
35 duplicatesObjects []typed.YAMLObject
36 }
37
38 var validationCases = []validationTestCase{{
39 name: "simple pair",
40 rootTypeName: "stringPair",
41 schema: `types:
42 - name: stringPair
43 map:
44 fields:
45 - name: key
46 type:
47 scalar: string
48 - name: value
49 type:
50 namedType: __untyped_atomic_
51 - name: __untyped_atomic_
52 scalar: untyped
53 list:
54 elementType:
55 namedType: __untyped_atomic_
56 elementRelationship: atomic
57 map:
58 elementType:
59 namedType: __untyped_atomic_
60 elementRelationship: atomic
61 `,
62 validObjects: []typed.YAMLObject{
63 `{"key":"foo","value":1}`,
64 `{"key":"foo","value":{}}`,
65 `{"key":"foo","value":null}`,
66 `{"key":"foo"}`,
67 `{"key":"foo","value":true}`,
68 `{"key":null}`,
69 },
70 invalidObjects: []typed.YAMLObject{
71 `{"key":true,"value":1}`,
72 `{"key":1,"value":{}}`,
73 `{"key":false,"value":null}`,
74 `{"key":[1, 2]}`,
75 `{"key":{"foo":true}}`,
76 },
77 }, {
78 name: "struct grab bag",
79 rootTypeName: "myStruct",
80 schema: `types:
81 - name: myStruct
82 map:
83 fields:
84 - name: numeric
85 type:
86 scalar: numeric
87 - name: string
88 type:
89 scalar: string
90 - name: bool
91 type:
92 scalar: boolean
93 - name: setStr
94 type:
95 list:
96 elementType:
97 scalar: string
98 elementRelationship: associative
99 - name: setBool
100 type:
101 list:
102 elementType:
103 scalar: boolean
104 elementRelationship: associative
105 - name: setNumeric
106 type:
107 list:
108 elementType:
109 scalar: numeric
110 elementRelationship: associative
111 `,
112 validObjects: []typed.YAMLObject{
113 `{"numeric":null}`,
114 `{"numeric":1}`,
115 `{"numeric":3.14159}`,
116 `{"string":null}`,
117 `{"string":"aoeu"}`,
118 `{"bool":null}`,
119 `{"bool":true}`,
120 `{"bool":false}`,
121 `{"setStr":["a","b","c"]}`,
122 `{"setBool":[true,false]}`,
123 `{"setNumeric":[1,2,3,3.14159]}`,
124 },
125 invalidObjects: []typed.YAMLObject{
126 `{"numeric":["foo"]}`,
127 `{"numeric":{"a":1}}`,
128 `{"numeric":"foo"}`,
129 `{"numeric":true}`,
130 `{"string":1}`,
131 `{"string":3.5}`,
132 `{"string":true}`,
133 `{"string":{"a":1}}`,
134 `{"string":["foo"]}`,
135 `{"bool":1}`,
136 `{"bool":3.5}`,
137 `{"bool":"aoeu"}`,
138 `{"bool":{"a":1}}`,
139 `{"bool":["foo"]}`,
140 `{"setStr":[1]}`,
141 `{"setStr":[true]}`,
142 `{"setStr":[1.5]}`,
143 `{"setStr":[null]}`,
144 `{"setStr":[{}]}`,
145 `{"setStr":[[]]}`,
146 `{"setBool":[1]}`,
147 `{"setBool":[1.5]}`,
148 `{"setBool":[null]}`,
149 `{"setBool":[{}]}`,
150 `{"setBool":[[]]}`,
151 `{"setBool":["a"]}`,
152 `{"setNumeric":[null]}`,
153 `{"setNumeric":[true]}`,
154 `{"setNumeric":["a"]}`,
155 `{"setNumeric":[[]]}`,
156 `{"setNumeric":[{}]}`,
157 }, duplicatesObjects: []typed.YAMLObject{
158 `{"setStr":["a","a"]}`,
159 `{"setBool":[true,false,true]}`,
160 `{"setNumeric":[1,2,3,3.14159,1]}`,
161 },
162 }, {
163 name: "associative list",
164 rootTypeName: "myRoot",
165 schema: `types:
166 - name: myRoot
167 map:
168 fields:
169 - name: list
170 type:
171 namedType: myList
172 - name: atomicList
173 type:
174 namedType: mySequence
175 - name: myList
176 list:
177 elementType:
178 namedType: myElement
179 elementRelationship: associative
180 keys:
181 - key
182 - id
183 - name: mySequence
184 list:
185 elementType:
186 scalar: string
187 elementRelationship: atomic
188 - name: myElement
189 map:
190 fields:
191 - name: key
192 type:
193 scalar: string
194 - name: id
195 type:
196 scalar: numeric
197 - name: value
198 type:
199 namedType: myValue
200 - name: bv
201 type:
202 scalar: boolean
203 - name: nv
204 type:
205 scalar: numeric
206 - name: myValue
207 map:
208 elementType:
209 scalar: string
210 `,
211 validObjects: []typed.YAMLObject{
212 `{"list":[]}`,
213 `{"list":[{"key":"a","id":1,"value":{"a":"a"}}]}`,
214 `{"list":[{"key":"a","id":1},{"key":"a","id":2},{"key":"b","id":1}]}`,
215 `{"atomicList":["a","a","a"]}`,
216 },
217 invalidObjects: []typed.YAMLObject{
218 `{"key":true,"value":1}`,
219 `{"list":{"key":true,"value":1}}`,
220 `{"list":true}`,
221 `true`,
222 `{"list":[{"key":true,"value":1}]}`,
223 `{"list":[{"key":[],"value":1}]}`,
224 `{"list":[{"key":{},"value":1}]}`,
225 `{"list":[{"key":1.5,"value":1}]}`,
226 `{"list":[{"key":1,"value":1}]}`,
227 `{"list":[{"key":null,"value":1}]}`,
228 `{"list":[{},{}]}`,
229 `{"list":[{},null]}`,
230 `{"list":[[]]}`,
231 `{"list":[null]}`,
232 `{"list":[{}]}`,
233 `{"list":[{"value":{"a":"a"},"bv":true,"nv":3.14}]}`,
234 `{"list":[{"key":"a","id":1,"value":{"a":1}}]}`,
235 `{"list":[{"key":"a","id":1,"value":{"a":"a"},"bv":"true","nv":3.14}]}`,
236 `{"list":[{"key":"a","id":1,"value":{"a":"a"},"bv":true,"nv":false}]}`,
237 }, duplicatesObjects: []typed.YAMLObject{
238 `{"list":[{"key":"a","id":1},{"key":"a","id":1}]}`,
239 },
240 }}
241
242 func (tt validationTestCase) test(t *testing.T) {
243 parser, err := typed.NewParser(tt.schema)
244 if err != nil {
245 t.Fatalf("failed to create schema: %v", err)
246 }
247 pt := parser.Type(tt.rootTypeName)
248
249 for i, v := range tt.validObjects {
250 v := v
251 t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) {
252 t.Parallel()
253 _, err := pt.FromYAML(v)
254 if err != nil {
255 t.Errorf("failed to parse/validate yaml: %v\n%v", err, v)
256 }
257 })
258 }
259
260 for i, iv := range tt.invalidObjects {
261 iv := iv
262 t.Run(fmt.Sprintf("%v-invalid-%v", tt.name, i), func(t *testing.T) {
263 t.Parallel()
264 _, err := pt.FromYAML(iv)
265 if err == nil {
266 t.Fatalf("Object should fail:\n%v", iv)
267 }
268 if strings.Contains(err.Error(), "invalid atom") {
269 t.Errorf("Error should be useful, but got: %v\n%v", err, iv)
270 }
271 })
272 }
273 for i, iv := range tt.duplicatesObjects {
274 iv := iv
275 t.Run(fmt.Sprintf("%v-duplicates-%v", tt.name, i), func(t *testing.T) {
276 t.Parallel()
277 _, err := pt.FromYAML(iv)
278 if err == nil {
279 t.Fatalf("Object should fail:\n%v", iv)
280 }
281 if strings.Contains(err.Error(), "invalid atom") {
282 t.Errorf("Error should be useful, but got: %v\n%v", err, iv)
283 }
284 _, err = pt.FromYAML(iv, typed.AllowDuplicates)
285 if err != nil {
286 t.Errorf("failed to parse/validate yaml: %v\n%v", err, iv)
287 }
288 })
289 }
290 }
291
292 func TestSchemaValidation(t *testing.T) {
293 for _, tt := range validationCases {
294 tt := tt
295 t.Run(tt.name, func(t *testing.T) {
296 t.Parallel()
297 tt.test(t)
298 })
299 }
300 }
301
302 func TestSchemaSchema(t *testing.T) {
303
304 _, err := typed.NewParser(typed.YAMLObject(schema.SchemaSchemaYAML))
305 if err != nil {
306 t.Fatalf("failed to create schemaschema: %v", err)
307 }
308 }
309
310 func BenchmarkValidateStructured(b *testing.B) {
311 type Primitives struct {
312 s string
313 i int64
314 f float64
315 b bool
316 }
317
318 primitive1 := Primitives{s: "string1"}
319 primitive2 := Primitives{i: 100}
320 primitive3 := Primitives{f: 3.14}
321 primitive4 := Primitives{b: true}
322
323 type Example struct {
324 listOfPrimitives []Primitives
325 mapOfPrimitives map[string]Primitives
326 mapOfLists map[string][]Primitives
327 }
328
329 tests := []struct {
330 name string
331 rootTypeName string
332 schema typed.YAMLObject
333 object interface{}
334 }{
335 {
336 name: "struct",
337 rootTypeName: "example",
338 schema: `types:
339 - name: example
340 map:
341 fields:
342 - name: listOfPrimitives
343 type:
344 list:
345 elementType:
346 namedType: primitives
347 - name: mapOfPrimitives
348 type:
349 map:
350 elementType:
351 namedType: primitives
352 - name: mapOfLists
353 type:
354 map:
355 elementType:
356 list:
357 elementType:
358 namedType: primitives
359 - name: primitives
360 map:
361 fields:
362 - name: s
363 type:
364 scalar: string
365 - name: i
366 type:
367 scalar: numeric
368 - name: f
369 type:
370 scalar: numeric
371 - name: b
372 type:
373 scalar: boolean
374 `,
375 object: &Example{
376 listOfPrimitives: []Primitives{primitive1, primitive2, primitive3, primitive4},
377 mapOfPrimitives: map[string]Primitives{"1": primitive1, "2": primitive2, "3": primitive3, "4": primitive4},
378 mapOfLists: map[string][]Primitives{
379 "1": {primitive1, primitive2, primitive3, primitive4},
380 "2": {primitive1, primitive2, primitive3, primitive4},
381 },
382 },
383 },
384 }
385
386 for _, test := range tests {
387 b.Run(test.name, func(b *testing.B) {
388 parser, err := typed.NewParser(test.schema)
389 if err != nil {
390 b.Fatalf("failed to create schema: %v", err)
391 }
392 pt := parser.Type(test.rootTypeName)
393
394 b.ReportAllocs()
395 for n := 0; n < b.N; n++ {
396 tv, err := pt.FromStructured(test.object)
397 if err != nil {
398 b.Errorf("failed to parse/validate yaml: %v\n%v", err, tv)
399 }
400 }
401 })
402 }
403 }
404
View as plain text