1 package jsonschema
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io/ioutil"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "testing"
13
14 "github.com/sergi/go-diff/diffmatchpatch"
15 )
16
17 func ExampleBasic() {
18 ctx := context.Background()
19 var schemaData = []byte(`{
20 "title": "Person",
21 "type": "object",
22 "$id": "https://qri.io/schema/",
23 "$comment" : "sample comment",
24 "properties": {
25 "firstName": {
26 "type": "string"
27 },
28 "lastName": {
29 "type": "string"
30 },
31 "age": {
32 "description": "Age in years",
33 "type": "integer",
34 "minimum": 0
35 },
36 "friends": {
37 "type" : "array",
38 "items" : { "title" : "REFERENCE", "$ref" : "#" }
39 }
40 },
41 "required": ["firstName", "lastName"]
42 }`)
43
44 rs := &Schema{}
45 if err := json.Unmarshal(schemaData, rs); err != nil {
46 panic("unmarshal schema: " + err.Error())
47 }
48
49 var valid = []byte(`{
50 "firstName" : "George",
51 "lastName" : "Michael"
52 }`)
53 errs, err := rs.ValidateBytes(ctx, valid)
54 if err != nil {
55 panic(err)
56 }
57
58 if len(errs) > 0 {
59 fmt.Println(errs[0].Error())
60 }
61
62 var invalidPerson = []byte(`{
63 "firstName" : "Prince"
64 }`)
65
66 errs, err = rs.ValidateBytes(ctx, invalidPerson)
67 if err != nil {
68 panic(err)
69 }
70 if len(errs) > 0 {
71 fmt.Println(errs[0].Error())
72 }
73
74 var invalidFriend = []byte(`{
75 "firstName" : "Jay",
76 "lastName" : "Z",
77 "friends" : [{
78 "firstName" : "Nas"
79 }]
80 }`)
81 errs, err = rs.ValidateBytes(ctx, invalidFriend)
82 if err != nil {
83 panic(err)
84 }
85 if len(errs) > 0 {
86 fmt.Println(errs[0].Error())
87 }
88
89
90
91 }
92
93 func TestTopLevelType(t *testing.T) {
94 schemaObject := []byte(`{
95 "title": "Car",
96 "type": "object",
97 "properties": {
98 "color": {
99 "type": "string"
100 }
101 },
102 "required": ["color"]
103 }`)
104 rs := &Schema{}
105 if err := json.Unmarshal(schemaObject, rs); err != nil {
106 panic("unmarshal schema: " + err.Error())
107 }
108 if rs.TopLevelType() != "object" {
109 t.Errorf("error: schemaObject should be an object")
110 }
111
112 schemaArray := []byte(`{
113 "title": "Cities",
114 "type": "array",
115 "items" : { "title" : "REFERENCE", "$ref" : "#" }
116 }`)
117 rs = &Schema{}
118 if err := json.Unmarshal(schemaArray, rs); err != nil {
119 panic("unmarshal schema: " + err.Error())
120 }
121 if rs.TopLevelType() != "array" {
122 t.Errorf("error: schemaArray should be an array")
123 }
124
125 schemaUnknown := []byte(`{
126 "title": "Typeless",
127 "items" : { "title" : "REFERENCE", "$ref" : "#" }
128 }`)
129 rs = &Schema{}
130 if err := json.Unmarshal(schemaUnknown, rs); err != nil {
131 panic("unmarshal schema: " + err.Error())
132 }
133 if rs.TopLevelType() != "unknown" {
134 t.Errorf("error: schemaUnknown should have unknown type")
135 }
136 }
137
138 func TestParseUrl(t *testing.T) {
139
140 schemaObject := []byte(`{
141 "title": "Car",
142 "type": "object",
143 "$id": "http://example.com/root.json"
144 }`)
145 rs := &Schema{}
146 if err := json.Unmarshal(schemaObject, rs); err != nil {
147 panic("unmarshal schema: " + err.Error())
148 }
149
150
151 schemaObject = []byte(`{
152 "title": "Car",
153 "type": "object",
154 "$id": "#/properites/firstName"
155 }`)
156 rs = &Schema{}
157 if err := json.Unmarshal(schemaObject, rs); err != nil {
158 panic("unmarshal schema: " + err.Error())
159 }
160
161
162 schemaObject = []byte(`{
163 "title": "Car",
164 "type": "object",
165 "$id": "#"
166 }`)
167 rs = &Schema{}
168 if err := json.Unmarshal(schemaObject, rs); err != nil {
169 panic("unmarshal schema: " + err.Error())
170 }
171 }
172
173 func TestMust(t *testing.T) {
174 defer func() {
175 if r := recover(); r != nil {
176 if err, ok := r.(error); ok {
177 if err.Error() != "unexpected end of JSON input" {
178 t.Errorf("expected panic error to equal: %s", "unexpected end of JSON input")
179 }
180 } else {
181 t.Errorf("must paniced with a non-error")
182 }
183 } else {
184 t.Errorf("expected invalid call to Must to panic")
185 }
186 }()
187
188
189 rs := Must(`{}`)
190 if rs == nil {
191 t.Errorf("expected parse of empty schema to return *RootSchema, got nil")
192 return
193 }
194
195
196 Must(``)
197 }
198
199 func TestDraft3(t *testing.T) {
200 runJSONTests(t, []string{
201 "testdata/draft3/additionalProperties.json",
202 "testdata/draft3/default.json",
203 "testdata/draft3/format.json",
204 "testdata/draft3/items.json",
205 "testdata/draft3/maxItems.json",
206 "testdata/draft3/maxLength.json",
207 "testdata/draft3/minItems.json",
208 "testdata/draft3/minLength.json",
209 "testdata/draft3/pattern.json",
210 "testdata/draft3/patternProperties.json",
211 "testdata/draft3/properties.json",
212 "testdata/draft3/uniqueItems.json",
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 func TestDraft4(t *testing.T) {
239 runJSONTests(t, []string{
240 "testdata/draft4/additionalItems.json",
241 "testdata/draft4/allOf.json",
242 "testdata/draft4/anyOf.json",
243 "testdata/draft4/default.json",
244 "testdata/draft4/enum.json",
245 "testdata/draft4/format.json",
246 "testdata/draft4/maxItems.json",
247 "testdata/draft4/maxLength.json",
248 "testdata/draft4/maxProperties.json",
249 "testdata/draft4/minItems.json",
250 "testdata/draft4/minLength.json",
251 "testdata/draft4/minProperties.json",
252 "testdata/draft4/multipleOf.json",
253 "testdata/draft4/not.json",
254 "testdata/draft4/oneOf.json",
255 "testdata/draft4/optional/format.json",
256 "testdata/draft4/pattern.json",
257 "testdata/draft4/patternProperties.json",
258 "testdata/draft4/properties.json",
259 "testdata/draft4/required.json",
260 "testdata/draft4/type.json",
261 "testdata/draft4/uniqueItems.json",
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 })
280 }
281
282 func TestDraft6(t *testing.T) {
283 runJSONTests(t, []string{
284 "testdata/draft6/additionalItems.json",
285 "testdata/draft6/allOf.json",
286 "testdata/draft6/anyOf.json",
287 "testdata/draft6/boolean_schema.json",
288 "testdata/draft6/const.json",
289 "testdata/draft6/contains.json",
290 "testdata/draft6/default.json",
291 "testdata/draft6/enum.json",
292 "testdata/draft6/exclusiveMaximum.json",
293 "testdata/draft6/exclusiveMinimum.json",
294 "testdata/draft6/format.json",
295 "testdata/draft6/maximum.json",
296 "testdata/draft6/maxItems.json",
297 "testdata/draft6/maxLength.json",
298 "testdata/draft6/maxProperties.json",
299 "testdata/draft6/minimum.json",
300 "testdata/draft6/minItems.json",
301 "testdata/draft6/minLength.json",
302 "testdata/draft6/minProperties.json",
303 "testdata/draft6/multipleOf.json",
304 "testdata/draft6/not.json",
305 "testdata/draft6/oneOf.json",
306 "testdata/draft6/pattern.json",
307 "testdata/draft6/patternProperties.json",
308 "testdata/draft6/properties.json",
309 "testdata/draft6/propertyNames.json",
310 "testdata/draft6/required.json",
311 "testdata/draft6/type.json",
312 "testdata/draft6/uniqueItems.json",
313
314 "testdata/draft6/optional/format.json",
315 "testdata/draft6/optional/zeroTerminatedFloats.json",
316
317
318
319
320
321
322
323
324
325
326
327
328 })
329 }
330
331 func TestDraft7(t *testing.T) {
332 path := "testdata/draft-07_schema.json"
333 data, err := ioutil.ReadFile(path)
334 if err != nil {
335 t.Errorf("error reading %s: %s", path, err.Error())
336 return
337 }
338
339 rsch := &Schema{}
340 if err := json.Unmarshal(data, rsch); err != nil {
341 t.Errorf("error unmarshaling schema: %s", err.Error())
342 return
343 }
344
345 runJSONTests(t, []string{
346 "testdata/draft7/additionalItems.json",
347 "testdata/draft7/allOf.json",
348 "testdata/draft7/anyOf.json",
349 "testdata/draft7/boolean_schema.json",
350 "testdata/draft7/const.json",
351 "testdata/draft7/contains.json",
352 "testdata/draft7/default.json",
353 "testdata/draft7/enum.json",
354 "testdata/draft7/exclusiveMaximum.json",
355 "testdata/draft7/exclusiveMinimum.json",
356 "testdata/draft7/format.json",
357 "testdata/draft7/if-then-else.json",
358 "testdata/draft7/maximum.json",
359 "testdata/draft7/maxItems.json",
360 "testdata/draft7/maxLength.json",
361 "testdata/draft7/maxProperties.json",
362 "testdata/draft7/minimum.json",
363 "testdata/draft7/minItems.json",
364 "testdata/draft7/minLength.json",
365 "testdata/draft7/minProperties.json",
366 "testdata/draft7/multipleOf.json",
367 "testdata/draft7/not.json",
368 "testdata/draft7/oneOf.json",
369 "testdata/draft7/pattern.json",
370 "testdata/draft7/patternProperties.json",
371 "testdata/draft7/properties.json",
372 "testdata/draft7/propertyNames.json",
373 "testdata/draft7/required.json",
374 "testdata/draft7/type.json",
375 "testdata/draft7/uniqueItems.json",
376
377 "testdata/draft7/optional/zeroTerminatedFloats.json",
378 "testdata/draft7/optional/format/date-time.json",
379 "testdata/draft7/optional/format/date.json",
380 "testdata/draft7/optional/format/email.json",
381 "testdata/draft7/optional/format/hostname.json",
382 "testdata/draft7/optional/format/idn-email.json",
383 "testdata/draft7/optional/format/idn-hostname.json",
384 "testdata/draft7/optional/format/ipv4.json",
385 "testdata/draft7/optional/format/ipv6.json",
386 "testdata/draft7/optional/format/iri-reference.json",
387 "testdata/draft7/optional/format/json-pointer.json",
388 "testdata/draft7/optional/format/regex.json",
389 "testdata/draft7/optional/format/relative-json-pointer.json",
390 "testdata/draft7/optional/format/time.json",
391 "testdata/draft7/optional/format/uri-reference.json",
392 "testdata/draft7/optional/format/uri-template.json",
393 "testdata/draft7/optional/format/uri.json",
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 })
409 }
410
411 func TestDraft2019_09(t *testing.T) {
412 path := "testdata/draft2019-09_schema.json"
413 data, err := ioutil.ReadFile(path)
414 if err != nil {
415 t.Errorf("error reading %s: %s", path, err.Error())
416 return
417 }
418
419 rsch := &Schema{}
420 if err := json.Unmarshal(data, rsch); err != nil {
421 t.Errorf("error unmarshaling schema: %s", err.Error())
422 return
423 }
424
425 runJSONTests(t, []string{
426 "testdata/draft2019-09/additionalItems.json",
427
428 "testdata/draft2019-09/allOf.json",
429 "testdata/draft2019-09/anchor.json",
430 "testdata/draft2019-09/anyOf.json",
431 "testdata/draft2019-09/boolean_schema.json",
432 "testdata/draft2019-09/const.json",
433 "testdata/draft2019-09/contains.json",
434 "testdata/draft2019-09/default.json",
435 "testdata/draft2019-09/defs.json",
436 "testdata/draft2019-09/dependentRequired.json",
437 "testdata/draft2019-09/dependentSchemas.json",
438 "testdata/draft2019-09/enum.json",
439 "testdata/draft2019-09/exclusiveMaximum.json",
440 "testdata/draft2019-09/exclusiveMinimum.json",
441 "testdata/draft2019-09/format.json",
442 "testdata/draft2019-09/if-then-else.json",
443 "testdata/draft2019-09/items.json",
444 "testdata/draft2019-09/maximum.json",
445 "testdata/draft2019-09/maxItems.json",
446 "testdata/draft2019-09/maxLength.json",
447 "testdata/draft2019-09/maxProperties.json",
448 "testdata/draft2019-09/minimum.json",
449 "testdata/draft2019-09/minItems.json",
450 "testdata/draft2019-09/minLength.json",
451 "testdata/draft2019-09/minProperties.json",
452 "testdata/draft2019-09/multipleOf.json",
453 "testdata/draft2019-09/not.json",
454 "testdata/draft2019-09/oneOf.json",
455 "testdata/draft2019-09/pattern.json",
456 "testdata/draft2019-09/patternProperties.json",
457 "testdata/draft2019-09/properties.json",
458 "testdata/draft2019-09/propertyNames.json",
459 "testdata/draft2019-09/ref.json",
460 "testdata/draft2019-09/required.json",
461 "testdata/draft2019-09/type.json",
462
463
464 "testdata/draft2019-09/uniqueItems.json",
465
466 "testdata/draft2019-09/optional/zeroTerminatedFloats.json",
467 "testdata/draft2019-09/optional/format/date-time.json",
468 "testdata/draft2019-09/optional/format/date.json",
469 "testdata/draft2019-09/optional/format/email.json",
470 "testdata/draft2019-09/optional/format/hostname.json",
471 "testdata/draft2019-09/optional/format/idn-email.json",
472 "testdata/draft2019-09/optional/format/idn-hostname.json",
473 "testdata/draft2019-09/optional/format/ipv4.json",
474 "testdata/draft2019-09/optional/format/ipv6.json",
475 "testdata/draft2019-09/optional/format/iri-reference.json",
476 "testdata/draft2019-09/optional/format/json-pointer.json",
477 "testdata/draft2019-09/optional/format/regex.json",
478 "testdata/draft2019-09/optional/format/relative-json-pointer.json",
479 "testdata/draft2019-09/optional/format/time.json",
480 "testdata/draft2019-09/optional/format/uri-reference.json",
481 "testdata/draft2019-09/optional/format/uri-template.json",
482 "testdata/draft2019-09/optional/format/uri.json",
483
484
485
486
487 "testdata/draft2019-09/unevaluatedProperties_modified.json",
488 "testdata/draft2019-09/unevaluatedItems_modified.json",
489
490
491
492
493 "testdata/draft2019-09/additionalProperties_modified.json",
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 })
509 }
510
511
512
513
514 type TestSet struct {
515 Description string `json:"description"`
516 Schema *Schema `json:"schema"`
517 Tests []TestCase `json:"tests"`
518 }
519
520 type TestCase struct {
521 Description string `json:"description"`
522 Data interface{} `json:"data"`
523 Valid bool `json:"valid"`
524 }
525
526 func runJSONTests(t *testing.T, testFilepaths []string) {
527 tests := 0
528 passed := 0
529 ctx := context.Background()
530 for _, path := range testFilepaths {
531 t.Run(path, func(t *testing.T) {
532 base := filepath.Base(path)
533 testSets := []*TestSet{}
534 data, err := ioutil.ReadFile(path)
535 if err != nil {
536 t.Errorf("error loading test file: %s", err.Error())
537 return
538 }
539
540 if err := json.Unmarshal(data, &testSets); err != nil {
541 t.Errorf("error unmarshaling test set %s from JSON: %s", base, err.Error())
542 return
543 }
544
545 for _, ts := range testSets {
546 sc := ts.Schema
547 for i, c := range ts.Tests {
548 tests++
549 validationState := sc.Validate(ctx, c.Data)
550 if validationState.IsValid() != c.Valid {
551 t.Errorf("%s: %s test case %d: %s. error: %s", base, ts.Description, i, c.Description, *validationState.Errs)
552 } else {
553 passed++
554 }
555 }
556 }
557 })
558 }
559 t.Logf("%d/%d tests passed", passed, tests)
560 }
561
562 func TestDataType(t *testing.T) {
563 type customObject struct{}
564 type customNumber float64
565
566 cases := []struct {
567 data interface{}
568 expect string
569 }{
570 {nil, "null"},
571 {float64(4), "integer"},
572 {float64(4.0), "integer"},
573 {float64(4.5), "number"},
574 {customNumber(4.5), "number"},
575 {true, "boolean"},
576 {"foo", "string"},
577 {[]interface{}{}, "array"},
578 {[0]interface{}{}, "array"},
579 {map[string]interface{}{}, "object"},
580 {struct{}{}, "object"},
581 {customObject{}, "object"},
582 {int8(42), "unknown"},
583
584 {"true", "boolean"},
585 {4.0, "number"},
586 }
587
588 for i, c := range cases {
589 got := DataTypeWithHint(c.data, c.expect)
590 if got != c.expect {
591 t.Errorf("case %d result mismatch. expected: '%s', got: '%s'", i, c.expect, got)
592 }
593 }
594 }
595
596 func TestJSONCoding(t *testing.T) {
597 cases := []string{
598 "testdata/coding/false.json",
599 "testdata/coding/true.json",
600 "testdata/coding/std.json",
601 "testdata/coding/booleans.json",
602 "testdata/coding/conditionals.json",
603 "testdata/coding/numeric.json",
604 "testdata/coding/objects.json",
605 "testdata/coding/strings.json",
606 }
607
608 for i, c := range cases {
609 data, err := ioutil.ReadFile(c)
610 if err != nil {
611 t.Errorf("case %d error reading file: %s", i, err.Error())
612 continue
613 }
614
615 rs := &Schema{}
616 if err := json.Unmarshal(data, rs); err != nil {
617 t.Errorf("case %d error unmarshaling from json: %s", i, err.Error())
618 continue
619 }
620
621 output, err := json.MarshalIndent(rs, "", " ")
622 if err != nil {
623 t.Errorf("case %d error marshaling to JSON: %s", i, err.Error())
624 continue
625 }
626
627 if !bytes.Equal(data, output) {
628 dmp := diffmatchpatch.New()
629 diffs := dmp.DiffMain(string(data), string(output), true)
630 if len(diffs) == 0 {
631 t.Logf("case %d bytes were unequal but computed no difference between results", i)
632 continue
633 }
634
635 t.Errorf("case %d %s mismatch:\n", i, c)
636 t.Errorf("diff:\n%s", dmp.DiffPrettyText(diffs))
637 t.Errorf("expected:\n%s", string(data))
638 t.Errorf("got:\n%s", string(output))
639 continue
640 }
641 }
642 }
643
644 func TestValidateBytes(t *testing.T) {
645 ctx := context.Background()
646 cases := []struct {
647 schema string
648 input string
649 errors []string
650 }{
651 {`true`, `"just a string yo"`, nil},
652 {`{"type":"array", "items": {"type":"string"}}`,
653 `[1,false,null]`,
654 []string{
655 `/0: 1 type should be string, got integer`,
656 `/1: false type should be string, got boolean`,
657 `/2: type should be string, got null`,
658 }},
659 }
660
661 for i, c := range cases {
662 rs := &Schema{}
663 if err := rs.UnmarshalJSON([]byte(c.schema)); err != nil {
664 t.Errorf("case %d error parsing %s", i, err.Error())
665 continue
666 }
667
668 errors, err := rs.ValidateBytes(ctx, []byte(c.input))
669 if err != nil {
670 t.Errorf("case %d error validating: %s", i, err.Error())
671 continue
672 }
673
674 if len(errors) != len(c.errors) {
675 t.Errorf("case %d: error length mismatch. expected: '%d', got: '%d'", i, len(c.errors), len(errors))
676 t.Errorf("%v", errors)
677 continue
678 }
679
680 for j, e := range errors {
681 if e.Error() != c.errors[j] {
682 t.Errorf("case %d: validation error %d mismatch. expected: '%s', got: '%s'", i, j, c.errors[j], e.Error())
683 continue
684 }
685 }
686 }
687 }
688
689 func BenchmarkAdditionalItems(b *testing.B) {
690 runBenchmark(b,
691 func(sampleSize int) (string, interface{}) {
692 data := make([]interface{}, sampleSize)
693 for i := 0; i < sampleSize; i++ {
694 data[i] = float64(i)
695 }
696 return `{
697 "items": {},
698 "additionalItems": false
699 }`, data
700 },
701 )
702 }
703
704 func BenchmarkAdditionalProperties(b *testing.B) {
705 runBenchmark(b,
706 func(sampleSize int) (string, interface{}) {
707 data := make(map[string]interface{}, sampleSize)
708 for i := 0; i < sampleSize; i++ {
709 p := fmt.Sprintf("p%v", i)
710 data[p] = struct{}{}
711 }
712 d, err := json.Marshal(data)
713 if err != nil {
714 b.Errorf("unable to marshal data: %v", err)
715 return "", nil
716 }
717 return `{
718 "properties": ` + string(d) + `,
719 "additionalProperties": false
720 }`, data
721 },
722 )
723 }
724
725 func BenchmarkConst(b *testing.B) {
726 runBenchmark(b,
727 func(sampleSize int) (string, interface{}) {
728 data := make(map[string]interface{}, sampleSize)
729 for i := 0; i < sampleSize; i++ {
730 data[fmt.Sprintf("p%v", i)] = fmt.Sprintf("p%v", 2*i)
731 }
732 d, err := json.Marshal(data)
733 if err != nil {
734 b.Errorf("unable to marshal data: %v", err)
735 return "", nil
736 }
737 return `{
738 "const": ` + string(d) + `
739 }`, data
740 },
741 )
742 }
743
744 func BenchmarkContains(b *testing.B) {
745 runBenchmark(b,
746 func(sampleSize int) (string, interface{}) {
747 data := make([]interface{}, sampleSize)
748 for i := 0; i < sampleSize; i++ {
749 data[i] = float64(i)
750 }
751 return `{
752 "contains": { "const": ` + strconv.Itoa(sampleSize-1) + ` }
753 }`, data
754 },
755 )
756 }
757
758 func BenchmarkDependencies(b *testing.B) {
759 runBenchmark(b,
760 func(sampleSize int) (string, interface{}) {
761 data := make(map[string]interface{}, sampleSize)
762 deps := []string{}
763 for i := 0; i < sampleSize; i++ {
764 p := fmt.Sprintf("p%v", i)
765 data[p] = fmt.Sprintf("p%v", 2*i)
766 if i != 0 {
767 deps = append(deps, p)
768 }
769 }
770 d, err := json.Marshal(deps)
771 if err != nil {
772 b.Errorf("unable to marshal data: %v", err)
773 return "", nil
774 }
775 return `{
776 "dependencies": {"p0": ` + string(d) + `}
777 }`, data
778 },
779 )
780 }
781
782 func BenchmarkEnum(b *testing.B) {
783 runBenchmark(b,
784 func(sampleSize int) (string, interface{}) {
785 data := make([]interface{}, sampleSize)
786 for i := 0; i < sampleSize; i++ {
787 data[i] = float64(i)
788 }
789 d, err := json.Marshal(data)
790 if err != nil {
791 b.Errorf("unable to marshal data: %v", err)
792 return "", nil
793 }
794 return `{
795 "enum": ` + string(d) + `
796 }`, float64(sampleSize / 2)
797 },
798 )
799 }
800
801 func BenchmarkMaximum(b *testing.B) {
802 runBenchmark(b, func(sampleSize int) (string, interface{}) {
803 return `{
804 "maximum": 3
805 }`, float64(2)
806 })
807 }
808
809 func BenchmarkMinimum(b *testing.B) {
810 runBenchmark(b, func(sampleSize int) (string, interface{}) {
811 return `{
812 "minimum": 3
813 }`, float64(4)
814 })
815 }
816
817 func BenchmarkExclusiveMaximum(b *testing.B) {
818 runBenchmark(b, func(sampleSize int) (string, interface{}) {
819 return `{
820 "exclusiveMaximum": 3
821 }`, float64(2)
822 })
823 }
824
825 func BenchmarkExclusiveMinimum(b *testing.B) {
826 runBenchmark(b, func(sampleSize int) (string, interface{}) {
827 return `{
828 "exclusiveMinimum": 3
829 }`, float64(4)
830 })
831 }
832
833 func BenchmarkMaxItems(b *testing.B) {
834 runBenchmark(b,
835 func(sampleSize int) (string, interface{}) {
836 data := make([]interface{}, sampleSize)
837 for i := 0; i < sampleSize; i++ {
838 data[i] = float64(i)
839 }
840 return `{
841 "maxItems": 10000
842 }`, data
843 },
844 )
845 }
846
847 func BenchmarkMinItems(b *testing.B) {
848 runBenchmark(b,
849 func(sampleSize int) (string, interface{}) {
850 data := make([]interface{}, sampleSize)
851 for i := 0; i < sampleSize; i++ {
852 data[i] = float64(i)
853 }
854 return `{
855 "minItems": 1
856 }`, data
857 },
858 )
859 }
860
861 func BenchmarkMaxLength(b *testing.B) {
862 runBenchmark(b,
863 func(sampleSize int) (string, interface{}) {
864 data := make([]rune, sampleSize)
865 for i := 0; i < sampleSize; i++ {
866 data[i] = 'a'
867 }
868 return `{
869 "maxLength": ` + strconv.Itoa(sampleSize) + `
870 }`, string(data)
871 },
872 )
873 }
874
875 func BenchmarkMinLength(b *testing.B) {
876 runBenchmark(b,
877 func(sampleSize int) (string, interface{}) {
878 data := make([]rune, sampleSize)
879 for i := 0; i < sampleSize; i++ {
880 data[i] = 'a'
881 }
882 return `{
883 "minLength": 1
884 }`, string(data)
885 },
886 )
887 }
888
889 func BenchmarkMaxProperties(b *testing.B) {
890 runBenchmark(b,
891 func(sampleSize int) (string, interface{}) {
892 data := make(map[string]interface{}, sampleSize)
893 for i := 0; i < sampleSize; i++ {
894 data[fmt.Sprintf("p%v", i)] = fmt.Sprintf("p%v", 2*i)
895 }
896 return `{
897 "maxProperties": ` + strconv.Itoa(sampleSize) + `
898 }`, data
899 },
900 )
901 }
902
903 func BenchmarkMinProperties(b *testing.B) {
904 runBenchmark(b,
905 func(sampleSize int) (string, interface{}) {
906 data := make(map[string]interface{}, sampleSize)
907 for i := 0; i < sampleSize; i++ {
908 data[fmt.Sprintf("p%v", i)] = fmt.Sprintf("p%v", 2*i)
909 }
910 return `{
911 "minProperties": 1
912 }`, data
913 },
914 )
915 }
916
917 func BenchmarkMultipleOf(b *testing.B) {
918 runBenchmark(b,
919 func(sampleSize int) (string, interface{}) {
920 return `{
921 "multipleOf": 2
922 }`, float64(42)
923 },
924 )
925 }
926
927 func BenchmarkPattern(b *testing.B) {
928 runBenchmark(b,
929 func(sampleSize int) (string, interface{}) {
930 data := make([]rune, sampleSize)
931 for i := 0; i < sampleSize; i++ {
932 data[i] = 'a'
933 }
934 return `{
935 "pattern": "^a*$"
936 }`, string(data)
937 },
938 )
939 }
940
941 func BenchmarkType(b *testing.B) {
942 runBenchmark(b,
943 func(sampleSize int) (string, interface{}) {
944 data := make(map[string]interface{}, sampleSize)
945 var schema strings.Builder
946
947 for i := 0; i < sampleSize; i++ {
948 propNull := fmt.Sprintf("n%v", nil)
949 propBool := fmt.Sprintf("b%v", i)
950 propInt := fmt.Sprintf("i%v", i)
951 propFloat := fmt.Sprintf("f%v", i)
952 propStr := fmt.Sprintf("s%v", i)
953 propArr := fmt.Sprintf("a%v", i)
954 propObj := fmt.Sprintf("o%v", i)
955
956 data[propBool] = true
957 data[propInt] = float64(42)
958 data[propFloat] = float64(42.5)
959 data[propStr] = "foobar"
960 data[propArr] = []interface{}{interface{}(1), interface{}(2), interface{}(3)}
961 data[propObj] = struct{}{}
962
963 schema.WriteString(fmt.Sprintf(`"%v": { "type": "null" },`, propNull))
964 schema.WriteString(fmt.Sprintf(`"%v": { "type": "boolean" },`, propBool))
965 schema.WriteString(fmt.Sprintf(`"%v": { "type": "integer" },`, propInt))
966 schema.WriteString(fmt.Sprintf(`"%v": { "type": "number" },`, propFloat))
967 schema.WriteString(fmt.Sprintf(`"%v": { "type": "string" },`, propStr))
968 schema.WriteString(fmt.Sprintf(`"%v": { "type": "array" },`, propArr))
969 schema.WriteString(fmt.Sprintf(`"%v": { "type": "object" }`, propObj))
970
971 if i != sampleSize-1 {
972 schema.WriteString(",")
973 }
974 }
975
976 return `{
977 "type": "object",
978 "properties": { ` + schema.String() + ` }
979 }`, data
980 },
981 )
982 }
983
984 func runBenchmark(b *testing.B, dataFn func(sampleSize int) (string, interface{})) {
985 ctx := context.Background()
986 for _, sampleSize := range []int{1, 10, 100, 1000} {
987 b.Run(fmt.Sprintf("sample size %v", sampleSize), func(b *testing.B) {
988 schema, data := dataFn(sampleSize)
989 if data == nil {
990 b.Skip("data == nil, skipping")
991 return
992 }
993
994 var validator Schema
995 if err := json.Unmarshal([]byte(schema), &validator); err != nil {
996 b.Errorf("error parsing schema: %s", err.Error())
997 return
998 }
999
1000 currentState := NewValidationState(&validator)
1001 currentState.ClearState()
1002 b.ResetTimer()
1003 for i := 0; i < b.N; i++ {
1004 validator.ValidateKeyword(ctx, currentState, data)
1005 }
1006 b.StopTimer()
1007
1008 if !currentState.IsValid() {
1009 b.Errorf("error running benchmark: %s", *currentState.Errs)
1010 }
1011 })
1012 }
1013 }
1014
View as plain text