1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package openapi
16
17 import (
18 "fmt"
19 "math"
20 "path"
21 "regexp"
22 "sort"
23 "strings"
24
25 "cuelang.org/go/cue"
26 "cuelang.org/go/cue/ast"
27 "cuelang.org/go/cue/errors"
28 "cuelang.org/go/cue/token"
29 "cuelang.org/go/internal"
30 "cuelang.org/go/internal/core/adt"
31 internalvalue "cuelang.org/go/internal/value"
32 )
33
34 type buildContext struct {
35 inst cue.Value
36 instExt cue.Value
37 refPrefix string
38 path []cue.Selector
39 errs errors.Error
40
41 expandRefs bool
42 structural bool
43 exclusiveBool bool
44 nameFunc func(inst cue.Value, path cue.Path) string
45 descFunc func(v cue.Value) string
46 fieldFilter *regexp.Regexp
47
48 schemas *OrderedMap
49
50
51 externalRefs map[string]*externalType
52
53
54
55
56
57
58
59 cycleNodes []*adt.Vertex
60
61
62
63
64 imports map[cue.Value]*cue.Instance
65 }
66
67 type externalType struct {
68 ref string
69 inst cue.Value
70 path cue.Path
71 value cue.Value
72 }
73
74 type oaSchema = OrderedMap
75
76 type typeFunc func(b *builder, a cue.Value)
77
78 func schemas(g *Generator, inst cue.InstanceOrValue) (schemas *ast.StructLit, err error) {
79 val := inst.Value()
80 _, isInstance := inst.(*cue.Instance)
81 var fieldFilter *regexp.Regexp
82 if g.FieldFilter != "" {
83 fieldFilter, err = regexp.Compile(g.FieldFilter)
84 if err != nil {
85 return nil, errors.Newf(token.NoPos, "invalid field filter: %v", err)
86 }
87
88
89 for _, f := range strings.Split(
90 "version,title,allOf,anyOf,not,enum,Schema/properties,Schema/items"+
91 "nullable,type", ",") {
92 if fieldFilter.MatchString(f) {
93 return nil, errors.Newf(token.NoPos, "field filter may not exclude %q", f)
94 }
95 }
96 }
97
98 if g.Version == "" {
99 g.Version = "3.0.0"
100 }
101
102 c := &buildContext{
103 inst: val,
104 instExt: val,
105 refPrefix: "components/schemas",
106 expandRefs: g.ExpandReferences,
107 structural: g.ExpandReferences,
108 nameFunc: g.NameFunc,
109 descFunc: g.DescriptionFunc,
110 schemas: &OrderedMap{},
111 externalRefs: map[string]*externalType{},
112 fieldFilter: fieldFilter,
113 }
114 if g.ReferenceFunc != nil {
115 if !isInstance {
116 panic("cannot use ReferenceFunc along with cue.Value")
117 }
118 if g.NameFunc != nil {
119 panic("cannot specify both ReferenceFunc and NameFunc")
120 }
121
122 c.nameFunc = func(val cue.Value, path cue.Path) string {
123 sels := path.Selectors()
124 labels := make([]string, len(sels))
125 for i, sel := range sels {
126 labels[i] = selectorLabel(sel)
127 }
128 inst, ok := c.imports[val]
129 if !ok {
130 r, n := internalvalue.ToInternal(val)
131 buildInst := r.GetInstanceFromNode(n)
132 var err error
133 inst, err = (*cue.Runtime)(r).Build(buildInst)
134 if err != nil {
135 panic("cannot build instance from value")
136 }
137 if c.imports == nil {
138 c.imports = make(map[cue.Value]*cue.Instance)
139 }
140 c.imports[val] = inst
141 }
142 return g.ReferenceFunc(inst, labels)
143 }
144 }
145
146 switch g.Version {
147 case "3.0.0":
148 c.exclusiveBool = true
149 case "3.1.0":
150 default:
151 return nil, errors.Newf(token.NoPos, "unsupported version %s", g.Version)
152 }
153
154 defer func() {
155 switch x := recover().(type) {
156 case nil:
157 case *openapiError:
158 err = x
159 default:
160 panic(x)
161 }
162 }()
163
164
165
166 i, err := inst.Value().Fields(cue.Definitions(true))
167 if err != nil {
168 return nil, err
169 }
170 for i.Next() {
171 sel := i.Selector()
172 if !sel.IsDefinition() {
173 continue
174 }
175
176 if c.isInternal(sel) {
177 continue
178 }
179 ref := c.makeRef(val, cue.MakePath(sel))
180 if ref == "" {
181 continue
182 }
183 c.schemas.Set(ref, c.build(sel, i.Value()))
184 }
185
186
187 for done := 0; len(c.externalRefs) != done; {
188 done = len(c.externalRefs)
189
190
191 external := []string{}
192 for k := range c.externalRefs {
193 external = append(external, k)
194 }
195 sort.Strings(external)
196
197 for _, k := range external {
198 ext := c.externalRefs[k]
199 c.instExt = ext.inst
200 sels := ext.path.Selectors()
201 last := len(sels) - 1
202 c.path = sels[:last]
203 name := sels[last]
204 c.schemas.Set(ext.ref, c.build(name, cue.Dereference(ext.value)))
205 }
206 }
207
208 a := c.schemas.Elts
209 sort.Slice(a, func(i, j int) bool {
210 x, _, _ := ast.LabelName(a[i].(*ast.Field).Label)
211 y, _, _ := ast.LabelName(a[j].(*ast.Field).Label)
212 return x < y
213 })
214
215 return (*ast.StructLit)(c.schemas), c.errs
216 }
217
218 func (c *buildContext) build(name cue.Selector, v cue.Value) *ast.StructLit {
219 return newCoreBuilder(c).schema(nil, name, v)
220 }
221
222
223 func (c *buildContext) isInternal(sel cue.Selector) bool {
224
225
226 return sel.Type().LabelType() == cue.DefinitionLabel &&
227 strings.HasSuffix(sel.String(), "_value")
228 }
229
230 func (b *builder) failf(v cue.Value, format string, args ...interface{}) {
231 panic(&openapiError{
232 errors.NewMessagef(format, args...),
233 cue.MakePath(b.ctx.path...),
234 v.Pos(),
235 })
236 }
237
238 func (b *builder) unsupported(v cue.Value) {
239 if b.format == "" {
240
241
242 }
243 }
244
245 func (b *builder) checkArgs(a []cue.Value, n int) {
246 if len(a)-1 != n {
247 b.failf(a[0], "%v must be used with %d arguments", a[0], len(a)-1)
248 }
249 }
250
251 func (b *builder) schema(core *builder, name cue.Selector, v cue.Value) *ast.StructLit {
252 oldPath := b.ctx.path
253 b.ctx.path = append(b.ctx.path, name)
254 defer func() { b.ctx.path = oldPath }()
255
256 var c *builder
257 if core == nil && b.ctx.structural {
258 c = newCoreBuilder(b.ctx)
259 c.buildCore(v)
260 c.coreSchema()
261 } else {
262 c = newRootBuilder(b.ctx)
263 c.core = core
264 }
265
266 return c.fillSchema(v)
267 }
268
269 func (b *builder) getDoc(v cue.Value) {
270 doc := []string{}
271 if b.ctx.descFunc != nil {
272 if str := b.ctx.descFunc(v); str != "" {
273 doc = append(doc, str)
274 }
275 } else {
276 for _, d := range v.Doc() {
277 doc = append(doc, d.Text())
278 }
279 }
280 if len(doc) > 0 {
281 str := strings.TrimSpace(strings.Join(doc, "\n\n"))
282 b.setSingle("description", ast.NewString(str), true)
283 }
284 }
285
286 func (b *builder) fillSchema(v cue.Value) *ast.StructLit {
287 if b.filled != nil {
288 return b.filled
289 }
290
291 b.setValueType(v)
292 b.format = extractFormat(v)
293 b.deprecated = getDeprecated(v)
294
295 if b.core == nil || len(b.core.values) > 1 {
296 isRef := b.value(v, nil)
297 if isRef {
298 b.typ = ""
299 }
300
301 if !isRef && !b.ctx.structural {
302 b.getDoc(v)
303 }
304 }
305
306 schema := b.finish()
307 s := (*ast.StructLit)(schema)
308
309 simplify(b, s)
310
311 sortSchema(s)
312
313 b.filled = s
314 return s
315 }
316
317 func label(d ast.Decl) string {
318 f := d.(*ast.Field)
319 s, _, _ := ast.LabelName(f.Label)
320 return s
321 }
322
323 func value(d ast.Decl) ast.Expr {
324 return d.(*ast.Field).Value
325 }
326
327 func sortSchema(s *ast.StructLit) {
328 sort.Slice(s.Elts, func(i, j int) bool {
329 iName := label(s.Elts[i])
330 jName := label(s.Elts[j])
331 pi := fieldOrder[iName]
332 pj := fieldOrder[jName]
333 if pi != pj {
334 return pi > pj
335 }
336 return iName < jName
337 })
338 }
339
340 var fieldOrder = map[string]int{
341 "description": 31,
342 "type": 30,
343 "format": 29,
344 "required": 28,
345 "properties": 27,
346 "minProperties": 26,
347 "maxProperties": 25,
348 "minimum": 24,
349 "exclusiveMinimum": 23,
350 "maximum": 22,
351 "exclusiveMaximum": 21,
352 "minItems": 18,
353 "maxItems": 17,
354 "minLength": 16,
355 "maxLength": 15,
356 "items": 14,
357 "enum": 13,
358 "default": 12,
359 }
360
361 func (b *builder) value(v cue.Value, f typeFunc) (isRef bool) {
362 b.pushNode(v)
363 defer b.popNode()
364
365 count := 0
366 disallowDefault := false
367 var values cue.Value
368 if b.ctx.expandRefs || b.format != "" {
369 values = cue.Dereference(v)
370 count = 1
371 } else {
372 dedup := map[string]bool{}
373 hasNoRef := false
374 accept := v
375 conjuncts := appendSplit(nil, cue.AndOp, v)
376 for _, v := range conjuncts {
377
378
379
380 switch v1, path := v.ReferencePath(); {
381 case len(path.Selectors()) > 0:
382 ref := b.ctx.makeRef(v1, path)
383 if ref == "" {
384 v = cue.Dereference(v)
385 break
386 }
387 if dedup[ref] {
388 continue
389 }
390 dedup[ref] = true
391
392 b.addRef(v, v1, path)
393 disallowDefault = true
394 continue
395 }
396 hasNoRef = true
397 count++
398 values = values.UnifyAccept(v, accept)
399 }
400 isRef = !hasNoRef && len(dedup) == 1
401 }
402
403 if count > 0 {
404
405
406 if values.IncompleteKind()&cue.StructKind != cue.StructKind && !isRef {
407 values = values.Eval()
408 }
409
410 conjuncts := appendSplit(nil, cue.AndOp, values)
411 for i, v := range conjuncts {
412 switch {
413 case isConcrete(v):
414 b.dispatch(f, v)
415 if !b.isNonCore() {
416 b.set("enum", ast.NewList(b.decode(v)))
417 }
418 default:
419 a := appendSplit(nil, cue.OrOp, v)
420 for i, v := range a {
421 if _, r := v.Reference(); len(r) == 0 {
422 a[i] = v.Eval()
423 }
424 }
425
426 _ = i
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 switch len(a) {
453 case 0:
454
455 case 1:
456 v = a[0]
457 if err := v.Err(); err != nil {
458 b.failf(v, "openapi: %v", err)
459 return
460 }
461 b.dispatch(f, v)
462 default:
463 b.disjunction(a, f)
464 }
465 }
466 }
467 }
468
469 if v, ok := v.Default(); ok && v.IsConcrete() && !disallowDefault {
470
471
472 switch {
473 case v.Kind() == cue.ListKind:
474 iter, _ := v.List()
475 if !iter.Next() {
476
477 break
478 }
479 fallthrough
480 default:
481 if !b.isNonCore() {
482 e := v.Syntax(cue.Concrete(true)).(ast.Expr)
483 b.setFilter("Schema", "default", e)
484 }
485 }
486 }
487 return isRef
488 }
489
490 func appendSplit(a []cue.Value, splitBy cue.Op, v cue.Value) []cue.Value {
491 op, args := v.Expr()
492
493 k := 1
494 outer:
495 for i := 1; i < len(args); i++ {
496 for j := 0; j < k; j++ {
497 if args[i].Subsume(args[j], cue.Raw()) == nil &&
498 args[j].Subsume(args[i], cue.Raw()) == nil {
499 continue outer
500 }
501 }
502 args[k] = args[i]
503 k++
504 }
505 args = args[:k]
506
507 if op == cue.NoOp && len(args) == 1 {
508
509
510 a = append(a, args...)
511 } else if op != splitBy {
512 a = append(a, v)
513 } else {
514 for _, v := range args {
515 a = appendSplit(a, splitBy, v)
516 }
517 }
518 return a
519 }
520
521
522
523
524
525 func isConcrete(v cue.Value) bool {
526 if !v.IsConcrete() {
527 return false
528 }
529 if v.Kind() == cue.StructKind {
530 return false
531 }
532 for list, _ := v.List(); list.Next(); {
533 if !isConcrete(list.Value()) {
534 return false
535 }
536 }
537 return true
538 }
539
540 func (b *builder) disjunction(a []cue.Value, f typeFunc) {
541 disjuncts := []cue.Value{}
542 enums := []ast.Expr{}
543 nullable := false
544
545 for _, v := range a {
546 switch {
547 case v.Null() == nil:
548
549 nullable = true
550
551 case isConcrete(v):
552 enums = append(enums, b.decode(v))
553
554 default:
555 disjuncts = append(disjuncts, v)
556 }
557 }
558
559
560 if len(disjuncts) == 0 || (len(disjuncts) == 1 && len(enums) == 0) {
561 if len(disjuncts) == 1 {
562 b.value(disjuncts[0], f)
563 }
564 if len(enums) > 0 && !b.isNonCore() {
565 b.set("enum", ast.NewList(enums...))
566 }
567 if nullable {
568 b.setSingle("nullable", ast.NewBool(true), true)
569 }
570 return
571 }
572
573 anyOf := []ast.Expr{}
574 if len(enums) > 0 {
575 anyOf = append(anyOf, b.kv("enum", ast.NewList(enums...)))
576 }
577
578 if nullable {
579 b.setSingle("nullable", ast.NewBool(true), true)
580 }
581
582 schemas := make([]*ast.StructLit, len(disjuncts))
583 for i, v := range disjuncts {
584 c := newOASBuilder(b)
585 c.value(v, f)
586 t := c.finish()
587 schemas[i] = (*ast.StructLit)(t)
588 if len(t.Elts) == 0 {
589 if c.typ == "" {
590 return
591 }
592 }
593 }
594
595 for i, v := range disjuncts {
596
597
598
599
600
601
602
603 subsumed := []ast.Expr{}
604 for j, w := range disjuncts {
605 if i == j {
606 continue
607 }
608 err := v.Subsume(w, cue.Schema())
609 if err == nil || errors.Is(err, internal.ErrInexact) {
610 subsumed = append(subsumed, schemas[j])
611 }
612 }
613
614 t := schemas[i]
615 if len(subsumed) > 0 {
616
617
618 exclude := ast.NewStruct("not",
619 ast.NewStruct("anyOf", ast.NewList(subsumed...)))
620 if len(t.Elts) == 0 {
621 t = exclude
622 } else {
623 t = ast.NewStruct("allOf", ast.NewList(t, exclude))
624 }
625 }
626 anyOf = append(anyOf, t)
627 }
628
629 b.set("oneOf", ast.NewList(anyOf...))
630 }
631
632 func (b *builder) setValueType(v cue.Value) {
633 if b.core != nil {
634 return
635 }
636
637 k := v.IncompleteKind() &^ adt.NullKind
638 switch k {
639 case cue.BoolKind:
640 b.typ = "boolean"
641 case cue.FloatKind, cue.NumberKind:
642 b.typ = "number"
643 case cue.IntKind:
644 b.typ = "integer"
645 case cue.BytesKind:
646 b.typ = "string"
647 case cue.StringKind:
648 b.typ = "string"
649 case cue.StructKind:
650 b.typ = "object"
651 case cue.ListKind:
652 b.typ = "array"
653 }
654 }
655
656 func (b *builder) dispatch(f typeFunc, v cue.Value) {
657 if f != nil {
658 f(b, v)
659 return
660 }
661
662 switch v.IncompleteKind() {
663 case cue.NullKind:
664
665
666 b.setSingle("nullable", ast.NewBool(true), true)
667
668 case cue.BoolKind:
669 b.setType("boolean", "")
670
671
672 case cue.FloatKind, cue.NumberKind:
673
674
675
676
677 b.setType("number", "")
678 b.number(v)
679
680 case cue.IntKind:
681
682
683 b.setType("integer", "")
684 b.number(v)
685
686
687
688 case cue.BytesKind:
689
690
691 b.setType("string", "byte")
692 b.bytes(v)
693 case cue.StringKind:
694
695
696
697 b.setType("string", "")
698 b.string(v)
699 case cue.StructKind:
700 b.setType("object", "")
701 b.object(v)
702 case cue.ListKind:
703 b.setType("array", "")
704 b.array(v)
705 }
706 }
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721 func (b *builder) object(v cue.Value) {
722
723
724
725
726
727 switch op, a := v.Expr(); op {
728 case cue.CallOp:
729 name := fmt.Sprint(a[0])
730 switch name {
731 case "struct.MinFields":
732 b.checkArgs(a, 1)
733 b.setFilter("Schema", "minProperties", b.int(a[1]))
734 return
735
736 case "struct.MaxFields":
737 b.checkArgs(a, 1)
738 b.setFilter("Schema", "maxProperties", b.int(a[1]))
739 return
740
741 default:
742 b.unsupported(a[0])
743 return
744 }
745
746 case cue.NoOp:
747
748
749 default:
750 b.failf(v, "unsupported op %v for object type (%v)", op, v)
751 return
752 }
753
754 required := []ast.Expr{}
755 for i, _ := v.Fields(); i.Next(); {
756 required = append(required, ast.NewString(i.Label()))
757 }
758 if len(required) > 0 {
759 b.setFilter("Schema", "required", ast.NewList(required...))
760 }
761
762 var properties *OrderedMap
763 if b.singleFields != nil {
764 properties = b.singleFields.getMap("properties")
765 }
766 hasProps := properties != nil
767 if !hasProps {
768 properties = &OrderedMap{}
769 }
770
771 for i, _ := v.Fields(cue.Optional(true), cue.Definitions(true)); i.Next(); {
772 sel := i.Selector()
773 if b.ctx.isInternal(sel) {
774 continue
775 }
776 label := selectorLabel(sel)
777 var core *builder
778 if b.core != nil {
779 core = b.core.properties[label]
780 }
781 schema := b.schema(core, sel, i.Value())
782 switch {
783 case sel.IsDefinition():
784 ref := b.ctx.makeRef(b.ctx.instExt, cue.MakePath(append(b.ctx.path, sel)...))
785 if ref == "" {
786 continue
787 }
788 b.ctx.schemas.Set(ref, schema)
789 case !b.isNonCore() || len(schema.Elts) > 0:
790 properties.Set(label, schema)
791 }
792 }
793
794 if !hasProps && properties.len() > 0 {
795 b.setSingle("properties", (*ast.StructLit)(properties), false)
796 }
797
798 if t, ok := v.Elem(); ok &&
799 (b.core == nil || b.core.items == nil) && b.checkCycle(t) {
800 schema := b.schema(nil, cue.AnyString, t)
801 if len(schema.Elts) > 0 {
802 b.setSingle("additionalProperties", schema, true)
803 }
804 }
805
806
807
808 }
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832 func (b *builder) array(v cue.Value) {
833
834 switch op, a := v.Expr(); op {
835 case cue.CallOp:
836 name := fmt.Sprint(a[0])
837 switch name {
838 case "list.UniqueItems", "list.UniqueItems()":
839 b.checkArgs(a, 0)
840 b.setFilter("Schema", "uniqueItems", ast.NewBool(true))
841 return
842
843 case "list.MinItems":
844 b.checkArgs(a, 1)
845 b.setFilter("Schema", "minItems", b.int(a[1]))
846 return
847
848 case "list.MaxItems":
849 b.checkArgs(a, 1)
850 b.setFilter("Schema", "maxItems", b.int(a[1]))
851 return
852
853 default:
854 b.unsupported(a[0])
855 return
856 }
857
858 case cue.NoOp:
859
860
861 default:
862 b.failf(v, "unsupported op %v for array type", op)
863 return
864 }
865
866
867
868
869
870
871
872 items := []ast.Expr{}
873 count := 0
874 for i, _ := v.List(); i.Next(); count++ {
875 items = append(items, b.schema(nil, cue.Index(count), i.Value()))
876 }
877 if len(items) > 0 {
878
879
880
881 b.set("items", ast.NewList(items...))
882
883 }
884
885
886
887
888 cap := v.Len()
889 hasMax := false
890 maxLength := int64(math.MaxInt64)
891
892 if n, capErr := cap.Int64(); capErr == nil {
893 maxLength = n
894 hasMax = true
895 } else {
896 b.value(cap, (*builder).listCap)
897 }
898
899 if !hasMax || int64(len(items)) < maxLength {
900 if typ, ok := v.Elem(); ok && b.checkCycle(typ) {
901 var core *builder
902 if b.core != nil {
903 core = b.core.items
904 }
905 t := b.schema(core, cue.AnyString, typ)
906 if len(items) > 0 {
907 b.setFilter("Schema", "additionalItems", t)
908 } else if !b.isNonCore() || len(t.Elts) > 0 {
909 b.setSingle("items", t, true)
910 }
911 }
912 }
913 }
914
915 func (b *builder) listCap(v cue.Value) {
916 switch op, a := v.Expr(); op {
917 case cue.LessThanOp:
918 b.setFilter("Schema", "maxItems", b.inta(a[0], -1))
919 case cue.LessThanEqualOp:
920 b.setFilter("Schema", "maxItems", b.inta(a[0], 0))
921 case cue.GreaterThanOp:
922 b.setFilter("Schema", "minItems", b.inta(a[0], 1))
923 case cue.GreaterThanEqualOp:
924 if b.int64(a[0]) > 0 {
925 b.setFilter("Schema", "minItems", b.inta(a[0], 0))
926 }
927 case cue.NoOp:
928
929 case cue.NotEqualOp:
930 i := b.int(a[0])
931 b.setNot("allOff", ast.NewList(
932 b.kv("minItems", i),
933 b.kv("maxItems", i),
934 ))
935
936 default:
937 b.failf(v, "unsupported op for list capacity %v", op)
938 return
939 }
940 }
941
942 func (b *builder) number(v cue.Value) {
943
944
945
946 switch op, a := v.Expr(); op {
947 case cue.LessThanOp:
948 if b.ctx.exclusiveBool {
949 b.setFilter("Schema", "exclusiveMaximum", ast.NewBool(true))
950 b.setFilter("Schema", "maximum", b.big(a[0]))
951 } else {
952 b.setFilter("Schema", "exclusiveMaximum", b.big(a[0]))
953 }
954
955 case cue.LessThanEqualOp:
956 b.setFilter("Schema", "maximum", b.big(a[0]))
957
958 case cue.GreaterThanOp:
959 if b.ctx.exclusiveBool {
960 b.setFilter("Schema", "exclusiveMinimum", ast.NewBool(true))
961 b.setFilter("Schema", "minimum", b.big(a[0]))
962 } else {
963 b.setFilter("Schema", "exclusiveMinimum", b.big(a[0]))
964 }
965
966 case cue.GreaterThanEqualOp:
967 b.setFilter("Schema", "minimum", b.big(a[0]))
968
969 case cue.NotEqualOp:
970 i := b.big(a[0])
971 b.setNot("allOff", ast.NewList(
972 b.kv("minimum", i),
973 b.kv("maximum", i),
974 ))
975
976 case cue.CallOp:
977 name := fmt.Sprint(a[0])
978 switch name {
979 case "math.MultipleOf":
980 b.checkArgs(a, 1)
981 b.setFilter("Schema", "multipleOf", b.int(a[1]))
982
983 default:
984 b.unsupported(a[0])
985 return
986 }
987
988 case cue.NoOp:
989
990
991 default:
992 b.failf(v, "unsupported op for number %v", op)
993 }
994 }
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035 func (b *builder) string(v cue.Value) {
1036 switch op, a := v.Expr(); op {
1037
1038 case cue.RegexMatchOp, cue.NotRegexMatchOp:
1039 s, err := a[0].String()
1040 if err != nil {
1041
1042
1043
1044 b.failf(v, "regexp value must be a string: %v", err)
1045 return
1046 }
1047 if op == cue.RegexMatchOp {
1048 b.setFilter("Schema", "pattern", ast.NewString(s))
1049 } else {
1050 b.setNot("pattern", ast.NewString(s))
1051 }
1052
1053 case cue.NoOp, cue.SelectorOp:
1054
1055 case cue.CallOp:
1056 name := fmt.Sprint(a[0])
1057 switch name {
1058 case "strings.MinRunes":
1059 b.checkArgs(a, 1)
1060 b.setFilter("Schema", "minLength", b.int(a[1]))
1061 return
1062
1063 case "strings.MaxRunes":
1064 b.checkArgs(a, 1)
1065 b.setFilter("Schema", "maxLength", b.int(a[1]))
1066 return
1067
1068 default:
1069 b.unsupported(a[0])
1070 return
1071 }
1072
1073 default:
1074 b.failf(v, "unsupported op %v for string type", op)
1075 }
1076 }
1077
1078 func (b *builder) bytes(v cue.Value) {
1079 switch op, a := v.Expr(); op {
1080
1081 case cue.RegexMatchOp, cue.NotRegexMatchOp:
1082 s, err := a[0].Bytes()
1083 if err != nil {
1084
1085
1086
1087 b.failf(v, "regexp value must be of type bytes: %v", err)
1088 return
1089 }
1090
1091 e := ast.NewString(string(s))
1092 if op == cue.RegexMatchOp {
1093 b.setFilter("Schema", "pattern", e)
1094 } else {
1095 b.setNot("pattern", e)
1096 }
1097
1098
1099
1100
1101
1102 case cue.NoOp, cue.SelectorOp:
1103
1104 default:
1105 b.failf(v, "unsupported op %v for bytes type", op)
1106 }
1107 }
1108
1109 type builder struct {
1110 ctx *buildContext
1111 typ string
1112 format string
1113 singleFields *oaSchema
1114 current *oaSchema
1115 allOf []*ast.StructLit
1116 deprecated bool
1117
1118
1119 core *builder
1120 kind cue.Kind
1121 filled *ast.StructLit
1122 values []cue.Value
1123 keys []string
1124 properties map[string]*builder
1125 items *builder
1126 }
1127
1128 func newRootBuilder(c *buildContext) *builder {
1129 return &builder{ctx: c}
1130 }
1131
1132 func newOASBuilder(parent *builder) *builder {
1133 core := parent
1134 if parent.core != nil {
1135 core = parent.core
1136 }
1137 b := &builder{
1138 core: core,
1139 ctx: parent.ctx,
1140 typ: parent.typ,
1141 format: parent.format,
1142 }
1143 return b
1144 }
1145
1146 func (b *builder) isNonCore() bool {
1147 return b.core != nil
1148 }
1149
1150 func (b *builder) setType(t, format string) {
1151 if b.typ == "" {
1152 b.typ = t
1153 if format != "" {
1154 b.format = format
1155 }
1156 }
1157 }
1158
1159 func setType(t *oaSchema, b *builder) {
1160 if b.typ != "" {
1161 if b.core == nil || (b.core.typ != b.typ && !b.ctx.structural) {
1162 if !t.exists("type") {
1163 t.Set("type", ast.NewString(b.typ))
1164 }
1165 }
1166 }
1167 if b.format != "" {
1168 if b.core == nil || b.core.format != b.format {
1169 t.Set("format", ast.NewString(b.format))
1170 }
1171 }
1172 }
1173
1174
1175 func (b *builder) setFilter(schema, key string, v ast.Expr) {
1176 if re := b.ctx.fieldFilter; re != nil && re.MatchString(path.Join(schema, key)) {
1177 return
1178 }
1179 b.set(key, v)
1180 }
1181
1182
1183 func (b *builder) setSingle(key string, v ast.Expr, drop bool) {
1184 if b.singleFields == nil {
1185 b.singleFields = &OrderedMap{}
1186 }
1187 if b.singleFields.exists(key) {
1188 if !drop {
1189 b.failf(cue.Value{}, "more than one value added for key %q", key)
1190 }
1191 }
1192 b.singleFields.Set(key, v)
1193 }
1194
1195 func (b *builder) set(key string, v ast.Expr) {
1196 if b.current == nil {
1197 b.current = &OrderedMap{}
1198 b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
1199 } else if b.current.exists(key) {
1200 b.current = &OrderedMap{}
1201 b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
1202 }
1203 b.current.Set(key, v)
1204 }
1205
1206 func (b *builder) kv(key string, value ast.Expr) *ast.StructLit {
1207 return ast.NewStruct(key, value)
1208 }
1209
1210 func (b *builder) setNot(key string, value ast.Expr) {
1211 b.add(ast.NewStruct("not", b.kv(key, value)))
1212 }
1213
1214 func (b *builder) finish() *ast.StructLit {
1215 var t *OrderedMap
1216
1217 if b.filled != nil {
1218 return b.filled
1219 }
1220 switch len(b.allOf) {
1221 case 0:
1222 t = &OrderedMap{}
1223
1224 case 1:
1225 hasRef := false
1226 for _, e := range b.allOf[0].Elts {
1227 if f, ok := e.(*ast.Field); ok {
1228 name, _, _ := ast.LabelName(f.Label)
1229 hasRef = hasRef || name == "$ref"
1230 }
1231 }
1232 if !hasRef || b.singleFields == nil {
1233 t = (*OrderedMap)(b.allOf[0])
1234 break
1235 }
1236 fallthrough
1237
1238 default:
1239 exprs := []ast.Expr{}
1240 for _, s := range b.allOf {
1241 exprs = append(exprs, s)
1242 }
1243 t = &OrderedMap{}
1244 t.Set("allOf", ast.NewList(exprs...))
1245 }
1246 if b.singleFields != nil {
1247 b.singleFields.Elts = append(b.singleFields.Elts, t.Elts...)
1248 t = b.singleFields
1249 }
1250 if b.deprecated {
1251 t.Set("deprecated", ast.NewBool(true))
1252 }
1253 setType(t, b)
1254 sortSchema((*ast.StructLit)(t))
1255 return (*ast.StructLit)(t)
1256 }
1257
1258 func (b *builder) add(t *ast.StructLit) {
1259 b.allOf = append(b.allOf, t)
1260 }
1261
1262 func (b *builder) addConjunct(f func(*builder)) {
1263 c := newOASBuilder(b)
1264 f(c)
1265 b.add((*ast.StructLit)(c.finish()))
1266 }
1267
1268 func (b *builder) addRef(v cue.Value, inst cue.Value, ref cue.Path) {
1269 name := b.ctx.makeRef(inst, ref)
1270 b.addConjunct(func(b *builder) {
1271 b.allOf = append(b.allOf, ast.NewStruct(
1272 "$ref",
1273 ast.NewString(path.Join("#", b.ctx.refPrefix, name)),
1274 ))
1275 })
1276
1277 if b.ctx.inst != inst {
1278 b.ctx.externalRefs[name] = &externalType{
1279 ref: name,
1280 inst: inst,
1281 path: ref,
1282 value: v,
1283 }
1284 }
1285 }
1286
1287 func (b *buildContext) makeRef(inst cue.Value, ref cue.Path) string {
1288 if b.nameFunc != nil {
1289 return b.nameFunc(inst, ref)
1290 }
1291 var buf strings.Builder
1292 for i, sel := range ref.Selectors() {
1293 if i > 0 {
1294 buf.WriteByte('.')
1295 }
1296
1297 buf.WriteString(selectorLabel(sel))
1298 }
1299 return buf.String()
1300 }
1301
1302 func (b *builder) int64(v cue.Value) int64 {
1303 v, _ = v.Default()
1304 i, err := v.Int64()
1305 if err != nil {
1306 b.failf(v, "could not retrieve int: %v", err)
1307 }
1308 return i
1309 }
1310
1311 func (b *builder) intExpr(i int64) ast.Expr {
1312 return &ast.BasicLit{
1313 Kind: token.INT,
1314 Value: fmt.Sprint(i),
1315 }
1316 }
1317
1318 func (b *builder) int(v cue.Value) ast.Expr {
1319 return b.intExpr(b.int64(v))
1320 }
1321
1322 func (b *builder) inta(v cue.Value, offset int64) ast.Expr {
1323 return b.intExpr(b.int64(v) + offset)
1324 }
1325
1326 func (b *builder) decode(v cue.Value) ast.Expr {
1327 v, _ = v.Default()
1328 return v.Syntax(cue.Final()).(ast.Expr)
1329 }
1330
1331 func (b *builder) big(v cue.Value) ast.Expr {
1332 v, _ = v.Default()
1333 return v.Syntax(cue.Final()).(ast.Expr)
1334 }
1335
1336 func selectorLabel(sel cue.Selector) string {
1337 if sel.Type().ConstraintType() == cue.PatternConstraint {
1338 return "*"
1339 }
1340 switch sel.LabelType() {
1341 case cue.StringLabel:
1342 return sel.Unquoted()
1343 case cue.DefinitionLabel:
1344 return sel.String()[1:]
1345 }
1346
1347
1348
1349 panic(fmt.Sprintf("unreachable %v", sel.Type()))
1350 }
1351
View as plain text