1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package jsonschema
16
17 import (
18 "fmt"
19 "math/big"
20 "path"
21 "regexp"
22
23 "cuelang.org/go/cue"
24 "cuelang.org/go/cue/ast"
25 "cuelang.org/go/cue/errors"
26 "cuelang.org/go/cue/token"
27 "cuelang.org/go/internal"
28 )
29
30
31
32
33 type constraint struct {
34 key string
35
36
37
38
39
40
41 phase int
42
43
44 draft int
45 fn constraintFunc
46 }
47
48
49
50 type constraintFunc func(n cue.Value, s *state)
51
52 func p0(name string, f constraintFunc) *constraint {
53 return &constraint{key: name, fn: f}
54 }
55
56 func p1d(name string, draft int, f constraintFunc) *constraint {
57 return &constraint{key: name, phase: 1, draft: draft, fn: f}
58 }
59
60 func p1(name string, f constraintFunc) *constraint {
61 return &constraint{key: name, phase: 1, fn: f}
62 }
63
64 func p2(name string, f constraintFunc) *constraint {
65 return &constraint{key: name, phase: 2, fn: f}
66 }
67
68 func p3(name string, f constraintFunc) *constraint {
69 return &constraint{key: name, phase: 3, fn: f}
70 }
71
72
73
74
75 var constraintMap = map[string]*constraint{}
76
77 func init() {
78 for _, c := range constraints {
79 constraintMap[c.key] = c
80 }
81 }
82
83 func addDefinitions(n cue.Value, s *state) {
84 if n.Kind() != cue.StructKind {
85 s.errf(n, `"definitions" expected an object, found %s`, n.Kind())
86 }
87
88 old := s.isSchema
89 s.isSchema = true
90 defer func() { s.isSchema = old }()
91
92 s.processMap(n, func(key string, n cue.Value) {
93 name := key
94
95 var f *ast.Field
96
97 ident := "#" + name
98 if ast.IsValidIdent(ident) {
99 f = &ast.Field{Value: s.schema(n, label{ident, true})}
100 f.Label = ast.NewIdent(ident)
101 } else {
102 f = &ast.Field{Value: s.schema(n, label{"#", true}, label{name: name})}
103 f.Label = ast.NewString(name)
104 ident = "#"
105 f = &ast.Field{
106 Label: ast.NewIdent("#"),
107 Value: ast.NewStruct(f),
108 }
109 }
110
111 ast.SetRelPos(f, token.NewSection)
112 s.definitions = append(s.definitions, f)
113 s.setField(label{name: ident, isDef: true}, f)
114 })
115 }
116
117 var constraints = []*constraint{
118
119
120 p0("$schema", func(n cue.Value, s *state) {
121
122
123 s.jsonschema, _ = s.strValue(n)
124 }),
125
126 p0("$id", func(n cue.Value, s *state) {
127
128
129
130
131
132
133
134 u := s.resolveURI(n)
135 if u == nil {
136 return
137 }
138
139 if u.Fragment != "" {
140 if s.cfg.Strict {
141 s.errf(n, "$id URI may not contain a fragment")
142 }
143 return
144 }
145 s.id = u
146
147 obj := s.object(n)
148
149
150
151 obj.Elts = append(obj.Elts, &ast.Attribute{
152 Text: fmt.Sprintf("@jsonschema(id=%q)", u)})
153 }),
154
155
156
157 p1("type", func(n cue.Value, s *state) {
158 var types cue.Kind
159 set := func(n cue.Value) {
160 str, ok := s.strValue(n)
161 if !ok {
162 s.errf(n, "type value should be a string")
163 }
164 switch str {
165 case "null":
166 types |= cue.NullKind
167 s.setTypeUsed(n, nullType)
168
169 case "boolean":
170 types |= cue.BoolKind
171 s.setTypeUsed(n, boolType)
172 case "string":
173 types |= cue.StringKind
174 s.setTypeUsed(n, stringType)
175 case "number":
176 types |= cue.NumberKind
177 s.setTypeUsed(n, numType)
178 case "integer":
179 types |= cue.IntKind
180 s.setTypeUsed(n, numType)
181 s.add(n, numType, ast.NewIdent("int"))
182 case "array":
183 types |= cue.ListKind
184 s.setTypeUsed(n, arrayType)
185 case "object":
186 types |= cue.StructKind
187 s.setTypeUsed(n, objectType)
188
189 default:
190 s.errf(n, "unknown type %q", n)
191 }
192 }
193
194 switch n.Kind() {
195 case cue.StringKind:
196 set(n)
197 case cue.ListKind:
198 for i, _ := n.List(); i.Next(); {
199 set(i.Value())
200 }
201 default:
202 s.errf(n, `value of "type" must be a string or list of strings`)
203 }
204
205 s.allowedTypes &= types
206 }),
207
208 p1("enum", func(n cue.Value, s *state) {
209 var a []ast.Expr
210 for _, x := range s.listItems("enum", n, true) {
211 a = append(a, s.value(x))
212 }
213 s.all.add(n, ast.NewBinExpr(token.OR, a...))
214 }),
215
216
217 p1("nullable", func(n cue.Value, s *state) {
218 null := ast.NewNull()
219 setPos(null, n)
220 s.nullable = null
221 }),
222
223 p1d("const", 6, func(n cue.Value, s *state) {
224 s.all.add(n, s.value(n))
225 }),
226
227 p1("default", func(n cue.Value, s *state) {
228 sc := *s
229 s.default_ = sc.value(n)
230
231
232 s.examples = append(s.examples, s.default_)
233 }),
234
235 p1("deprecated", func(n cue.Value, s *state) {
236 if s.boolValue(n) {
237 s.deprecated = true
238 }
239 }),
240
241 p1("examples", func(n cue.Value, s *state) {
242 if n.Kind() != cue.ListKind {
243 s.errf(n, `value of "examples" must be an array, found %v`, n.Kind())
244 }
245
246
247
248
249
250
251 }),
252
253 p1("description", func(n cue.Value, s *state) {
254 s.description, _ = s.strValue(n)
255 }),
256
257 p1("title", func(n cue.Value, s *state) {
258 s.title, _ = s.strValue(n)
259 }),
260
261 p1d("$comment", 7, func(n cue.Value, s *state) {
262 }),
263
264 p1("$defs", addDefinitions),
265 p1("definitions", addDefinitions),
266 p1("$ref", func(n cue.Value, s *state) {
267 s.usedTypes = allTypes
268
269 u := s.resolveURI(n)
270
271 if u.Fragment != "" && !path.IsAbs(u.Fragment) {
272 s.addErr(errors.Newf(n.Pos(), "anchors (%s) not supported", u.Fragment))
273
274 return
275 }
276
277 expr := s.makeCUERef(n, u)
278
279 if expr == nil {
280 expr = &ast.BadExpr{From: n.Pos()}
281 }
282
283 s.all.add(n, expr)
284 }),
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 p2("allOf", func(n cue.Value, s *state) {
307 var a []ast.Expr
308 for _, v := range s.listItems("allOf", n, false) {
309 x, sub := s.schemaState(v, s.allowedTypes, nil, true)
310 s.allowedTypes &= sub.allowedTypes
311 s.usedTypes |= sub.usedTypes
312 if sub.hasConstraints() {
313 a = append(a, x)
314 }
315 }
316 if len(a) > 0 {
317 s.all.add(n, ast.NewBinExpr(token.AND, a...))
318 }
319 }),
320
321 p2("anyOf", func(n cue.Value, s *state) {
322 var types cue.Kind
323 var a []ast.Expr
324 for _, v := range s.listItems("anyOf", n, false) {
325 x, sub := s.schemaState(v, s.allowedTypes, nil, true)
326 types |= sub.allowedTypes
327 a = append(a, x)
328 }
329 s.allowedTypes &= types
330 if len(a) > 0 {
331 s.all.add(n, ast.NewBinExpr(token.OR, a...))
332 }
333 }),
334
335 p2("oneOf", func(n cue.Value, s *state) {
336 var types cue.Kind
337 var a []ast.Expr
338 hasSome := false
339 for _, v := range s.listItems("oneOf", n, false) {
340 x, sub := s.schemaState(v, s.allowedTypes, nil, true)
341 types |= sub.allowedTypes
342
343
344 if sub.hasConstraints() {
345 hasSome = true
346 }
347
348 if !isAny(x) {
349 a = append(a, x)
350 }
351 }
352 s.allowedTypes &= types
353 if len(a) > 0 && hasSome {
354 s.usedTypes = allTypes
355 s.all.add(n, ast.NewBinExpr(token.OR, a...))
356 }
357
358
359
360 }),
361
362
363
364 p1("pattern", func(n cue.Value, s *state) {
365 str, _ := n.String()
366 if _, err := regexp.Compile(str); err != nil {
367 if s.cfg.Strict {
368 s.errf(n, "unsupported regexp: %v", err)
369 }
370 return
371 }
372 s.usedTypes |= cue.StringKind
373 s.add(n, stringType, &ast.UnaryExpr{Op: token.MAT, X: s.string(n)})
374 }),
375
376 p1("minLength", func(n cue.Value, s *state) {
377 s.usedTypes |= cue.StringKind
378 min := s.number(n)
379 strings := s.addImport(n, "strings")
380 s.add(n, stringType, ast.NewCall(ast.NewSel(strings, "MinRunes"), min))
381 }),
382
383 p1("maxLength", func(n cue.Value, s *state) {
384 s.usedTypes |= cue.StringKind
385 max := s.number(n)
386 strings := s.addImport(n, "strings")
387 s.add(n, stringType, ast.NewCall(ast.NewSel(strings, "MaxRunes"), max))
388 }),
389
390 p1d("contentMediaType", 7, func(n cue.Value, s *state) {
391
392
393 }),
394
395 p1d("contentEncoding", 7, func(n cue.Value, s *state) {
396
397
398
399
400
401
402 }),
403
404
405
406 p2("minimum", func(n cue.Value, s *state) {
407 s.usedTypes |= cue.NumberKind
408 op := token.GEQ
409 if s.exclusiveMin {
410 op = token.GTR
411 }
412 s.add(n, numType, &ast.UnaryExpr{Op: op, X: s.number(n)})
413 }),
414
415 p1("exclusiveMinimum", func(n cue.Value, s *state) {
416 if n.Kind() == cue.BoolKind {
417 s.exclusiveMin = true
418 return
419 }
420 s.usedTypes |= cue.NumberKind
421 s.add(n, numType, &ast.UnaryExpr{Op: token.GTR, X: s.number(n)})
422 }),
423
424 p2("maximum", func(n cue.Value, s *state) {
425 s.usedTypes |= cue.NumberKind
426 op := token.LEQ
427 if s.exclusiveMax {
428 op = token.LSS
429 }
430 s.add(n, numType, &ast.UnaryExpr{Op: op, X: s.number(n)})
431 }),
432
433 p1("exclusiveMaximum", func(n cue.Value, s *state) {
434 if n.Kind() == cue.BoolKind {
435 s.exclusiveMax = true
436 return
437 }
438 s.usedTypes |= cue.NumberKind
439 s.add(n, numType, &ast.UnaryExpr{Op: token.LSS, X: s.number(n)})
440 }),
441
442 p1("multipleOf", func(n cue.Value, s *state) {
443 s.usedTypes |= cue.NumberKind
444 multiple := s.number(n)
445 var x big.Int
446 _, _ = n.MantExp(&x)
447 if x.Cmp(big.NewInt(0)) != 1 {
448 s.errf(n, `"multipleOf" value must be < 0; found %s`, n)
449 }
450 math := s.addImport(n, "math")
451 s.add(n, numType, ast.NewCall(ast.NewSel(math, "MultipleOf"), multiple))
452 }),
453
454
455
456 p1("properties", func(n cue.Value, s *state) {
457 s.usedTypes |= cue.StructKind
458 obj := s.object(n)
459
460 if n.Kind() != cue.StructKind {
461 s.errf(n, `"properties" expected an object, found %v`, n.Kind())
462 }
463
464 s.processMap(n, func(key string, n cue.Value) {
465
466 name := ast.NewString(key)
467 expr, state := s.schemaState(n, allTypes, []label{{name: key}}, false)
468 f := &ast.Field{Label: name, Value: expr}
469 state.doc(f)
470 f.Optional = token.Blank.Pos()
471 if len(obj.Elts) > 0 && len(f.Comments()) > 0 {
472
473
474 ast.SetRelPos(f.Comments()[0], token.NewSection)
475 }
476 if state.deprecated {
477 switch expr.(type) {
478 case *ast.StructLit:
479 obj.Elts = append(obj.Elts, addTag(name, "deprecated", ""))
480 default:
481 f.Attrs = append(f.Attrs, internal.NewAttr("deprecated", ""))
482 }
483 }
484 obj.Elts = append(obj.Elts, f)
485 s.setField(label{name: key}, f)
486 })
487 }),
488
489 p2("required", func(n cue.Value, s *state) {
490 if n.Kind() != cue.ListKind {
491 s.errf(n, `value of "required" must be list of strings, found %v`, n.Kind())
492 return
493 }
494
495 s.usedTypes |= cue.StructKind
496
497
498
499 obj := s.object(n)
500
501
502 fields := map[string]*ast.Field{}
503 for _, d := range obj.Elts {
504 f, ok := d.(*ast.Field)
505 if !ok {
506 continue
507 }
508 str, _, err := ast.LabelName(f.Label)
509 if err == nil {
510 fields[str] = f
511 }
512 }
513
514 for _, n := range s.listItems("required", n, true) {
515 str, ok := s.strValue(n)
516 f := fields[str]
517 if f == nil && ok {
518 f := &ast.Field{
519 Label: ast.NewString(str),
520 Value: ast.NewIdent("_"),
521 }
522 fields[str] = f
523 obj.Elts = append(obj.Elts, f)
524 continue
525 }
526 if f.Optional == token.NoPos {
527 s.errf(n, "duplicate required field %q", str)
528 }
529 f.Optional = token.NoPos
530 }
531 }),
532
533 p1d("propertyNames", 6, func(n cue.Value, s *state) {
534
535 if names, _ := s.schemaState(n, cue.StringKind, nil, false); !isAny(names) {
536 s.usedTypes |= cue.StructKind
537 x := ast.NewStruct(ast.NewList(names), ast.NewIdent("_"))
538 s.add(n, objectType, x)
539 }
540 }),
541
542
543
544
545
546
547
548
549
550 p1("maxProperties", func(n cue.Value, s *state) {
551 s.usedTypes |= cue.StructKind
552
553 pkg := s.addImport(n, "struct")
554 x := ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n))
555 s.add(n, objectType, x)
556 }),
557
558 p1("dependencies", func(n cue.Value, s *state) {
559 s.usedTypes |= cue.StructKind
560
561
562
563
564
565
571 }),
572
573 p2("patternProperties", func(n cue.Value, s *state) {
574 s.usedTypes |= cue.StructKind
575 if n.Kind() != cue.StructKind {
576 s.errf(n, `value of "patternProperties" must be an object, found %v`, n.Kind())
577 }
578 obj := s.object(n)
579 existing := excludeFields(s.obj.Elts)
580 s.processMap(n, func(key string, n cue.Value) {
581
582 s.patterns = append(s.patterns,
583 &ast.UnaryExpr{Op: token.NMAT, X: ast.NewString(key)})
584 f := internal.EmbedStruct(ast.NewStruct(&ast.Field{
585 Label: ast.NewList(ast.NewBinExpr(token.AND,
586 &ast.UnaryExpr{Op: token.MAT, X: ast.NewString(key)},
587 existing)),
588 Value: s.schema(n),
589 }))
590 ast.SetRelPos(f, token.NewSection)
591 obj.Elts = append(obj.Elts, f)
592 })
593 }),
594
595 p3("additionalProperties", func(n cue.Value, s *state) {
596 switch n.Kind() {
597 case cue.BoolKind:
598 s.closeStruct = !s.boolValue(n)
599
600 case cue.StructKind:
601 s.usedTypes |= cue.StructKind
602 s.closeStruct = true
603 obj := s.object(n)
604 if len(obj.Elts) == 0 {
605 obj.Elts = append(obj.Elts, &ast.Field{
606 Label: ast.NewList(ast.NewIdent("string")),
607 Value: s.schema(n),
608 })
609 return
610 }
611
612 existing := append(s.patterns, excludeFields(obj.Elts))
613 f := internal.EmbedStruct(ast.NewStruct(&ast.Field{
614 Label: ast.NewList(ast.NewBinExpr(token.AND, existing...)),
615 Value: s.schema(n),
616 }))
617 obj.Elts = append(obj.Elts, f)
618
619 default:
620 s.errf(n, `value of "additionalProperties" must be an object or boolean`)
621 }
622 }),
623
624
625
626 p1("items", func(n cue.Value, s *state) {
627 s.usedTypes |= cue.ListKind
628 switch n.Kind() {
629 case cue.StructKind:
630 elem := s.schema(n)
631 ast.SetRelPos(elem, token.NoRelPos)
632 s.add(n, arrayType, ast.NewList(&ast.Ellipsis{Type: elem}))
633
634 case cue.ListKind:
635 var a []ast.Expr
636 for _, n := range s.listItems("items", n, true) {
637 v := s.schema(n)
638 ast.SetRelPos(v, token.NoRelPos)
639 a = append(a, v)
640 }
641 s.list = ast.NewList(a...)
642 s.add(n, arrayType, s.list)
643
644 default:
645 s.errf(n, `value of "items" must be an object or array`)
646 }
647 }),
648
649 p1("additionalItems", func(n cue.Value, s *state) {
650 switch n.Kind() {
651 case cue.BoolKind:
652
653
654 case cue.StructKind:
655 if s.list != nil {
656 s.usedTypes |= cue.ListKind
657 elem := s.schema(n)
658 s.list.Elts = append(s.list.Elts, &ast.Ellipsis{Type: elem})
659 }
660
661 default:
662 s.errf(n, `value of "additionalItems" must be an object or boolean`)
663 }
664 }),
665
666 p1("contains", func(n cue.Value, s *state) {
667 s.usedTypes |= cue.ListKind
668 list := s.addImport(n, "list")
669
670 if x := s.schema(n); !isAny(x) {
671 x := ast.NewCall(ast.NewSel(list, "Contains"), clearPos(x))
672 s.add(n, arrayType, x)
673 }
674 }),
675
676
677
678 p1("minItems", func(n cue.Value, s *state) {
679 s.usedTypes |= cue.ListKind
680 a := []ast.Expr{}
681 p, err := n.Uint64()
682 if err != nil {
683 s.errf(n, "invalid uint")
684 }
685 for ; p > 0; p-- {
686 a = append(a, ast.NewIdent("_"))
687 }
688 s.add(n, arrayType, ast.NewList(append(a, &ast.Ellipsis{})...))
689
690
691
692
693 }),
694
695 p1("maxItems", func(n cue.Value, s *state) {
696 s.usedTypes |= cue.ListKind
697 list := s.addImport(n, "list")
698 x := ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n)))
699 s.add(n, arrayType, x)
700
701 }),
702
703 p1("uniqueItems", func(n cue.Value, s *state) {
704 s.usedTypes |= cue.ListKind
705 if s.boolValue(n) {
706 list := s.addImport(n, "list")
707 s.add(n, arrayType, ast.NewCall(ast.NewSel(list, "UniqueItems")))
708 }
709 }),
710 }
711
712 func clearPos(e ast.Expr) ast.Expr {
713 ast.SetRelPos(e, token.NoRelPos)
714 return e
715 }
716
View as plain text