blob: 405b21cbea2bebd5cb3659cb27f9de6650ae850e [file] [log] [blame]
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package openapi
16
17import (
Marcel van Lohuizence195d22019-07-25 16:03:33 +020018 "fmt"
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040019 "math"
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +020020 "math/big"
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040021 "path"
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020022 "regexp"
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020023 "sort"
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020024 "strconv"
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040025 "strings"
26
Koichi Shiraishi30a4bee2019-12-26 03:06:48 +090027 "github.com/cockroachdb/apd/v2"
28
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040029 "cuelang.org/go/cue"
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020030 "cuelang.org/go/cue/errors"
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020031 "cuelang.org/go/cue/token"
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040032)
33
34type buildContext struct {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020035 inst *cue.Instance
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040036 refPrefix string
37 path []string
38
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020039 expandRefs bool
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +020040 structural bool
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020041 nameFunc func(inst *cue.Instance, path []string) string
42 descFunc func(v cue.Value) string
43 fieldFilter *regexp.Regexp
Marcel van Lohuizen73e3f6b2019-08-01 16:10:57 +020044 evalDepth int // detect cycles when resolving references
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040045
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020046 schemas *OrderedMap
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020047
48 // Track external schemas.
49 externalRefs map[string]*externalType
50}
51
52type externalType struct {
53 ref string
54 inst *cue.Instance
55 path []string
56 value cue.Value
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040057}
58
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020059type oaSchema = OrderedMap
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040060
61type typeFunc func(b *builder, a cue.Value)
62
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +020063func schemas(g *Generator, inst *cue.Instance) (schemas *OrderedMap, err error) {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020064 var fieldFilter *regexp.Regexp
65 if g.FieldFilter != "" {
66 fieldFilter, err = regexp.Compile(g.FieldFilter)
67 if err != nil {
68 return nil, errors.Newf(token.NoPos, "invalid field filter: %v", err)
69 }
70
71 // verify that certain elements are still passed.
72 for _, f := range strings.Split(
73 "version,title,allOf,anyOf,not,enum,Schema/properties,Schema/items"+
74 "nullable,type", ",") {
75 if fieldFilter.MatchString(f) {
76 return nil, errors.Newf(token.NoPos, "field filter may not exclude %q", f)
77 }
78 }
79 }
80
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040081 c := buildContext{
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020082 inst: inst,
Marcel van Lohuizen0a975332019-07-04 10:43:25 +020083 refPrefix: "components/schemas",
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020084 expandRefs: g.ExpandReferences,
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +020085 structural: g.ExpandReferences,
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020086 nameFunc: g.ReferenceFunc,
Marcel van Lohuizen4de93e12019-07-02 20:04:10 +020087 descFunc: g.DescriptionFunc,
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020088 schemas: &OrderedMap{},
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020089 externalRefs: map[string]*externalType{},
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020090 fieldFilter: fieldFilter,
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040091 }
92
93 defer func() {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020094 switch x := recover().(type) {
95 case nil:
96 case *openapiError:
97 err = x
98 default:
99 panic(x)
100 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400101 }()
102
Marcel van Lohuizen865f1592019-07-02 19:43:45 +0200103 // Although paths is empty for now, it makes it valid OpenAPI spec.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400104
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200105 i, err := inst.Value().Fields(cue.Definitions(true))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400106 if err != nil {
107 return nil, err
108 }
109 for i.Next() {
110 // message, enum, or constant.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200111 label := i.Label()
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200112 if c.isInternal(label) {
113 continue
114 }
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200115 ref := c.makeRef(inst, []string{label})
116 if ref == "" {
117 continue
118 }
119 c.schemas.Set(ref, c.build(label, i.Value()))
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200120 }
121
122 // keep looping until a fixed point is reached.
123 for done := 0; len(c.externalRefs) != done; {
124 done = len(c.externalRefs)
125
126 // From now on, all references need to be expanded
127 external := []string{}
128 for k := range c.externalRefs {
129 external = append(external, k)
130 }
131 sort.Strings(external)
132
133 for _, k := range external {
134 ext := c.externalRefs[k]
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +0200135 c.schemas.Set(ext.ref, c.build(ext.ref, ext.value.Eval()))
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200136 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400137 }
Marcel van Lohuizen865f1592019-07-02 19:43:45 +0200138
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +0200139 return c.schemas, nil
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400140}
141
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200142func (c *buildContext) build(name string, v cue.Value) *oaSchema {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200143 return newCoreBuilder(c).schema(nil, name, v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400144}
145
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200146// isInternal reports whether or not to include this type.
147func (c *buildContext) isInternal(name string) bool {
148 // TODO: allow a regexp filter in Config. If we have closed structs and
149 // definitions, this will likely be unnecessary.
150 return strings.HasSuffix(name, "_value")
151}
152
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200153func (b *builder) failf(v cue.Value, format string, args ...interface{}) {
154 panic(&openapiError{
155 errors.NewMessage(format, args),
156 b.ctx.path,
157 v.Pos(),
158 })
159}
160
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200161func (b *builder) unsupported(v cue.Value) {
162 if b.format == "" {
163 // Not strictly an error, but consider listing it as a warning
164 // in strict mode.
165 }
166}
167
168func (b *builder) checkArgs(a []cue.Value, n int) {
169 if len(a)-1 != n {
170 b.failf(a[0], "%v must be used with %d arguments", a[0], len(a)-1)
171 }
172}
173
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200174func (b *builder) schema(core *builder, name string, v cue.Value) *oaSchema {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200175 oldPath := b.ctx.path
176 b.ctx.path = append(b.ctx.path, name)
177 defer func() { b.ctx.path = oldPath }()
178
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200179 var c *builder
180 if core == nil && b.ctx.structural {
181 c = newCoreBuilder(b.ctx)
182 c.buildCore(v) // initialize core structure
183 c.coreSchema(name) // build the
184 } else {
185 c = newRootBuilder(b.ctx)
186 c.core = core
187 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200188
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200189 return c.fillSchema(v)
190}
191
192func (b *builder) getDoc(v cue.Value) {
193 doc := []string{}
194 if b.ctx.descFunc != nil {
195 if str := b.ctx.descFunc(v); str != "" {
196 doc = append(doc, str)
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200197 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200198 } else {
199 for _, d := range v.Doc() {
200 doc = append(doc, d.Text())
201 }
202 }
203 if len(doc) > 0 {
204 str := strings.TrimSpace(strings.Join(doc, "\n\n"))
205 b.setSingle("description", str, true)
206 }
207}
208
209func (b *builder) fillSchema(v cue.Value) *oaSchema {
210 if b.filled != nil {
211 return b.filled
212 }
213
214 b.setValueType(v)
215 b.format = extractFormat(v)
Jason Wang2b56efa2019-08-21 13:35:53 -0700216 b.deprecated = getDeprecated(v)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200217
218 if b.core == nil || len(b.core.values) > 1 {
219 isRef := b.value(v, nil)
220 if isRef {
221 b.typ = ""
222 }
223
224 if !isRef && !b.ctx.structural {
225 b.getDoc(v)
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200226 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400227 }
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200228
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200229 schema := b.finish()
230
231 simplify(b, schema)
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200232
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200233 sortSchema(schema)
234
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200235 b.filled = schema
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400236 return schema
237}
238
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200239func sortSchema(s *oaSchema) {
240 sort.Slice(s.kvs, func(i, j int) bool {
241 pi := fieldOrder[s.kvs[i].Key]
242 pj := fieldOrder[s.kvs[j].Key]
243 if pi != pj {
244 return pi > pj
245 }
246 return s.kvs[i].Key < s.kvs[j].Key
247 })
248}
249
250var fieldOrder = map[string]int{
251 "description": 31,
252 "type": 30,
253 "format": 29,
254 "required": 28,
255 "properties": 27,
256 "minProperties": 26,
257 "maxProperties": 25,
258 "minimum": 24,
259 "exclusiveMinimum": 23,
260 "maximum": 22,
261 "exclusiveMaximum": 21,
262 "minItems": 18,
263 "maxItems": 17,
264 "minLength": 16,
265 "maxLength": 15,
266 "items": 14,
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200267 "enum": 13,
268 "default": 12,
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200269}
270
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200271func (b *builder) resolve(v cue.Value) cue.Value {
272 // Cycles are not allowed when expanding references. Right now we just
273 // cap the depth of evaluation at 30.
274 // TODO: do something more principled.
275 const maxDepth = 30
276 if b.ctx.evalDepth > maxDepth {
277 b.failf(v, "maximum stack depth of %d reached", maxDepth)
278 }
279 b.ctx.evalDepth++
280 defer func() { b.ctx.evalDepth-- }()
281
Marcel van Lohuizen73e3f6b2019-08-01 16:10:57 +0200282 switch op, a := v.Expr(); op {
283 case cue.SelectorOp:
284 field, _ := a[1].String()
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200285 f, _ := b.resolve(a[0]).LookupField(field)
286 v = cue.Value{}
287 if !f.IsOptional {
288 v = f.Value
289 }
Marcel van Lohuizen73e3f6b2019-08-01 16:10:57 +0200290 }
291 return v
292}
293
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200294func (b *builder) value(v cue.Value, f typeFunc) (isRef bool) {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400295 count := 0
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200296 disallowDefault := false
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400297 var values cue.Value
Marcel van Lohuizen6bf36972019-08-06 20:15:58 +0200298 if b.ctx.expandRefs || b.format != "" {
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200299 values = b.resolve(v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400300 count = 1
301 } else {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200302 dedup := map[string]bool{}
303 hasNoRef := false
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400304 for _, v := range appendSplit(nil, cue.AndOp, v) {
305 // This may be a reference to an enum. So we need to check references before
306 // dissecting them.
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +0200307 switch p, r := v.Reference(); {
308 case len(r) > 0:
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200309 ref := b.ctx.makeRef(p, r)
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200310 if ref == "" {
311 v = b.resolve(v)
312 break
313 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200314 if dedup[ref] {
315 continue
316 }
317 dedup[ref] = true
318
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +0200319 b.addRef(v, p, r)
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200320 disallowDefault = true
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200321 continue
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400322 }
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200323 hasNoRef = true
324 count++
325 values = values.Unify(v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400326 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200327 isRef = !hasNoRef && len(dedup) == 1
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400328 }
329
330 if count > 0 { // TODO: implement IsAny.
331 // NOTE: Eval is not necessary here. Removing it will yield
332 // different results that also are correct. The difference is that
333 // Eval detects and eliminates impossible combinations at the
334 // expense of having potentially much larger configurations due to
335 // a combinatorial explosion. This rudimentary check picks the least
336 // fo the two extreme forms.
337 if eval := values.Eval(); countNodes(eval) < countNodes(values) {
338 values = eval
339 }
340
341 for _, v := range appendSplit(nil, cue.AndOp, values) {
342 switch {
343 case isConcrete(v):
344 b.dispatch(f, v)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200345 if !b.isNonCore() {
346 b.set("enum", []interface{}{b.decode(v)})
347 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400348 default:
349 if a := appendSplit(nil, cue.OrOp, v); len(a) > 1 {
350 b.disjunction(a, f)
351 } else {
352 v = a[0]
353 if err := v.Err(); err != nil {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200354 b.failf(v, "openapi: %v", err)
355 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400356 }
357 b.dispatch(f, v)
358 }
359 }
360 }
361 }
362
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200363 if v, ok := v.Default(); ok && v.IsConcrete() && !disallowDefault {
364 // TODO: should we show the empty list default? This would be correct
365 // but perhaps a bit too pedantic and noisy.
366 switch {
367 case v.Kind() == cue.ListKind:
368 iter, _ := v.List()
369 if !iter.Next() {
370 // Don't show default for empty list.
371 break
372 }
373 fallthrough
374 default:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200375 if !b.isNonCore() {
376 b.setFilter("Schema", "default", v)
377 }
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200378 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400379 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200380 return isRef
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400381}
382
383func appendSplit(a []cue.Value, splitBy cue.Op, v cue.Value) []cue.Value {
384 op, args := v.Expr()
385 if op == cue.NoOp && len(args) > 0 {
386 // TODO: this is to deal with default value removal. This may change
387 // whe we completely separate default values from values.
388 a = append(a, args...)
389 } else if op != splitBy {
390 a = append(a, v)
391 } else {
392 for _, v := range args {
393 a = appendSplit(a, splitBy, v)
394 }
395 }
396 return a
397}
398
399func countNodes(v cue.Value) (n int) {
400 switch op, a := v.Expr(); op {
401 case cue.OrOp, cue.AndOp:
402 for _, v := range a {
403 n += countNodes(v)
404 }
405 n += len(a) - 1
406 default:
407 switch v.Kind() {
408 case cue.ListKind:
409 for i, _ := v.List(); i.Next(); {
410 n += countNodes(i.Value())
411 }
412 case cue.StructKind:
413 for i, _ := v.Fields(); i.Next(); {
414 n += countNodes(i.Value()) + 1
415 }
416 }
417 }
418 return n + 1
419}
420
421// isConcrete reports whether v is concrete and not a struct (recursively).
422// structs are not supported as the result of a struct enum depends on how
423// conjunctions and disjunctions are distributed. We could consider still doing
424// this if we define a normal form.
425func isConcrete(v cue.Value) bool {
426 if !v.IsConcrete() {
427 return false
428 }
429 if v.Kind() == cue.StructKind {
430 return false // TODO: handle struct kinds
431 }
432 for list, _ := v.List(); list.Next(); {
433 if !isConcrete(list.Value()) {
434 return false
435 }
436 }
437 return true
438}
439
440func (b *builder) disjunction(a []cue.Value, f typeFunc) {
441 disjuncts := []cue.Value{}
442 enums := []interface{}{} // TODO: unique the enums
443 nullable := false // Only supported in OpenAPI, not JSON schema
444
445 for _, v := range a {
446 switch {
447 case v.Null() == nil:
448 // TODO: for JSON schema, we need to fall through.
449 nullable = true
450
451 case isConcrete(v):
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200452 enums = append(enums, b.decode(v))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400453
454 default:
455 disjuncts = append(disjuncts, v)
456 }
457 }
458
459 // Only one conjunct?
460 if len(disjuncts) == 0 || (len(disjuncts) == 1 && len(enums) == 0) {
461 if len(disjuncts) == 1 {
462 b.value(disjuncts[0], f)
463 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200464 if len(enums) > 0 && !b.isNonCore() {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400465 b.set("enum", enums)
466 }
467 if nullable {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200468 b.setSingle("nullable", true, true) // allowed in Structural
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400469 }
470 return
471 }
472
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200473 anyOf := []*oaSchema{}
474 if len(enums) > 0 {
475 anyOf = append(anyOf, b.kv("enum", enums))
476 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400477
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200478 hasEmpty := false
479 for _, v := range disjuncts {
480 c := newOASBuilder(b)
481 c.value(v, f)
482 t := c.finish()
483 if len(t.kvs) == 0 {
484 hasEmpty = true
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400485 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200486 anyOf = append(anyOf, t)
487 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400488
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200489 // If any of the types was "any", a oneOf may be discarded.
490 if !hasEmpty {
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200491 b.set("oneOf", anyOf)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200492 }
493
494 // TODO: analyze CUE structs to figure out if it should be oneOf or
495 // anyOf. As the source is protobuf for now, it is always oneOf.
496 if nullable {
497 b.setSingle("nullable", true, true)
498 }
499}
500
501func (b *builder) setValueType(v cue.Value) {
502 if b.core != nil {
503 return
504 }
505
Marcel van Lohuizend99e32e2019-08-26 14:42:14 +0200506 switch v.IncompleteKind() {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200507 case cue.BoolKind:
508 b.typ = "boolean"
509 case cue.FloatKind, cue.NumberKind:
510 b.typ = "number"
511 case cue.IntKind:
512 b.typ = "integer"
513 case cue.BytesKind:
514 b.typ = "string"
515 case cue.StringKind:
516 b.typ = "string"
517 case cue.StructKind:
518 b.typ = "object"
519 case cue.ListKind:
520 b.typ = "array"
521 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400522}
523
524func (b *builder) dispatch(f typeFunc, v cue.Value) {
525 if f != nil {
526 f(b, v)
527 return
528 }
529
Marcel van Lohuizend99e32e2019-08-26 14:42:14 +0200530 switch v.IncompleteKind() {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400531 case cue.NullKind:
532 // TODO: for JSON schema we would set the type here. For OpenAPI,
533 // it must be nullable.
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200534 b.setSingle("nullable", true, true)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400535
536 case cue.BoolKind:
537 b.setType("boolean", "")
538 // No need to call.
539
540 case cue.FloatKind, cue.NumberKind:
541 // TODO:
542 // Common Name type format Comments
543 // float number float
544 // double number double
545 b.setType("number", "") // may be overridden to integer
546 b.number(v)
547
548 case cue.IntKind:
549 // integer integer int32 signed 32 bits
550 // long integer int64 signed 64 bits
551 b.setType("integer", "") // may be overridden to integer
552 b.number(v)
553
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200554 // TODO: for JSON schema, consider adding multipleOf: 1.
555
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400556 case cue.BytesKind:
557 // byte string byte base64 encoded characters
558 // binary string binary any sequence of octets
559 b.setType("string", "byte")
560 b.bytes(v)
561 case cue.StringKind:
562 // date string date As defined by full-date - RFC3339
563 // dateTime string date-time As defined by date-time - RFC3339
564 // password string password A hint to UIs to obscure input
565 b.setType("string", "")
566 b.string(v)
567 case cue.StructKind:
568 b.setType("object", "")
569 b.object(v)
570 case cue.ListKind:
571 b.setType("array", "")
572 b.array(v)
573 }
574}
575
576// object supports the following
577// - maxProperties: maximum allowed fields in this struct.
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200578// - minProperties: minimum required fields in this struct.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400579// - patternProperties: [regexp]: schema
580// TODO: we can support this once .kv(key, value) allow
581// foo [=~"pattern"]: type
582// An instance field must match all schemas for which a regexp matches.
583// Even though it is not supported in OpenAPI, we should still accept it
584// when receiving from OpenAPI. We could possibly use disjunctions to encode
585// this.
586// - dependencies: what?
587// - propertyNames: schema
588// every property name in the enclosed schema matches that of
589func (b *builder) object(v cue.Value) {
590 // TODO: discriminator objects: we could theoretically derive discriminator
591 // objects automatically: for every object in a oneOf/allOf/anyOf, or any
592 // object composed of the same type, if a property is required and set to a
593 // constant value for each type, it is a discriminator.
594
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200595 switch op, a := v.Expr(); op {
596 case cue.CallOp:
597 name := fmt.Sprint(a[0])
598 switch name {
599 case "struct.MinFields":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200600 b.checkArgs(a, 1)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200601 b.setFilter("Schema", "minProperties", b.int(a[1]))
602 return
603
604 case "struct.MaxFields":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200605 b.checkArgs(a, 1)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200606 b.setFilter("Schema", "maxProperties", b.int(a[1]))
607 return
608
609 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200610 b.unsupported(a[0])
611 return
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200612 }
613
614 case cue.NoOp:
615 // TODO: extract format from specific type.
616
617 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200618 b.failf(v, "unsupported op %v for object type (%v)", op, v)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200619 return
620 }
621
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400622 required := []string{}
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200623 for i, _ := v.Fields(); i.Next(); {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400624 required = append(required, i.Label())
625 }
626 if len(required) > 0 {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200627 b.setFilter("Schema", "required", required)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400628 }
629
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200630 var properties *OrderedMap
631 if b.singleFields != nil {
632 properties = b.singleFields.getMap("properties")
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400633 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200634 hasProps := properties != nil
635 if !hasProps {
636 properties = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400637 }
638
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200639 for i, _ := v.Fields(cue.Optional(true)); i.Next(); {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200640 label := i.Label()
641 var core *builder
642 if b.core != nil {
643 core = b.core.properties[label]
644 }
645 schema := b.schema(core, label, i.Value())
646 if !b.isNonCore() || len(schema.kvs) > 0 {
647 properties.Set(label, schema)
648 }
649 }
650
651 if !hasProps && len(properties.kvs) > 0 {
652 b.setSingle("properties", properties, false)
653 }
654
655 if t, ok := v.Elem(); ok && (b.core == nil || b.core.items == nil) {
656 schema := b.schema(nil, "*", t)
657 if len(schema.kvs) > 0 {
658 b.setSingle("additionalProperties", schema, true) // Not allowed in structural.
659 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400660 }
661
662 // TODO: maxProperties, minProperties: can be done once we allow cap to
663 // unify with structs.
664}
665
666// List constraints:
667//
668// Max and min items.
669// - maxItems: int (inclusive)
670// - minItems: int (inclusive)
671// - items (item type)
672// schema: applies to all items
673// array of schemas:
674// schema at pos must match if both value and items are defined.
675// - additional items:
676// schema: where items must be an array of schemas, intstance elements
677// succeed for if they match this value for any value at a position
678// greater than that covered by items.
679// - uniqueItems: bool
680// TODO: support with list.Unique() unique() or comprehensions.
681// For the latter, we need equality for all values, which is doable,
682// but not done yet.
683//
684// NOT SUPPORTED IN OpenAPI:
685// - contains:
686// schema: an array instance is valid if at least one element matches
687// this schema.
688func (b *builder) array(v cue.Value) {
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200689
690 switch op, a := v.Expr(); op {
691 case cue.CallOp:
692 name := fmt.Sprint(a[0])
693 switch name {
694 case "list.UniqueItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200695 b.checkArgs(a, 0)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200696 b.setFilter("Schema", "uniqueItems", true)
697 return
698
699 case "list.MinItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200700 b.checkArgs(a, 1)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200701 b.setFilter("Schema", "minItems", b.int(a[1]))
702 return
703
704 case "list.MaxItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200705 b.checkArgs(a, 1)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200706 b.setFilter("Schema", "maxItems", b.int(a[1]))
707 return
708
709 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200710 b.unsupported(a[0])
711 return
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200712 }
713
714 case cue.NoOp:
715 // TODO: extract format from specific type.
716
717 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200718 b.failf(v, "unsupported op %v for array type", op)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200719 return
720 }
721
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400722 // Possible conjuncts:
723 // - one list (CUE guarantees merging all conjuncts)
724 // - no cap: is unified with list
725 // - unique items: at most one, but idempotent if multiple.
726 // There is never a need for allOf or anyOf. Note that a CUE list
727 // corresponds almost one-to-one to OpenAPI lists.
728 items := []*oaSchema{}
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200729 count := 0
730 for i, _ := v.List(); i.Next(); count++ {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200731 items = append(items, b.schema(nil, strconv.Itoa(count), i.Value()))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400732 }
733 if len(items) > 0 {
734 // TODO: per-item schema are not allowed in OpenAPI, only in JSON Schema.
735 // Perhaps we should turn this into an OR after first normalizing
736 // the entries.
737 b.set("items", items)
738 // panic("per-item types not supported in OpenAPI")
739 }
740
741 // TODO:
742 // A CUE cap can be a set of discontinuous ranges. If we encounter this,
743 // we can create an allOf(list type, anyOf(ranges)).
744 cap := v.Len()
745 hasMax := false
746 maxLength := int64(math.MaxInt64)
747
748 if n, capErr := cap.Int64(); capErr == nil {
749 maxLength = n
750 hasMax = true
751 } else {
752 b.value(cap, (*builder).listCap)
753 }
754
755 if !hasMax || int64(len(items)) < maxLength {
756 if typ, ok := v.Elem(); ok {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200757 var core *builder
758 if b.core != nil {
759 core = b.core.items
760 }
761 t := b.schema(core, "*", typ)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400762 if len(items) > 0 {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200763 b.setFilter("Schema", "additionalItems", t) // Not allowed in structural.
764 } else if !b.isNonCore() || len(t.kvs) > 0 {
765 b.setSingle("items", t, true)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400766 }
767 }
768 }
769}
770
771func (b *builder) listCap(v cue.Value) {
772 switch op, a := v.Expr(); op {
773 case cue.LessThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200774 b.setFilter("Schema", "maxItems", b.int(a[0])-1)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400775 case cue.LessThanEqualOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200776 b.setFilter("Schema", "maxItems", b.int(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400777 case cue.GreaterThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200778 b.setFilter("Schema", "minItems", b.int(a[0])+1)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400779 case cue.GreaterThanEqualOp:
780 if b.int(a[0]) > 0 {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200781 b.setFilter("Schema", "minItems", b.int(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400782 }
783 case cue.NoOp:
784 // must be type, so okay.
785 case cue.NotEqualOp:
786 i := b.int(a[0])
787 b.setNot("allOff", []*oaSchema{
788 b.kv("minItems", i),
789 b.kv("maxItems", i),
790 })
791
792 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200793 b.failf(v, "unsupported op for list capacity %v", op)
794 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400795 }
796}
797
798func (b *builder) number(v cue.Value) {
799 // Multiple conjuncts mostly means just additive constraints.
800 // Type may be number of float.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400801
802 switch op, a := v.Expr(); op {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400803 case cue.LessThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200804 b.setFilter("Schema", "exclusiveMaximum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400805
806 case cue.LessThanEqualOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200807 b.setFilter("Schema", "maximum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400808
809 case cue.GreaterThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200810 b.setFilter("Schema", "exclusiveMinimum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400811
812 case cue.GreaterThanEqualOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200813 b.setFilter("Schema", "minimum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400814
815 case cue.NotEqualOp:
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200816 i := b.big(a[0])
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400817 b.setNot("allOff", []*oaSchema{
Marcel van Lohuizen5ba71742019-07-25 18:35:09 +0200818 b.kv("minimum", i),
819 b.kv("maximum", i),
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400820 })
821
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200822 case cue.CallOp:
823 name := fmt.Sprint(a[0])
824 switch name {
825 case "math.MultipleOf":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200826 b.checkArgs(a, 1)
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200827 b.setFilter("Schema", "multipleOf", b.int(a[1]))
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200828
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200829 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200830 b.unsupported(a[0])
831 return
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200832 }
833
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400834 case cue.NoOp:
835 // TODO: extract format from specific type.
836
837 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200838 b.failf(v, "unsupported op for number %v", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400839 }
840}
841
842// Multiple Regexp conjuncts are represented as allOf all other
843// constraints can be combined unless in the even of discontinuous
844// lengths.
845
846// string supports the following options:
847//
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200848// - maxLength (Unicode codepoints)
849// - minLength (Unicode codepoints)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400850// - pattern (a regexp)
851//
852// The regexp pattern is as follows, and is limited to be a strict subset of RE2:
853// Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-3.3
854//
855// JSON schema requires ECMA 262 regular expressions, but
856// limited to the following constructs:
857// - simple character classes: [abc]
858// - range character classes: [a-z]
859// - complement character classes: [^abc], [^a-z]
860// - simple quantifiers: +, *, ?, and lazy versions +? *? ??
861// - range quantifiers: {x}, {x,y}, {x,}, {x}?, {x,y}?, {x,}?
862// - begin and end anchors: ^ and $
863// - simple grouping: (...)
864// - alteration: |
865// This is a subset of RE2 used by CUE.
866//
867// Most notably absent:
868// - the '.' for any character (not sure if that is a doc bug)
869// - character classes \d \D [[::]] \pN \p{Name} \PN \P{Name}
870// - word boundaries
871// - capturing directives.
872// - flag setting
873// - comments
874//
875// The capturing directives and comments can be removed without
876// compromising the meaning of the regexp (TODO). Removing
877// flag setting will be tricky. Unicode character classes,
878// boundaries, etc can be compiled into simple character classes,
879// although the resulting regexp will look cumbersome.
880//
881func (b *builder) string(v cue.Value) {
882 switch op, a := v.Expr(); op {
883
884 case cue.RegexMatchOp, cue.NotRegexMatchOp:
885 s, err := a[0].String()
886 if err != nil {
887 // TODO: this may be an unresolved interpolation or expression. Consider
888 // whether it is reasonable to treat unevaluated operands as wholes and
889 // generate a compound regular expression.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200890 b.failf(v, "regexp value must be a string: %v", err)
891 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400892 }
893 if op == cue.RegexMatchOp {
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200894 b.setFilter("Schema", "pattern", s)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400895 } else {
896 b.setNot("pattern", s)
897 }
898
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200899 case cue.NoOp, cue.SelectorOp:
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400900
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200901 case cue.CallOp:
902 name := fmt.Sprint(a[0])
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200903 switch name {
904 case "strings.MinRunes":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200905 b.checkArgs(a, 1)
906 b.setFilter("Schema", "minLength", b.int(a[1]))
907 return
908
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200909 case "strings.MaxRunes":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200910 b.checkArgs(a, 1)
911 b.setFilter("Schema", "maxLength", b.int(a[1]))
912 return
913
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200914 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200915 b.unsupported(a[0])
916 return
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200917 }
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200918
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400919 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200920 b.failf(v, "unsupported op %v for string type", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400921 }
922}
923
924func (b *builder) bytes(v cue.Value) {
925 switch op, a := v.Expr(); op {
926
927 case cue.RegexMatchOp, cue.NotRegexMatchOp:
928 s, err := a[0].Bytes()
929 if err != nil {
930 // TODO: this may be an unresolved interpolation or expression. Consider
931 // whether it is reasonable to treat unevaluated operands as wholes and
932 // generate a compound regular expression.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200933 b.failf(v, "regexp value must be of type bytes: %v", err)
934 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400935 }
936
937 if op == cue.RegexMatchOp {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200938 b.setFilter("Schema", "pattern", s)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400939 } else {
940 b.setNot("pattern", s)
941 }
942
943 // TODO: support the following JSON schema constraints
944 // - maxLength
945 // - minLength
946
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200947 case cue.NoOp, cue.SelectorOp:
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400948
949 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200950 b.failf(v, "unsupported op %v for bytes type", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400951 }
952}
953
954type builder struct {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200955 ctx *buildContext
956 typ string
957 format string
958 singleFields *oaSchema
959 current *oaSchema
960 allOf []*oaSchema
Jason Wang2b56efa2019-08-21 13:35:53 -0700961 deprecated bool
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200962
963 // Building structural schema
964 core *builder
965 kind cue.Kind
966 filled *oaSchema
967 values []cue.Value // in structural mode, all values of not and *Of.
968 keys []string
969 properties map[string]*builder
970 items *builder
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400971}
972
973func newRootBuilder(c *buildContext) *builder {
974 return &builder{ctx: c}
975}
976
977func newOASBuilder(parent *builder) *builder {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200978 core := parent
979 if parent.core != nil {
980 core = parent.core
981 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400982 b := &builder{
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200983 core: core,
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400984 ctx: parent.ctx,
985 typ: parent.typ,
986 format: parent.format,
987 }
988 return b
989}
990
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200991func (b *builder) isNonCore() bool {
992 return b.core != nil
993}
994
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400995func (b *builder) setType(t, format string) {
996 if b.typ == "" {
997 b.typ = t
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200998 if format != "" {
999 b.format = format
1000 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001001 }
1002}
1003
1004func setType(t *oaSchema, b *builder) {
1005 if b.typ != "" {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001006 if b.core == nil || (b.core.typ != b.typ && !b.ctx.structural) {
1007 if !t.exists("type") {
1008 t.Set("type", b.typ)
1009 }
1010 }
1011 }
1012 if b.format != "" {
1013 if b.core == nil || b.core.format != b.format {
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001014 t.Set("format", b.format)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001015 }
1016 }
1017}
1018
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +02001019// setFilter is like set, but allows the key-value pair to be filtered.
1020func (b *builder) setFilter(schema, key string, v interface{}) {
1021 if re := b.ctx.fieldFilter; re != nil && re.MatchString(path.Join(schema, key)) {
1022 return
1023 }
1024 b.set(key, v)
1025}
1026
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001027// setSingle sets a value of which there should only be one.
1028func (b *builder) setSingle(key string, v interface{}, drop bool) {
1029 if b.singleFields == nil {
1030 b.singleFields = &OrderedMap{}
1031 }
1032 if b.singleFields.exists(key) {
1033 if !drop {
1034 b.failf(cue.Value{}, "more than one value added for key %q", key)
1035 }
1036 }
1037 b.singleFields.Set(key, v)
1038}
1039
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001040func (b *builder) set(key string, v interface{}) {
1041 if b.current == nil {
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001042 b.current = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001043 b.allOf = append(b.allOf, b.current)
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001044 } else if b.current.exists(key) {
1045 b.current = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001046 b.allOf = append(b.allOf, b.current)
1047 }
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001048 b.current.Set(key, v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001049}
1050
1051func (b *builder) kv(key string, value interface{}) *oaSchema {
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001052 constraint := &OrderedMap{}
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001053 constraint.Set(key, value)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001054 return constraint
1055}
1056
1057func (b *builder) setNot(key string, value interface{}) {
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001058 not := &OrderedMap{}
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001059 not.Set("not", b.kv(key, value))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001060 b.add(not)
1061}
1062
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001063func (b *builder) finish() (t *oaSchema) {
1064 if b.filled != nil {
1065 return b.filled
1066 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001067 switch len(b.allOf) {
1068 case 0:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001069 t = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001070
1071 case 1:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001072 t = b.allOf[0]
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001073
1074 default:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001075 t = &OrderedMap{}
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001076 t.Set("allOf", b.allOf)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001077 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001078 if b.singleFields != nil {
1079 b.singleFields.kvs = append(b.singleFields.kvs, t.kvs...)
1080 t = b.singleFields
1081 }
Jason Wang2b56efa2019-08-21 13:35:53 -07001082 if b.deprecated {
1083 t.Set("deprecated", true)
1084 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001085 setType(t, b)
1086 sortSchema(t)
1087 return t
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001088}
1089
1090func (b *builder) add(t *oaSchema) {
1091 b.allOf = append(b.allOf, t)
1092}
1093
1094func (b *builder) addConjunct(f func(*builder)) {
1095 c := newOASBuilder(b)
1096 f(c)
1097 b.add(c.finish())
1098}
1099
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +02001100func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +02001101 name := b.ctx.makeRef(inst, ref)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001102 b.addConjunct(func(b *builder) {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +02001103 b.set("$ref", path.Join("#", b.ctx.refPrefix, name))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001104 })
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +02001105
1106 if b.ctx.inst != inst {
1107 b.ctx.externalRefs[name] = &externalType{
1108 ref: name,
1109 inst: inst,
1110 path: ref,
1111 value: v,
1112 }
1113 }
1114}
1115
1116func (b *buildContext) makeRef(inst *cue.Instance, ref []string) string {
1117 a := make([]string, 0, len(ref)+3)
1118 if b.nameFunc != nil {
1119 a = append(a, b.nameFunc(inst, ref))
1120 } else {
1121 a = append(a, ref...)
1122 }
1123 return path.Join(a...)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001124}
1125
1126func (b *builder) int(v cue.Value) int64 {
1127 i, err := v.Int64()
1128 if err != nil {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001129 b.failf(v, "could not retrieve int: %v", err)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001130 }
1131 return i
1132}
1133
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001134func (b *builder) decode(v cue.Value) interface{} {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001135 var d interface{}
1136 if err := v.Decode(&d); err != nil {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001137 b.failf(v, "decode error: %v", err)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001138 }
1139 return d
1140}
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +02001141
1142func (b *builder) big(v cue.Value) interface{} {
1143 var mant big.Int
1144 exp, err := v.MantExp(&mant)
1145 if err != nil {
1146 b.failf(v, "value not a number: %v", err)
1147 return nil
1148 }
1149 return &decimal{apd.NewWithBigInt(&mant, int32(exp))}
1150}