blob: 1c8b1b6a2f6b410fb4402597aaac679cb074be99 [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"
20 "path"
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020021 "regexp"
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020022 "sort"
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020023 "strconv"
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040024 "strings"
25
26 "cuelang.org/go/cue"
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +010027 "cuelang.org/go/cue/ast"
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020028 "cuelang.org/go/cue/errors"
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020029 "cuelang.org/go/cue/token"
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +010030 "cuelang.org/go/internal"
31 "golang.org/x/xerrors"
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 Lohuizened90d002020-03-30 17:18:12 +020036 instExt *cue.Instance
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040037 refPrefix string
38 path []string
39
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020040 expandRefs bool
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +020041 structural bool
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020042 nameFunc func(inst *cue.Instance, path []string) string
43 descFunc func(v cue.Value) string
44 fieldFilter *regexp.Regexp
Marcel van Lohuizen73e3f6b2019-08-01 16:10:57 +020045 evalDepth int // detect cycles when resolving references
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040046
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020047 schemas *OrderedMap
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020048
49 // Track external schemas.
50 externalRefs map[string]*externalType
51}
52
53type externalType struct {
54 ref string
55 inst *cue.Instance
56 path []string
57 value cue.Value
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040058}
59
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020060type oaSchema = OrderedMap
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040061
62type typeFunc func(b *builder, a cue.Value)
63
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +010064func schemas(g *Generator, inst *cue.Instance) (schemas *ast.StructLit, err error) {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020065 var fieldFilter *regexp.Regexp
66 if g.FieldFilter != "" {
67 fieldFilter, err = regexp.Compile(g.FieldFilter)
68 if err != nil {
69 return nil, errors.Newf(token.NoPos, "invalid field filter: %v", err)
70 }
71
72 // verify that certain elements are still passed.
73 for _, f := range strings.Split(
74 "version,title,allOf,anyOf,not,enum,Schema/properties,Schema/items"+
75 "nullable,type", ",") {
76 if fieldFilter.MatchString(f) {
77 return nil, errors.Newf(token.NoPos, "field filter may not exclude %q", f)
78 }
79 }
80 }
81
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040082 c := buildContext{
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020083 inst: inst,
Marcel van Lohuizened90d002020-03-30 17:18:12 +020084 instExt: inst,
Marcel van Lohuizen0a975332019-07-04 10:43:25 +020085 refPrefix: "components/schemas",
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020086 expandRefs: g.ExpandReferences,
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +020087 structural: g.ExpandReferences,
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020088 nameFunc: g.ReferenceFunc,
Marcel van Lohuizen4de93e12019-07-02 20:04:10 +020089 descFunc: g.DescriptionFunc,
Marcel van Lohuizen865f1592019-07-02 19:43:45 +020090 schemas: &OrderedMap{},
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +020091 externalRefs: map[string]*externalType{},
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +020092 fieldFilter: fieldFilter,
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -040093 }
94
95 defer func() {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +020096 switch x := recover().(type) {
97 case nil:
98 case *openapiError:
99 err = x
100 default:
101 panic(x)
102 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400103 }()
104
Marcel van Lohuizen865f1592019-07-02 19:43:45 +0200105 // Although paths is empty for now, it makes it valid OpenAPI spec.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400106
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200107 i, err := inst.Value().Fields(cue.Definitions(true))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400108 if err != nil {
109 return nil, err
110 }
111 for i.Next() {
Marcel van Lohuizen3c437bd2020-04-01 18:19:30 +0200112 if !i.IsDefinition() {
113 continue
114 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400115 // message, enum, or constant.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200116 label := i.Label()
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200117 if c.isInternal(label) {
118 continue
119 }
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200120 ref := c.makeRef(inst, []string{label})
121 if ref == "" {
122 continue
123 }
124 c.schemas.Set(ref, c.build(label, i.Value()))
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200125 }
126
127 // keep looping until a fixed point is reached.
128 for done := 0; len(c.externalRefs) != done; {
129 done = len(c.externalRefs)
130
131 // From now on, all references need to be expanded
132 external := []string{}
133 for k := range c.externalRefs {
134 external = append(external, k)
135 }
136 sort.Strings(external)
137
138 for _, k := range external {
139 ext := c.externalRefs[k]
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200140 c.instExt = ext.inst
141 last := len(ext.path) - 1
142 c.path = ext.path[:last]
143 name := ext.path[last]
144 c.schemas.Set(ext.ref, c.build(name, cue.Dereference(ext.value)))
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200145 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400146 }
Marcel van Lohuizen865f1592019-07-02 19:43:45 +0200147
Marcel van Lohuizen56509a52020-03-30 13:29:31 +0200148 a := c.schemas.Elts
149 sort.Slice(a, func(i, j int) bool {
150 x, _, _ := ast.LabelName(a[i].(*ast.Field).Label)
151 y, _, _ := ast.LabelName(a[j].(*ast.Field).Label)
152 return x < y
153 })
154
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100155 return (*ast.StructLit)(c.schemas), nil
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400156}
157
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100158func (c *buildContext) build(name string, v cue.Value) *ast.StructLit {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200159 return newCoreBuilder(c).schema(nil, name, v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400160}
161
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200162// isInternal reports whether or not to include this type.
163func (c *buildContext) isInternal(name string) bool {
164 // TODO: allow a regexp filter in Config. If we have closed structs and
165 // definitions, this will likely be unnecessary.
166 return strings.HasSuffix(name, "_value")
167}
168
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200169func (b *builder) failf(v cue.Value, format string, args ...interface{}) {
170 panic(&openapiError{
171 errors.NewMessage(format, args),
172 b.ctx.path,
173 v.Pos(),
174 })
175}
176
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200177func (b *builder) unsupported(v cue.Value) {
178 if b.format == "" {
179 // Not strictly an error, but consider listing it as a warning
180 // in strict mode.
181 }
182}
183
184func (b *builder) checkArgs(a []cue.Value, n int) {
185 if len(a)-1 != n {
186 b.failf(a[0], "%v must be used with %d arguments", a[0], len(a)-1)
187 }
188}
189
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100190func (b *builder) schema(core *builder, name string, v cue.Value) *ast.StructLit {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200191 oldPath := b.ctx.path
192 b.ctx.path = append(b.ctx.path, name)
193 defer func() { b.ctx.path = oldPath }()
194
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200195 var c *builder
196 if core == nil && b.ctx.structural {
197 c = newCoreBuilder(b.ctx)
198 c.buildCore(v) // initialize core structure
199 c.coreSchema(name) // build the
200 } else {
201 c = newRootBuilder(b.ctx)
202 c.core = core
203 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200204
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200205 return c.fillSchema(v)
206}
207
208func (b *builder) getDoc(v cue.Value) {
209 doc := []string{}
210 if b.ctx.descFunc != nil {
211 if str := b.ctx.descFunc(v); str != "" {
212 doc = append(doc, str)
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200213 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200214 } else {
215 for _, d := range v.Doc() {
216 doc = append(doc, d.Text())
217 }
218 }
219 if len(doc) > 0 {
220 str := strings.TrimSpace(strings.Join(doc, "\n\n"))
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100221 b.setSingle("description", ast.NewString(str), true)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200222 }
223}
224
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100225func (b *builder) fillSchema(v cue.Value) *ast.StructLit {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200226 if b.filled != nil {
227 return b.filled
228 }
229
230 b.setValueType(v)
231 b.format = extractFormat(v)
Jason Wang2b56efa2019-08-21 13:35:53 -0700232 b.deprecated = getDeprecated(v)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200233
234 if b.core == nil || len(b.core.values) > 1 {
235 isRef := b.value(v, nil)
236 if isRef {
237 b.typ = ""
238 }
239
240 if !isRef && !b.ctx.structural {
241 b.getDoc(v)
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200242 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400243 }
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200244
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200245 schema := b.finish()
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100246 s := (*ast.StructLit)(schema)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200247
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100248 simplify(b, s)
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200249
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100250 sortSchema(s)
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200251
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100252 b.filled = s
253 return s
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400254}
255
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100256func label(d ast.Decl) string {
257 f := d.(*ast.Field)
258 s, _, _ := ast.LabelName(f.Label)
259 return s
260}
261
262func value(d ast.Decl) ast.Expr {
263 return d.(*ast.Field).Value
264}
265
266func sortSchema(s *ast.StructLit) {
267 sort.Slice(s.Elts, func(i, j int) bool {
268 iName := label(s.Elts[i])
269 jName := label(s.Elts[j])
270 pi := fieldOrder[iName]
271 pj := fieldOrder[jName]
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200272 if pi != pj {
273 return pi > pj
274 }
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100275 return iName < jName
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200276 })
277}
278
279var fieldOrder = map[string]int{
280 "description": 31,
281 "type": 30,
282 "format": 29,
283 "required": 28,
284 "properties": 27,
285 "minProperties": 26,
286 "maxProperties": 25,
287 "minimum": 24,
288 "exclusiveMinimum": 23,
289 "maximum": 22,
290 "exclusiveMaximum": 21,
291 "minItems": 18,
292 "maxItems": 17,
293 "minLength": 16,
294 "maxLength": 15,
295 "items": 14,
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200296 "enum": 13,
297 "default": 12,
Marcel van Lohuizen35abfa72019-08-12 15:31:53 +0200298}
299
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200300func (b *builder) value(v cue.Value, f typeFunc) (isRef bool) {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400301 count := 0
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200302 disallowDefault := false
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400303 var values cue.Value
Marcel van Lohuizen6bf36972019-08-06 20:15:58 +0200304 if b.ctx.expandRefs || b.format != "" {
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200305 values = cue.Dereference(v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400306 count = 1
307 } else {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200308 dedup := map[string]bool{}
309 hasNoRef := false
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400310 for _, v := range appendSplit(nil, cue.AndOp, v) {
311 // This may be a reference to an enum. So we need to check references before
312 // dissecting them.
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +0200313 switch p, r := v.Reference(); {
314 case len(r) > 0:
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200315 ref := b.ctx.makeRef(p, r)
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200316 if ref == "" {
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200317 v = cue.Dereference(v)
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200318 break
319 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200320 if dedup[ref] {
321 continue
322 }
323 dedup[ref] = true
324
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +0200325 b.addRef(v, p, r)
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200326 disallowDefault = true
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200327 continue
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400328 }
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200329 hasNoRef = true
330 count++
331 values = values.Unify(v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400332 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200333 isRef = !hasNoRef && len(dedup) == 1
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400334 }
335
336 if count > 0 { // TODO: implement IsAny.
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100337 // TODO: perhaps find optimal representation. For now we assume the
338 // representation as is is already optimized for human consumption.
339 if values.IncompleteKind()&cue.StructKind != cue.StructKind && !isRef {
340 values = values.Eval()
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400341 }
342
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100343 conjuncts := appendSplit(nil, cue.AndOp, values)
344 for i, v := range conjuncts {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400345 switch {
346 case isConcrete(v):
347 b.dispatch(f, v)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200348 if !b.isNonCore() {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100349 b.set("enum", ast.NewList(b.decode(v)))
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200350 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400351 default:
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100352 a := appendSplit(nil, cue.OrOp, v)
353 for i, v := range a {
354 if _, r := v.Reference(); len(r) == 0 {
355 a[i] = v.Eval()
356 }
357 }
358
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200359 _ = i
360 // TODO: it matters here whether a conjunct is obtained
361 // from embedding or normal unification. Fix this at some
362 // point.
363 //
364 // if len(a) > 1 {
365 // // Filter disjuncts that cannot unify with other conjuncts,
366 // // and thus can never be satisfied.
367 // // TODO: there should be generalized simplification logic
368 // // in CUE (outside of the usual implicit simplifications).
369 // k := 0
370 // outer:
371 // for _, d := range a {
372 // for j, w := range conjuncts {
373 // if i == j {
374 // continue
375 // }
376 // if d.Unify(w).Err() != nil {
377 // continue outer
378 // }
379 // }
380 // a[k] = d
381 // k++
382 // }
383 // a = a[:k]
384 // }
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100385 switch len(a) {
386 case 0:
387 // Conjunct entirely eliminated.
388 case 1:
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400389 v = a[0]
390 if err := v.Err(); err != nil {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200391 b.failf(v, "openapi: %v", err)
392 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400393 }
394 b.dispatch(f, v)
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100395 default:
396 b.disjunction(a, f)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400397 }
398 }
399 }
400 }
401
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200402 if v, ok := v.Default(); ok && v.IsConcrete() && !disallowDefault {
403 // TODO: should we show the empty list default? This would be correct
404 // but perhaps a bit too pedantic and noisy.
405 switch {
406 case v.Kind() == cue.ListKind:
407 iter, _ := v.List()
408 if !iter.Next() {
409 // Don't show default for empty list.
410 break
411 }
412 fallthrough
413 default:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200414 if !b.isNonCore() {
Marcel van Lohuizen3c437bd2020-04-01 18:19:30 +0200415 e := v.Syntax(cue.Concrete(true)).(ast.Expr)
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100416 b.setFilter("Schema", "default", e)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200417 }
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200418 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400419 }
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +0200420 return isRef
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400421}
422
423func appendSplit(a []cue.Value, splitBy cue.Op, v cue.Value) []cue.Value {
424 op, args := v.Expr()
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200425 if op == cue.NoOp && len(args) == 1 {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400426 // TODO: this is to deal with default value removal. This may change
427 // whe we completely separate default values from values.
428 a = append(a, args...)
429 } else if op != splitBy {
430 a = append(a, v)
431 } else {
432 for _, v := range args {
433 a = appendSplit(a, splitBy, v)
434 }
435 }
436 return a
437}
438
439func countNodes(v cue.Value) (n int) {
440 switch op, a := v.Expr(); op {
441 case cue.OrOp, cue.AndOp:
442 for _, v := range a {
443 n += countNodes(v)
444 }
445 n += len(a) - 1
446 default:
447 switch v.Kind() {
448 case cue.ListKind:
449 for i, _ := v.List(); i.Next(); {
450 n += countNodes(i.Value())
451 }
452 case cue.StructKind:
453 for i, _ := v.Fields(); i.Next(); {
454 n += countNodes(i.Value()) + 1
455 }
456 }
457 }
458 return n + 1
459}
460
461// isConcrete reports whether v is concrete and not a struct (recursively).
462// structs are not supported as the result of a struct enum depends on how
463// conjunctions and disjunctions are distributed. We could consider still doing
464// this if we define a normal form.
465func isConcrete(v cue.Value) bool {
466 if !v.IsConcrete() {
467 return false
468 }
469 if v.Kind() == cue.StructKind {
470 return false // TODO: handle struct kinds
471 }
472 for list, _ := v.List(); list.Next(); {
473 if !isConcrete(list.Value()) {
474 return false
475 }
476 }
477 return true
478}
479
480func (b *builder) disjunction(a []cue.Value, f typeFunc) {
481 disjuncts := []cue.Value{}
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100482 enums := []ast.Expr{} // TODO: unique the enums
483 nullable := false // Only supported in OpenAPI, not JSON schema
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400484
485 for _, v := range a {
486 switch {
487 case v.Null() == nil:
488 // TODO: for JSON schema, we need to fall through.
489 nullable = true
490
491 case isConcrete(v):
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200492 enums = append(enums, b.decode(v))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400493
494 default:
495 disjuncts = append(disjuncts, v)
496 }
497 }
498
499 // Only one conjunct?
500 if len(disjuncts) == 0 || (len(disjuncts) == 1 && len(enums) == 0) {
501 if len(disjuncts) == 1 {
502 b.value(disjuncts[0], f)
503 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200504 if len(enums) > 0 && !b.isNonCore() {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100505 b.set("enum", ast.NewList(enums...))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400506 }
507 if nullable {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100508 b.setSingle("nullable", ast.NewBool(true), true) // allowed in Structural
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400509 }
510 return
511 }
512
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100513 anyOf := []ast.Expr{}
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200514 if len(enums) > 0 {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100515 anyOf = append(anyOf, b.kv("enum", ast.NewList(enums...)))
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200516 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400517
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200518 if nullable {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100519 b.setSingle("nullable", ast.NewBool(true), true)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200520 }
Marcel van Lohuizeneac3ea22020-03-19 09:55:11 +0100521
522 schemas := make([]*ast.StructLit, len(disjuncts))
523 for i, v := range disjuncts {
524 c := newOASBuilder(b)
525 c.value(v, f)
526 t := c.finish()
527 schemas[i] = (*ast.StructLit)(t)
528 if len(t.Elts) == 0 {
529 if c.typ == "" {
530 return
531 }
532 }
533 }
534
535 for i, v := range disjuncts {
536 // In OpenAPI schema are open by default. To ensure forward compability,
537 // we do not represent closed structs with additionalProperties: false
538 // (this is discouraged and often disallowed by implementions), but
539 // rather enforce this by ensuring uniqueness of the disjuncts.
540 //
541 // TODO: subsumption may currently give false negatives. We are extra
542 // conservative in these instances.
543 subsumed := []ast.Expr{}
544 for j, w := range disjuncts {
545 if i == j {
546 continue
547 }
548 err := v.Subsume(w, cue.Schema())
549 if err == nil || xerrors.Is(err, internal.ErrInexact) {
550 subsumed = append(subsumed, schemas[j])
551 }
552 }
553
554 t := schemas[i]
555 if len(subsumed) > 0 {
556 // TODO: elide anyOf if there is only one element. This should be
557 // rare if originating from oneOf.
558 exclude := ast.NewStruct("not",
559 ast.NewStruct("anyOf", ast.NewList(subsumed...)))
560 if len(t.Elts) == 0 {
561 t = exclude
562 } else {
563 t = ast.NewStruct("allOf", ast.NewList(t, exclude))
564 }
565 }
566 anyOf = append(anyOf, t)
567 }
568
569 b.set("oneOf", ast.NewList(anyOf...))
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200570}
571
572func (b *builder) setValueType(v cue.Value) {
573 if b.core != nil {
574 return
575 }
576
Marcel van Lohuizend99e32e2019-08-26 14:42:14 +0200577 switch v.IncompleteKind() {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200578 case cue.BoolKind:
579 b.typ = "boolean"
580 case cue.FloatKind, cue.NumberKind:
581 b.typ = "number"
582 case cue.IntKind:
583 b.typ = "integer"
584 case cue.BytesKind:
585 b.typ = "string"
586 case cue.StringKind:
587 b.typ = "string"
588 case cue.StructKind:
589 b.typ = "object"
590 case cue.ListKind:
591 b.typ = "array"
592 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400593}
594
595func (b *builder) dispatch(f typeFunc, v cue.Value) {
596 if f != nil {
597 f(b, v)
598 return
599 }
600
Marcel van Lohuizend99e32e2019-08-26 14:42:14 +0200601 switch v.IncompleteKind() {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400602 case cue.NullKind:
603 // TODO: for JSON schema we would set the type here. For OpenAPI,
604 // it must be nullable.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100605 b.setSingle("nullable", ast.NewBool(true), true)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400606
607 case cue.BoolKind:
608 b.setType("boolean", "")
609 // No need to call.
610
611 case cue.FloatKind, cue.NumberKind:
612 // TODO:
613 // Common Name type format Comments
614 // float number float
615 // double number double
616 b.setType("number", "") // may be overridden to integer
617 b.number(v)
618
619 case cue.IntKind:
620 // integer integer int32 signed 32 bits
621 // long integer int64 signed 64 bits
622 b.setType("integer", "") // may be overridden to integer
623 b.number(v)
624
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200625 // TODO: for JSON schema, consider adding multipleOf: 1.
626
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400627 case cue.BytesKind:
628 // byte string byte base64 encoded characters
629 // binary string binary any sequence of octets
630 b.setType("string", "byte")
631 b.bytes(v)
632 case cue.StringKind:
633 // date string date As defined by full-date - RFC3339
634 // dateTime string date-time As defined by date-time - RFC3339
635 // password string password A hint to UIs to obscure input
636 b.setType("string", "")
637 b.string(v)
638 case cue.StructKind:
639 b.setType("object", "")
640 b.object(v)
641 case cue.ListKind:
642 b.setType("array", "")
643 b.array(v)
644 }
645}
646
647// object supports the following
648// - maxProperties: maximum allowed fields in this struct.
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200649// - minProperties: minimum required fields in this struct.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400650// - patternProperties: [regexp]: schema
651// TODO: we can support this once .kv(key, value) allow
652// foo [=~"pattern"]: type
653// An instance field must match all schemas for which a regexp matches.
654// Even though it is not supported in OpenAPI, we should still accept it
655// when receiving from OpenAPI. We could possibly use disjunctions to encode
656// this.
657// - dependencies: what?
658// - propertyNames: schema
659// every property name in the enclosed schema matches that of
660func (b *builder) object(v cue.Value) {
661 // TODO: discriminator objects: we could theoretically derive discriminator
662 // objects automatically: for every object in a oneOf/allOf/anyOf, or any
663 // object composed of the same type, if a property is required and set to a
664 // constant value for each type, it is a discriminator.
665
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200666 switch op, a := v.Expr(); op {
667 case cue.CallOp:
668 name := fmt.Sprint(a[0])
669 switch name {
670 case "struct.MinFields":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200671 b.checkArgs(a, 1)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200672 b.setFilter("Schema", "minProperties", b.int(a[1]))
673 return
674
675 case "struct.MaxFields":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200676 b.checkArgs(a, 1)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200677 b.setFilter("Schema", "maxProperties", b.int(a[1]))
678 return
679
680 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200681 b.unsupported(a[0])
682 return
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200683 }
684
685 case cue.NoOp:
686 // TODO: extract format from specific type.
687
688 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200689 b.failf(v, "unsupported op %v for object type (%v)", op, v)
Marcel van Lohuizen3086ea62019-08-01 18:05:40 +0200690 return
691 }
692
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100693 required := []ast.Expr{}
Marcel van Lohuizen7e4dc222019-10-08 13:14:34 +0200694 for i, _ := v.Fields(); i.Next(); {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100695 required = append(required, ast.NewString(i.Label()))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400696 }
697 if len(required) > 0 {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100698 b.setFilter("Schema", "required", ast.NewList(required...))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400699 }
700
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200701 var properties *OrderedMap
702 if b.singleFields != nil {
703 properties = b.singleFields.getMap("properties")
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400704 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200705 hasProps := properties != nil
706 if !hasProps {
707 properties = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400708 }
709
Marcel van Lohuizen439f8572020-03-18 12:07:54 +0100710 for i, _ := v.Fields(cue.Optional(true), cue.Definitions(true)); i.Next(); {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200711 label := i.Label()
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200712 if b.ctx.isInternal(label) {
713 continue
714 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200715 var core *builder
716 if b.core != nil {
717 core = b.core.properties[label]
718 }
719 schema := b.schema(core, label, i.Value())
Marcel van Lohuizen439f8572020-03-18 12:07:54 +0100720 switch {
721 case i.IsDefinition():
Marcel van Lohuizened90d002020-03-30 17:18:12 +0200722 ref := b.ctx.makeRef(b.ctx.instExt, append(b.ctx.path, label))
723 if ref == "" {
724 continue
725 }
Marcel van Lohuizen439f8572020-03-18 12:07:54 +0100726 b.ctx.schemas.Set(ref, schema)
727 case !b.isNonCore() || len(schema.Elts) > 0:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200728 properties.Set(label, schema)
729 }
730 }
731
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100732 if !hasProps && properties.len() > 0 {
733 b.setSingle("properties", (*ast.StructLit)(properties), false)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200734 }
735
736 if t, ok := v.Elem(); ok && (b.core == nil || b.core.items == nil) {
737 schema := b.schema(nil, "*", t)
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100738 if len(schema.Elts) > 0 {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200739 b.setSingle("additionalProperties", schema, true) // Not allowed in structural.
740 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400741 }
742
743 // TODO: maxProperties, minProperties: can be done once we allow cap to
744 // unify with structs.
745}
746
747// List constraints:
748//
749// Max and min items.
750// - maxItems: int (inclusive)
751// - minItems: int (inclusive)
752// - items (item type)
753// schema: applies to all items
754// array of schemas:
755// schema at pos must match if both value and items are defined.
756// - additional items:
757// schema: where items must be an array of schemas, intstance elements
758// succeed for if they match this value for any value at a position
759// greater than that covered by items.
760// - uniqueItems: bool
761// TODO: support with list.Unique() unique() or comprehensions.
762// For the latter, we need equality for all values, which is doable,
763// but not done yet.
764//
765// NOT SUPPORTED IN OpenAPI:
766// - contains:
767// schema: an array instance is valid if at least one element matches
768// this schema.
769func (b *builder) array(v cue.Value) {
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200770
771 switch op, a := v.Expr(); op {
772 case cue.CallOp:
773 name := fmt.Sprint(a[0])
774 switch name {
775 case "list.UniqueItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200776 b.checkArgs(a, 0)
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100777 b.setFilter("Schema", "uniqueItems", ast.NewBool(true))
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200778 return
779
780 case "list.MinItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200781 b.checkArgs(a, 1)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200782 b.setFilter("Schema", "minItems", b.int(a[1]))
783 return
784
785 case "list.MaxItems":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200786 b.checkArgs(a, 1)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200787 b.setFilter("Schema", "maxItems", b.int(a[1]))
788 return
789
790 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200791 b.unsupported(a[0])
792 return
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200793 }
794
795 case cue.NoOp:
796 // TODO: extract format from specific type.
797
798 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200799 b.failf(v, "unsupported op %v for array type", op)
Marcel van Lohuizen1ceb0462019-07-26 16:26:54 +0200800 return
801 }
802
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400803 // Possible conjuncts:
804 // - one list (CUE guarantees merging all conjuncts)
805 // - no cap: is unified with list
806 // - unique items: at most one, but idempotent if multiple.
807 // There is never a need for allOf or anyOf. Note that a CUE list
808 // corresponds almost one-to-one to OpenAPI lists.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100809 items := []ast.Expr{}
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200810 count := 0
811 for i, _ := v.List(); i.Next(); count++ {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200812 items = append(items, b.schema(nil, strconv.Itoa(count), i.Value()))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400813 }
814 if len(items) > 0 {
815 // TODO: per-item schema are not allowed in OpenAPI, only in JSON Schema.
816 // Perhaps we should turn this into an OR after first normalizing
817 // the entries.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100818 b.set("items", ast.NewList(items...))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400819 // panic("per-item types not supported in OpenAPI")
820 }
821
822 // TODO:
823 // A CUE cap can be a set of discontinuous ranges. If we encounter this,
824 // we can create an allOf(list type, anyOf(ranges)).
825 cap := v.Len()
826 hasMax := false
827 maxLength := int64(math.MaxInt64)
828
829 if n, capErr := cap.Int64(); capErr == nil {
830 maxLength = n
831 hasMax = true
832 } else {
833 b.value(cap, (*builder).listCap)
834 }
835
836 if !hasMax || int64(len(items)) < maxLength {
837 if typ, ok := v.Elem(); ok {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200838 var core *builder
839 if b.core != nil {
840 core = b.core.items
841 }
842 t := b.schema(core, "*", typ)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400843 if len(items) > 0 {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200844 b.setFilter("Schema", "additionalItems", t) // Not allowed in structural.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100845 } else if !b.isNonCore() || len(t.Elts) > 0 {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +0200846 b.setSingle("items", t, true)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400847 }
848 }
849 }
850}
851
852func (b *builder) listCap(v cue.Value) {
853 switch op, a := v.Expr(); op {
854 case cue.LessThanOp:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100855 b.setFilter("Schema", "maxItems", b.inta(a[0], -1))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400856 case cue.LessThanEqualOp:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100857 b.setFilter("Schema", "maxItems", b.inta(a[0], 0))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400858 case cue.GreaterThanOp:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100859 b.setFilter("Schema", "minItems", b.inta(a[0], 1))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400860 case cue.GreaterThanEqualOp:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100861 if b.int64(a[0]) > 0 {
862 b.setFilter("Schema", "minItems", b.inta(a[0], 0))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400863 }
864 case cue.NoOp:
865 // must be type, so okay.
866 case cue.NotEqualOp:
867 i := b.int(a[0])
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100868 b.setNot("allOff", ast.NewList(
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400869 b.kv("minItems", i),
870 b.kv("maxItems", i),
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100871 ))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400872
873 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200874 b.failf(v, "unsupported op for list capacity %v", op)
875 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400876 }
877}
878
879func (b *builder) number(v cue.Value) {
880 // Multiple conjuncts mostly means just additive constraints.
881 // Type may be number of float.
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400882
883 switch op, a := v.Expr(); op {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400884 case cue.LessThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200885 b.setFilter("Schema", "exclusiveMaximum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400886
887 case cue.LessThanEqualOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200888 b.setFilter("Schema", "maximum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400889
890 case cue.GreaterThanOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200891 b.setFilter("Schema", "exclusiveMinimum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400892
893 case cue.GreaterThanEqualOp:
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +0200894 b.setFilter("Schema", "minimum", b.big(a[0]))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400895
896 case cue.NotEqualOp:
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +0200897 i := b.big(a[0])
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100898 b.setNot("allOff", ast.NewList(
Marcel van Lohuizen5ba71742019-07-25 18:35:09 +0200899 b.kv("minimum", i),
900 b.kv("maximum", i),
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100901 ))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400902
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200903 case cue.CallOp:
904 name := fmt.Sprint(a[0])
905 switch name {
906 case "math.MultipleOf":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200907 b.checkArgs(a, 1)
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200908 b.setFilter("Schema", "multipleOf", b.int(a[1]))
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200909
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200910 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200911 b.unsupported(a[0])
912 return
Marcel van Lohuizenb05c7682019-07-25 17:51:24 +0200913 }
914
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400915 case cue.NoOp:
916 // TODO: extract format from specific type.
917
918 default:
Marcel van Lohuizenceb003d2019-08-08 13:45:10 +0200919 b.failf(v, "unsupported op for number %v", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400920 }
921}
922
923// Multiple Regexp conjuncts are represented as allOf all other
924// constraints can be combined unless in the even of discontinuous
925// lengths.
926
927// string supports the following options:
928//
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200929// - maxLength (Unicode codepoints)
930// - minLength (Unicode codepoints)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400931// - pattern (a regexp)
932//
933// The regexp pattern is as follows, and is limited to be a strict subset of RE2:
934// Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-3.3
935//
936// JSON schema requires ECMA 262 regular expressions, but
937// limited to the following constructs:
938// - simple character classes: [abc]
939// - range character classes: [a-z]
940// - complement character classes: [^abc], [^a-z]
941// - simple quantifiers: +, *, ?, and lazy versions +? *? ??
942// - range quantifiers: {x}, {x,y}, {x,}, {x}?, {x,y}?, {x,}?
943// - begin and end anchors: ^ and $
944// - simple grouping: (...)
945// - alteration: |
946// This is a subset of RE2 used by CUE.
947//
948// Most notably absent:
949// - the '.' for any character (not sure if that is a doc bug)
950// - character classes \d \D [[::]] \pN \p{Name} \PN \P{Name}
951// - word boundaries
952// - capturing directives.
953// - flag setting
954// - comments
955//
956// The capturing directives and comments can be removed without
957// compromising the meaning of the regexp (TODO). Removing
958// flag setting will be tricky. Unicode character classes,
959// boundaries, etc can be compiled into simple character classes,
960// although the resulting regexp will look cumbersome.
961//
962func (b *builder) string(v cue.Value) {
963 switch op, a := v.Expr(); op {
964
965 case cue.RegexMatchOp, cue.NotRegexMatchOp:
966 s, err := a[0].String()
967 if err != nil {
968 // TODO: this may be an unresolved interpolation or expression. Consider
969 // whether it is reasonable to treat unevaluated operands as wholes and
970 // generate a compound regular expression.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +0200971 b.failf(v, "regexp value must be a string: %v", err)
972 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400973 }
974 if op == cue.RegexMatchOp {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100975 b.setFilter("Schema", "pattern", ast.NewString(s))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400976 } else {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +0100977 b.setNot("pattern", ast.NewString(s))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400978 }
979
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +0200980 case cue.NoOp, cue.SelectorOp:
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -0400981
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200982 case cue.CallOp:
983 name := fmt.Sprint(a[0])
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200984 switch name {
985 case "strings.MinRunes":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200986 b.checkArgs(a, 1)
987 b.setFilter("Schema", "minLength", b.int(a[1]))
988 return
989
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200990 case "strings.MaxRunes":
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200991 b.checkArgs(a, 1)
992 b.setFilter("Schema", "maxLength", b.int(a[1]))
993 return
994
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200995 default:
Marcel van Lohuizen6cea1362019-08-06 19:39:05 +0200996 b.unsupported(a[0])
997 return
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200998 }
Marcel van Lohuizence195d22019-07-25 16:03:33 +0200999
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001000 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001001 b.failf(v, "unsupported op %v for string type", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001002 }
1003}
1004
1005func (b *builder) bytes(v cue.Value) {
1006 switch op, a := v.Expr(); op {
1007
1008 case cue.RegexMatchOp, cue.NotRegexMatchOp:
1009 s, err := a[0].Bytes()
1010 if err != nil {
1011 // TODO: this may be an unresolved interpolation or expression. Consider
1012 // whether it is reasonable to treat unevaluated operands as wholes and
1013 // generate a compound regular expression.
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001014 b.failf(v, "regexp value must be of type bytes: %v", err)
1015 return
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001016 }
1017
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001018 e := ast.NewString(string(s))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001019 if op == cue.RegexMatchOp {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001020 b.setFilter("Schema", "pattern", e)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001021 } else {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001022 b.setNot("pattern", e)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001023 }
1024
1025 // TODO: support the following JSON schema constraints
1026 // - maxLength
1027 // - minLength
1028
Marcel van Lohuizenfdd176c2019-06-25 18:12:26 +02001029 case cue.NoOp, cue.SelectorOp:
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001030
1031 default:
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001032 b.failf(v, "unsupported op %v for bytes type", op)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001033 }
1034}
1035
1036type builder struct {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001037 ctx *buildContext
1038 typ string
1039 format string
1040 singleFields *oaSchema
1041 current *oaSchema
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001042 allOf []*ast.StructLit
Jason Wang2b56efa2019-08-21 13:35:53 -07001043 deprecated bool
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001044
1045 // Building structural schema
1046 core *builder
1047 kind cue.Kind
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001048 filled *ast.StructLit
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001049 values []cue.Value // in structural mode, all values of not and *Of.
1050 keys []string
1051 properties map[string]*builder
1052 items *builder
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001053}
1054
1055func newRootBuilder(c *buildContext) *builder {
1056 return &builder{ctx: c}
1057}
1058
1059func newOASBuilder(parent *builder) *builder {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001060 core := parent
1061 if parent.core != nil {
1062 core = parent.core
1063 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001064 b := &builder{
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001065 core: core,
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001066 ctx: parent.ctx,
1067 typ: parent.typ,
1068 format: parent.format,
1069 }
1070 return b
1071}
1072
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001073func (b *builder) isNonCore() bool {
1074 return b.core != nil
1075}
1076
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001077func (b *builder) setType(t, format string) {
1078 if b.typ == "" {
1079 b.typ = t
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +02001080 if format != "" {
1081 b.format = format
1082 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001083 }
1084}
1085
1086func setType(t *oaSchema, b *builder) {
1087 if b.typ != "" {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001088 if b.core == nil || (b.core.typ != b.typ && !b.ctx.structural) {
1089 if !t.exists("type") {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001090 t.Set("type", ast.NewString(b.typ))
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001091 }
1092 }
1093 }
1094 if b.format != "" {
1095 if b.core == nil || b.core.format != b.format {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001096 t.Set("format", ast.NewString(b.format))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001097 }
1098 }
1099}
1100
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +02001101// setFilter is like set, but allows the key-value pair to be filtered.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001102func (b *builder) setFilter(schema, key string, v ast.Expr) {
Marcel van Lohuizen7b7ccdd2019-07-03 13:33:03 +02001103 if re := b.ctx.fieldFilter; re != nil && re.MatchString(path.Join(schema, key)) {
1104 return
1105 }
1106 b.set(key, v)
1107}
1108
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001109// setSingle sets a value of which there should only be one.
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001110func (b *builder) setSingle(key string, v ast.Expr, drop bool) {
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001111 if b.singleFields == nil {
1112 b.singleFields = &OrderedMap{}
1113 }
1114 if b.singleFields.exists(key) {
1115 if !drop {
1116 b.failf(cue.Value{}, "more than one value added for key %q", key)
1117 }
1118 }
1119 b.singleFields.Set(key, v)
1120}
1121
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001122func (b *builder) set(key string, v ast.Expr) {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001123 if b.current == nil {
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001124 b.current = &OrderedMap{}
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001125 b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
Marcel van Lohuizen865f1592019-07-02 19:43:45 +02001126 } else if b.current.exists(key) {
1127 b.current = &OrderedMap{}
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001128 b.allOf = append(b.allOf, (*ast.StructLit)(b.current))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001129 }
Marcel van Lohuizenc89f5a62019-07-04 10:41:18 +02001130 b.current.Set(key, v)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001131}
1132
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001133func (b *builder) kv(key string, value ast.Expr) *ast.StructLit {
1134 return ast.NewStruct(key, value)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001135}
1136
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001137func (b *builder) setNot(key string, value ast.Expr) {
1138 b.add(ast.NewStruct("not", b.kv(key, value)))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001139}
1140
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001141func (b *builder) finish() *ast.StructLit {
1142 var t *OrderedMap
1143
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001144 if b.filled != nil {
1145 return b.filled
1146 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001147 switch len(b.allOf) {
1148 case 0:
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001149 t = &OrderedMap{}
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001150
1151 case 1:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001152 t = (*OrderedMap)(b.allOf[0])
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001153
1154 default:
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001155 exprs := []ast.Expr{}
1156 for _, s := range b.allOf {
1157 exprs = append(exprs, s)
1158 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001159 t = &OrderedMap{}
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001160 t.Set("allOf", ast.NewList(exprs...))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001161 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001162 if b.singleFields != nil {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001163 b.singleFields.Elts = append(b.singleFields.Elts, t.Elts...)
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001164 t = b.singleFields
1165 }
Jason Wang2b56efa2019-08-21 13:35:53 -07001166 if b.deprecated {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001167 t.Set("deprecated", ast.NewBool(true))
Jason Wang2b56efa2019-08-21 13:35:53 -07001168 }
Marcel van Lohuizen9fad62f2019-08-13 01:39:10 +02001169 setType(t, b)
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001170 sortSchema((*ast.StructLit)(t))
1171 return (*ast.StructLit)(t)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001172}
1173
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001174func (b *builder) add(t *ast.StructLit) {
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001175 b.allOf = append(b.allOf, t)
1176}
1177
1178func (b *builder) addConjunct(f func(*builder)) {
1179 c := newOASBuilder(b)
1180 f(c)
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001181 b.add((*ast.StructLit)(c.finish()))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001182}
1183
Marcel van Lohuizen065bfd42019-06-29 12:33:48 +02001184func (b *builder) addRef(v cue.Value, inst *cue.Instance, ref []string) {
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +02001185 name := b.ctx.makeRef(inst, ref)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001186 b.addConjunct(func(b *builder) {
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001187 b.set("$ref", ast.NewString(path.Join("#", b.ctx.refPrefix, name)))
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001188 })
Marcel van Lohuizenef90d5c2019-06-29 14:05:25 +02001189
1190 if b.ctx.inst != inst {
1191 b.ctx.externalRefs[name] = &externalType{
1192 ref: name,
1193 inst: inst,
1194 path: ref,
1195 value: v,
1196 }
1197 }
1198}
1199
1200func (b *buildContext) makeRef(inst *cue.Instance, ref []string) string {
1201 a := make([]string, 0, len(ref)+3)
1202 if b.nameFunc != nil {
1203 a = append(a, b.nameFunc(inst, ref))
1204 } else {
1205 a = append(a, ref...)
1206 }
Marcel van Lohuizen439f8572020-03-18 12:07:54 +01001207 return strings.Join(a, ".")
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001208}
1209
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001210func (b *builder) int64(v cue.Value) int64 {
1211 v, _ = v.Default()
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001212 i, err := v.Int64()
1213 if err != nil {
Marcel van Lohuizenebf960e2019-06-25 17:48:03 +02001214 b.failf(v, "could not retrieve int: %v", err)
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001215 }
1216 return i
1217}
1218
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001219func (b *builder) intExpr(i int64) ast.Expr {
1220 return &ast.BasicLit{
1221 Kind: token.INT,
1222 Value: fmt.Sprint(i),
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001223 }
Marcel van Lohuizenf4d483e2019-05-20 08:03:10 -04001224}
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +02001225
Marcel van Lohuizenf51c8792020-03-05 17:53:29 +01001226func (b *builder) int(v cue.Value) ast.Expr {
1227 return b.intExpr(b.int64(v))
1228}
1229
1230func (b *builder) inta(v cue.Value, offset int64) ast.Expr {
1231 return b.intExpr(b.int64(v) + offset)
1232}
1233
1234func (b *builder) decode(v cue.Value) ast.Expr {
1235 v, _ = v.Default()
1236 return v.Syntax().(ast.Expr)
1237}
1238
1239func (b *builder) big(v cue.Value) ast.Expr {
1240 v, _ = v.Default()
1241 return v.Syntax().(ast.Expr)
Marcel van Lohuizena0d2a402019-06-29 14:07:41 +02001242}