1 package jsonschema
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/url"
8 "strings"
9
10 jptr "github.com/qri-io/jsonpointer"
11 )
12
13
14 type SchemaURI string
15
16
17 func NewSchemaURI() Keyword {
18 return new(SchemaURI)
19 }
20
21
22 func (s *SchemaURI) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
23 schemaDebug("[SchemaURI] Validating")
24 }
25
26
27 func (s *SchemaURI) Register(uri string, registry *SchemaRegistry) {}
28
29
30 func (s *SchemaURI) Resolve(pointer jptr.Pointer, uri string) *Schema {
31 return nil
32 }
33
34
35 type ID string
36
37
38 func NewID() Keyword {
39 return new(ID)
40 }
41
42
43 func (i *ID) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
44 schemaDebug("[Id] Validating")
45
46 }
47
48
49 func (i *ID) Register(uri string, registry *SchemaRegistry) {}
50
51
52 func (i *ID) Resolve(pointer jptr.Pointer, uri string) *Schema {
53 return nil
54 }
55
56
57 type Description string
58
59
60 func NewDescription() Keyword {
61 return new(Description)
62 }
63
64
65 func (d *Description) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
66 schemaDebug("[Description] Validating")
67 }
68
69
70 func (d *Description) Register(uri string, registry *SchemaRegistry) {}
71
72
73 func (d *Description) Resolve(pointer jptr.Pointer, uri string) *Schema {
74 return nil
75 }
76
77
78 type Title string
79
80
81 func NewTitle() Keyword {
82 return new(Title)
83 }
84
85
86 func (t *Title) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
87 schemaDebug("[Title] Validating")
88 }
89
90
91 func (t *Title) Register(uri string, registry *SchemaRegistry) {}
92
93
94 func (t *Title) Resolve(pointer jptr.Pointer, uri string) *Schema {
95 return nil
96 }
97
98
99 type Comment string
100
101
102 func NewComment() Keyword {
103 return new(Comment)
104 }
105
106
107 func (c *Comment) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
108 schemaDebug("[Comment] Validating")
109 }
110
111
112 func (c *Comment) Register(uri string, registry *SchemaRegistry) {}
113
114
115 func (c *Comment) Resolve(pointer jptr.Pointer, uri string) *Schema {
116 return nil
117 }
118
119
120 type Default struct {
121 data interface{}
122 }
123
124
125 func NewDefault() Keyword {
126 return &Default{}
127 }
128
129
130 func (d *Default) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
131 schemaDebug("[Default] Validating")
132 }
133
134
135 func (d *Default) Register(uri string, registry *SchemaRegistry) {}
136
137
138 func (d *Default) Resolve(pointer jptr.Pointer, uri string) *Schema {
139 return nil
140 }
141
142
143 func (d *Default) UnmarshalJSON(data []byte) error {
144 var defaultData interface{}
145 if err := json.Unmarshal(data, &defaultData); err != nil {
146 return err
147 }
148 *d = Default{
149 data: defaultData,
150 }
151 return nil
152 }
153
154
155 type Examples []interface{}
156
157
158 func NewExamples() Keyword {
159 return new(Examples)
160 }
161
162
163 func (e *Examples) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
164 schemaDebug("[Examples] Validating")
165 }
166
167
168 func (e *Examples) Register(uri string, registry *SchemaRegistry) {}
169
170
171 func (e *Examples) Resolve(pointer jptr.Pointer, uri string) *Schema {
172 return nil
173 }
174
175
176 type ReadOnly bool
177
178
179 func NewReadOnly() Keyword {
180 return new(ReadOnly)
181 }
182
183
184 func (r *ReadOnly) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
185 schemaDebug("[ReadOnly] Validating")
186 }
187
188
189 func (r *ReadOnly) Register(uri string, registry *SchemaRegistry) {}
190
191
192 func (r *ReadOnly) Resolve(pointer jptr.Pointer, uri string) *Schema {
193 return nil
194 }
195
196
197 type WriteOnly bool
198
199
200 func NewWriteOnly() Keyword {
201 return new(WriteOnly)
202 }
203
204
205 func (w *WriteOnly) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
206 schemaDebug("[WriteOnly] Validating")
207 }
208
209
210 func (w *WriteOnly) Register(uri string, registry *SchemaRegistry) {}
211
212
213 func (w *WriteOnly) Resolve(pointer jptr.Pointer, uri string) *Schema {
214 return nil
215 }
216
217
218 type Ref struct {
219 reference string
220 resolved *Schema
221 resolvedRoot *Schema
222 resolvedFragment *jptr.Pointer
223 fragmentLocalized bool
224 }
225
226
227 func NewRef() Keyword {
228 return new(Ref)
229 }
230
231
232 func (r *Ref) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
233 schemaDebug("[Ref] Validating")
234 if r.resolved == nil {
235 r._resolveRef(ctx, currentState)
236 if r.resolved == nil {
237 currentState.AddError(data, fmt.Sprintf("failed to resolve schema for ref %s", r.reference))
238 }
239 }
240
241 subState := currentState.NewSubState()
242 subState.ClearState()
243 if r.resolvedRoot != nil {
244 subState.BaseURI = r.resolvedRoot.docPath
245 subState.Root = r.resolvedRoot
246 }
247 if r.resolvedFragment != nil && !r.resolvedFragment.IsEmpty() {
248 subState.BaseRelativeLocation = r.resolvedFragment
249 }
250 subState.DescendRelative("$ref")
251
252 r.resolved.ValidateKeyword(ctx, subState, data)
253
254 currentState.UpdateEvaluatedPropsAndItems(subState)
255 }
256
257
258 func (r *Ref) _resolveRef(ctx context.Context, currentState *ValidationState) {
259 if IsLocalSchemaID(r.reference) {
260 r.resolved = currentState.LocalRegistry.GetLocal(r.reference)
261 if r.resolved != nil {
262 return
263 }
264 }
265
266 docPath := currentState.BaseURI
267 refParts := strings.Split(r.reference, "#")
268 address := ""
269 if refParts != nil && len(strings.TrimSpace(refParts[0])) > 0 {
270 address = refParts[0]
271 } else if docPath != "" {
272 docPathParts := strings.Split(docPath, "#")
273 address = docPathParts[0]
274 }
275 if len(refParts) > 1 {
276 frag := refParts[1]
277 if len(frag) > 0 && frag[0] != '/' {
278 frag = "/" + frag
279 r.fragmentLocalized = true
280 }
281 fragPointer, err := jptr.Parse(frag)
282 if err != nil {
283 r.resolvedFragment = &jptr.Pointer{}
284 } else {
285 r.resolvedFragment = &fragPointer
286 }
287 } else {
288 r.resolvedFragment = &jptr.Pointer{}
289 }
290
291 if address != "" {
292 if u, err := url.Parse(address); err == nil {
293 if !u.IsAbs() {
294 address = currentState.Local.id + address
295 if docPath != "" {
296 uriFolder := ""
297 if docPath[len(docPath)-1] == '/' {
298 uriFolder = docPath
299 } else {
300 corePath := strings.Split(docPath, "#")[0]
301 pathComponents := strings.Split(corePath, "/")
302 pathComponents = pathComponents[:len(pathComponents)-1]
303 uriFolder = strings.Join(pathComponents, "/") + "/"
304 }
305 address, _ = SafeResolveURL(uriFolder, address)
306 }
307 }
308 }
309 r.resolvedRoot = GetSchemaRegistry().Get(ctx, address)
310 } else {
311 r.resolvedRoot = currentState.Root
312 }
313
314 if r.resolvedRoot == nil {
315 return
316 }
317
318 knownSchema := GetSchemaRegistry().GetKnown(r.reference)
319 if knownSchema != nil {
320 r.resolved = knownSchema
321 return
322 }
323
324 localURI := currentState.BaseURI
325 if r.resolvedRoot != nil && r.resolvedRoot.docPath != "" {
326 localURI = r.resolvedRoot.docPath
327 if r.fragmentLocalized && !r.resolvedFragment.IsEmpty() {
328 current := r.resolvedFragment.Head()
329 sch := currentState.LocalRegistry.GetLocal("#" + *current)
330 if sch != nil {
331 r.resolved = sch
332 return
333 }
334 }
335 }
336 r._resolveLocalRef(localURI)
337 }
338
339
340 func (r *Ref) _resolveLocalRef(uri string) {
341 if r.resolvedFragment.IsEmpty() {
342 r.resolved = r.resolvedRoot
343 return
344 }
345
346 if r.resolvedRoot != nil {
347 r.resolved = r.resolvedRoot.Resolve(*r.resolvedFragment, uri)
348 }
349 }
350
351
352 func (r *Ref) Register(uri string, registry *SchemaRegistry) {}
353
354
355 func (r *Ref) Resolve(pointer jptr.Pointer, uri string) *Schema {
356 return nil
357 }
358
359
360 func (r *Ref) UnmarshalJSON(data []byte) error {
361 var ref string
362 if err := json.Unmarshal(data, &ref); err != nil {
363 return err
364 }
365 normalizedRef, _ := url.QueryUnescape(ref)
366 *r = Ref{
367 reference: normalizedRef,
368 }
369 return nil
370 }
371
372
373 func (r Ref) MarshalJSON() ([]byte, error) {
374 return json.Marshal(r.reference)
375 }
376
377
378 type RecursiveRef struct {
379 reference string
380 resolved *Schema
381 resolvedRoot *Schema
382 resolvedFragment *jptr.Pointer
383
384 validatingLocations map[string]bool
385 }
386
387
388 func NewRecursiveRef() Keyword {
389 return new(RecursiveRef)
390 }
391
392
393 func (r *RecursiveRef) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
394 schemaDebug("[RecursiveRef] Validating")
395 if r.isLocationVisited(currentState.InstanceLocation.String()) {
396
397 return
398 }
399
400 if r.resolved == nil {
401 r._resolveRef(ctx, currentState)
402 if r.resolved == nil {
403 currentState.AddError(data, fmt.Sprintf("failed to resolve schema for ref %s", r.reference))
404 }
405 }
406
407 subState := currentState.NewSubState()
408 subState.ClearState()
409 if r.resolvedRoot != nil {
410 subState.BaseURI = r.resolvedRoot.docPath
411 subState.Root = r.resolvedRoot
412 }
413 if r.resolvedFragment != nil && !r.resolvedFragment.IsEmpty() {
414 subState.BaseRelativeLocation = r.resolvedFragment
415 }
416 subState.DescendRelative("$recursiveRef")
417
418 if r.validatingLocations == nil {
419 r.validatingLocations = map[string]bool{}
420 }
421
422 r.validatingLocations[currentState.InstanceLocation.String()] = true
423 r.resolved.ValidateKeyword(ctx, subState, data)
424 r.validatingLocations[currentState.InstanceLocation.String()] = false
425
426 currentState.UpdateEvaluatedPropsAndItems(subState)
427 }
428
429 func (r *RecursiveRef) isLocationVisited(location string) bool {
430 if r.validatingLocations == nil {
431 return false
432 }
433 v, ok := r.validatingLocations[location]
434 if !ok {
435 return false
436 }
437 return v
438 }
439
440
441 func (r *RecursiveRef) _resolveRef(ctx context.Context, currentState *ValidationState) {
442 if currentState.RecursiveAnchor != nil {
443 if currentState.BaseURI == "" {
444 currentState.AddError(nil, fmt.Sprintf("base uri not set"))
445 return
446 }
447 baseSchema := GetSchemaRegistry().Get(ctx, currentState.BaseURI)
448 if baseSchema != nil && baseSchema.HasKeyword("$recursiveAnchor") {
449 r.resolvedRoot = currentState.RecursiveAnchor
450 }
451 }
452
453 if IsLocalSchemaID(r.reference) {
454 r.resolved = currentState.LocalRegistry.GetLocal(r.reference)
455 if r.resolved != nil {
456 return
457 }
458 }
459
460 docPath := currentState.BaseURI
461 if r.resolvedRoot != nil && r.resolvedRoot.docPath != "" {
462 docPath = r.resolvedRoot.docPath
463 }
464
465 refParts := strings.Split(r.reference, "#")
466 address := ""
467 if refParts != nil && len(strings.TrimSpace(refParts[0])) > 0 {
468 address = refParts[0]
469 } else {
470 address = docPath
471 }
472
473 if len(refParts) > 1 {
474
475 fragPointer, err := jptr.Parse(refParts[1])
476 if err != nil {
477 r.resolvedFragment = &jptr.Pointer{}
478 } else {
479 r.resolvedFragment = &fragPointer
480 }
481 } else {
482 r.resolvedFragment = &jptr.Pointer{}
483 }
484
485 if r.resolvedRoot == nil {
486 if address != "" {
487 if u, err := url.Parse(address); err == nil {
488 if !u.IsAbs() {
489 address = currentState.Local.id + address
490 if docPath != "" {
491 uriFolder := ""
492 if docPath[len(docPath)-1] == '/' {
493 uriFolder = docPath
494 } else {
495 corePath := strings.Split(docPath, "#")[0]
496 pathComponents := strings.Split(corePath, "/")
497 pathComponents = pathComponents[:len(pathComponents)-1]
498 uriFolder = strings.Join(pathComponents, "/")
499 }
500 address, _ = SafeResolveURL(uriFolder, address)
501 }
502 }
503 }
504 r.resolvedRoot = GetSchemaRegistry().Get(ctx, address)
505 } else {
506 r.resolvedRoot = currentState.Root
507 }
508 }
509
510 if r.resolvedRoot == nil {
511 return
512 }
513
514 knownSchema := GetSchemaRegistry().GetKnown(r.reference)
515 if knownSchema != nil {
516 r.resolved = knownSchema
517 return
518 }
519
520 localURI := currentState.BaseURI
521 if r.resolvedRoot != nil && r.resolvedRoot.docPath != "" {
522 localURI = r.resolvedRoot.docPath
523 }
524 r._resolveLocalRef(localURI)
525 }
526
527
528 func (r *RecursiveRef) _resolveLocalRef(uri string) {
529 if r.resolvedFragment.IsEmpty() {
530 r.resolved = r.resolvedRoot
531 return
532 }
533
534 if r.resolvedRoot != nil {
535 r.resolved = r.resolvedRoot.Resolve(*r.resolvedFragment, uri)
536 }
537 }
538
539
540 func (r *RecursiveRef) Register(uri string, registry *SchemaRegistry) {}
541
542
543 func (r *RecursiveRef) Resolve(pointer jptr.Pointer, uri string) *Schema {
544 return nil
545 }
546
547
548 func (r *RecursiveRef) UnmarshalJSON(data []byte) error {
549 var ref string
550 if err := json.Unmarshal(data, &ref); err != nil {
551 return err
552 }
553 *r = RecursiveRef{
554 reference: ref,
555 }
556 return nil
557 }
558
559
560 func (r RecursiveRef) MarshalJSON() ([]byte, error) {
561 return json.Marshal(r.reference)
562 }
563
564
565 type Anchor string
566
567
568 func NewAnchor() Keyword {
569 return new(Anchor)
570 }
571
572
573 func (a *Anchor) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
574 schemaDebug("[Anchor] Validating")
575 }
576
577
578 func (a *Anchor) Register(uri string, registry *SchemaRegistry) {}
579
580
581 func (a *Anchor) Resolve(pointer jptr.Pointer, uri string) *Schema {
582 return nil
583 }
584
585
586 type RecursiveAnchor Schema
587
588
589 func NewRecursiveAnchor() Keyword {
590 return &RecursiveAnchor{}
591 }
592
593
594 func (r *RecursiveAnchor) Register(uri string, registry *SchemaRegistry) {
595 (*Schema)(r).Register(uri, registry)
596 }
597
598
599 func (r *RecursiveAnchor) Resolve(pointer jptr.Pointer, uri string) *Schema {
600 return (*Schema)(r).Resolve(pointer, uri)
601 }
602
603
604 func (r *RecursiveAnchor) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
605 schemaDebug("[RecursiveAnchor] Validating")
606 if currentState.RecursiveAnchor == nil {
607 currentState.RecursiveAnchor = currentState.Local
608 }
609 }
610
611
612 func (r *RecursiveAnchor) UnmarshalJSON(data []byte) error {
613 sch := &Schema{}
614 if err := json.Unmarshal(data, sch); err != nil {
615 return err
616 }
617 *r = (RecursiveAnchor)(*sch)
618 return nil
619 }
620
621
622 type Defs map[string]*Schema
623
624
625 func NewDefs() Keyword {
626 return &Defs{}
627 }
628
629
630 func (d *Defs) Register(uri string, registry *SchemaRegistry) {
631 for _, v := range *d {
632 v.Register(uri, registry)
633 }
634 }
635
636
637 func (d *Defs) Resolve(pointer jptr.Pointer, uri string) *Schema {
638 if pointer == nil {
639 return nil
640 }
641 current := pointer.Head()
642 if current == nil {
643 return nil
644 }
645
646 if schema, ok := (*d)[*current]; ok {
647 return schema.Resolve(pointer.Tail(), uri)
648 }
649
650 return nil
651 }
652
653
654 func (d Defs) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
655 schemaDebug("[Defs] Validating")
656 }
657
658
659 func (d Defs) JSONProp(name string) interface{} {
660 return d[name]
661 }
662
663
664 func (d Defs) JSONChildren() (res map[string]JSONPather) {
665 res = map[string]JSONPather{}
666 for key, sch := range d {
667 res[key] = sch
668 }
669 return
670 }
671
672
673 type Void struct{}
674
675
676 func NewVoid() Keyword {
677 return &Void{}
678 }
679
680
681 func (vo *Void) Register(uri string, registry *SchemaRegistry) {}
682
683
684 func (vo *Void) Resolve(pointer jptr.Pointer, uri string) *Schema {
685 return nil
686 }
687
688
689 func (vo *Void) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
690 schemaDebug("[Void] Validating")
691 schemaDebug("[Void] WARNING this is a placeholder and should not be used")
692 schemaDebug("[Void] Void is always true")
693 }
694
View as plain text