encoding/jsonschema: fix type exclusion logic
The current implementation misinterpreted the spec
that constraints from a certain type imply a value is of
that type: it does not.
This means that if, for instance, a schema contains only
constraints for a struct, one must still explicitly allow all
other types besides struct in CUE. The new logic does so,
and also addes logic to opimize
Change-Id: I69fecd9b6888b0dad89db194c17acb56841de490
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5648
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/jsonschema/constraints.go b/encoding/jsonschema/constraints.go
index b7d4335..90a6df2 100644
--- a/encoding/jsonschema/constraints.go
+++ b/encoding/jsonschema/constraints.go
@@ -58,25 +58,6 @@
return &constraint{key: name, phase: 2, fn: f}
}
-func combineSequence(name string, n cue.Value, s *state, op token.Token, f func(n cue.Value) ast.Expr) {
- if n.Kind() != cue.ListKind {
- s.errf(n, `value of %q must be an array, found %v`, name, n.Kind())
- }
- var a ast.Expr
- for _, n := range list(n) {
- if a == nil {
- a = f(n)
- continue
- }
- a = ast.NewBinExpr(token.OR, a, f(n))
- }
- if a == nil {
- s.errf(n, `empty array for %q`, name)
- return
- }
- s.add(a)
-}
-
// TODO:
// writeOnly, readOnly
@@ -89,7 +70,6 @@
}
func addDefinitions(n cue.Value, s *state) {
- s.kind |= cue.StructKind
if n.Kind() != cue.StructKind {
s.errf(n, `"definitions" expected an object, found %v`, n.Kind)
}
@@ -134,29 +114,65 @@
// Generic constraint
p0("type", func(n cue.Value, s *state) {
+ var types cue.Kind
+ set := func(n cue.Value) {
+ str, ok := s.strValue(n)
+ if !ok {
+ s.errf(n, "type value should be a string")
+ }
+ switch str {
+ case "null":
+ types |= cue.NullKind
+ // TODO: handle OpenAPI restrictions.
+ case "boolean":
+ types |= cue.BoolKind
+ case "string":
+ types |= cue.StringKind
+ case "number":
+ types |= cue.NumberKind
+ case "integer":
+ types |= cue.IntKind
+ case "array":
+ types |= cue.ListKind
+ case "object":
+ types |= cue.StructKind
+
+ default:
+ s.errf(n, "unknown type %q", n)
+ }
+ }
+
switch n.Kind() {
case cue.StringKind:
- s.types = append(s.types, n)
+ set(n)
case cue.ListKind:
for i, _ := n.List(); i.Next(); {
- s.types = append(s.types, i.Value())
+ set(i.Value())
}
default:
s.errf(n, `value of "type" must be a string or list of strings`)
}
+
+ s.allowedTypes &= types
}),
p0("enum", func(n cue.Value, s *state) {
- combineSequence("enum", n, s, token.OR, s.value)
+ var a []ast.Expr
+ for _, x := range s.listItems("enum", n, true) {
+ a = append(a, s.value(x))
+ }
+ s.addConjunct(ast.NewBinExpr(token.OR, a...))
s.typeOptional = true
}),
p0d("const", 6, func(n cue.Value, s *state) {
- s.add(s.value(n))
+ s.addConjunct(s.value(n))
}),
p0("default", func(n cue.Value, s *state) {
+ allowed, used := s.allowedTypes, s.usedTypes
s.default_ = s.value(n)
+ s.allowedTypes, s.usedTypes = allowed, used
// must validate that the default is subsumed by the normal value,
// as CUE will otherwise broaden the accepted values with the default.
s.examples = append(s.examples, s.default_)
@@ -172,8 +188,10 @@
if n.Kind() != cue.ListKind {
s.errf(n, `value of "examples" must be an array, found %v`, n.Kind)
}
- for _, n := range list(n) {
- s.examples = append(s.examples, s.schema(n))
+ for _, n := range s.listItems("examples", n, true) {
+ if ex := s.schema(n); !isAny(ex) {
+ s.examples = append(s.examples, ex)
+ }
}
}),
@@ -191,16 +209,17 @@
p0("$def", addDefinitions),
p0("definitions", addDefinitions),
p0("$ref", func(n cue.Value, s *state) {
+ s.usedTypes = allTypes
str, _ := s.strValue(n)
a := s.parseRef(n.Pos(), str)
if a != nil {
a = s.mapRef(n.Pos(), str, a)
}
if a == nil {
- s.add(&ast.BadExpr{From: n.Pos()})
+ s.addConjunct(&ast.BadExpr{From: n.Pos()})
return
}
- s.add(ast.NewSel(ast.NewIdent(a[0]), a[1:]...))
+ s.addConjunct(ast.NewSel(ast.NewIdent(a[0]), a[1:]...))
}),
// Combinators
@@ -223,16 +242,59 @@
// This is not necessary if the values are mutually exclusive/ have a
// discriminator.
- p0("allOf", func(n cue.Value, s *state) {
- combineSequence("allOf", n, s, token.AND, s.schema)
+ p1("allOf", func(n cue.Value, s *state) {
+ var a []ast.Expr
+ for _, v := range s.listItems("allOf", n, false) {
+ x, sub := s.schemaState(v, s.allowedTypes, true)
+ s.allowedTypes &= sub.allowedTypes
+ s.usedTypes |= sub.usedTypes
+ if sub.hasConstraints() {
+ a = append(a, x)
+ }
+ }
+ if len(a) > 0 {
+ s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.AND, a...))
+ }
}),
- p0("anyOf", func(n cue.Value, s *state) {
- combineSequence("anyOf", n, s, token.OR, s.schema)
+ p1("anyOf", func(n cue.Value, s *state) {
+ var types cue.Kind
+ var a []ast.Expr
+ for _, v := range s.listItems("anyOf", n, false) {
+ x, sub := s.schemaState(v, s.allowedTypes, true)
+ types |= sub.allowedTypes
+ if sub.hasConstraints() {
+ a = append(a, x)
+ }
+ }
+ s.allowedTypes &= types
+ if len(a) > 0 {
+ s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.OR, a...))
+ }
}),
- p0("oneOf", func(n cue.Value, s *state) {
- combineSequence("oneOf", n, s, token.OR, s.schema)
+ p1("oneOf", func(n cue.Value, s *state) {
+ var types cue.Kind
+ var a []ast.Expr
+ hasSome := false
+ for _, v := range s.listItems("oneOf", n, false) {
+ x, sub := s.schemaState(v, s.allowedTypes, true)
+ types |= sub.allowedTypes
+
+ // TODO: make more finegrained by making it two pass.
+ if sub.hasConstraints() {
+ hasSome = true
+ }
+
+ if !isAny(x) {
+ a = append(a, x)
+ }
+ }
+ s.allowedTypes &= types
+ if len(a) > 0 && hasSome {
+ s.usedTypes = allTypes
+ s.conjuncts = append(s.conjuncts, ast.NewBinExpr(token.OR, a...))
+ }
// TODO: oneOf({a:x}, {b:y}, ..., not(anyOf({a:x}, {b:y}, ...))),
// can be translated to {} | {a:x}, {b:y}, ...
@@ -241,16 +303,16 @@
// String constraints
p0("pattern", func(n cue.Value, s *state) {
- s.kind |= cue.StringKind
- s.add(&ast.UnaryExpr{Op: token.MAT, X: s.string(n)})
+ s.usedTypes |= cue.StringKind
+ s.addConjunct(&ast.UnaryExpr{Op: token.MAT, X: s.string(n)})
}),
p0d("contentMediaType", 7, func(n cue.Value, s *state) {
- s.kind |= cue.StringKind
+ s.usedTypes |= cue.StringKind
}),
p0d("contentEncoding", 7, func(n cue.Value, s *state) {
- s.kind |= cue.StringKind
+ s.usedTypes |= cue.StringKind
// 7bit, 8bit, binary, quoted-printable and base64.
// RFC 2054, part 6.1.
// https://tools.ietf.org/html/rfc2045
@@ -260,29 +322,29 @@
// Number constraints
p0("minimum", func(n cue.Value, s *state) {
- s.kind |= cue.NumberKind
- s.add(&ast.UnaryExpr{Op: token.GEQ, X: s.number(n)})
+ s.usedTypes |= cue.NumberKind
+ s.addConjunct(&ast.UnaryExpr{Op: token.GEQ, X: s.number(n)})
}),
p0("exclusiveMinimum", func(n cue.Value, s *state) {
// TODO: should we support Draft 4 booleans?
- s.kind |= cue.NumberKind
- s.add(&ast.UnaryExpr{Op: token.GTR, X: s.number(n)})
+ s.usedTypes |= cue.NumberKind
+ s.addConjunct(&ast.UnaryExpr{Op: token.GTR, X: s.number(n)})
}),
p0("maximum", func(n cue.Value, s *state) {
- s.kind |= cue.NumberKind
- s.add(&ast.UnaryExpr{Op: token.LEQ, X: s.number(n)})
+ s.usedTypes |= cue.NumberKind
+ s.addConjunct(&ast.UnaryExpr{Op: token.LEQ, X: s.number(n)})
}),
p0("exclusiveMaximum", func(n cue.Value, s *state) {
// TODO: should we support Draft 4 booleans?
- s.kind |= cue.NumberKind
- s.add(&ast.UnaryExpr{Op: token.LSS, X: s.number(n)})
+ s.usedTypes |= cue.NumberKind
+ s.addConjunct(&ast.UnaryExpr{Op: token.LSS, X: s.number(n)})
}),
p0("multipleOf", func(n cue.Value, s *state) {
- s.kind |= cue.NumberKind
+ s.usedTypes |= cue.NumberKind
multiple := s.number(n)
var x big.Int
_, _ = n.MantExp(&x)
@@ -290,13 +352,14 @@
s.errf(n, `"multipleOf" value must be < 0; found %s`, n)
}
math := s.addImport("math")
- s.add(ast.NewCall(ast.NewSel(math, "MultipleOf"), multiple))
+ s.addConjunct(ast.NewCall(ast.NewSel(math, "MultipleOf"), multiple))
}),
// Object constraints
p0("properties", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
+
if s.obj == nil {
s.obj = &ast.StructLit{}
}
@@ -306,7 +369,7 @@
s.processMap(n, func(key string, n cue.Value) {
// property?: value
- expr, state := s.schemaState(n)
+ expr, state := s.schemaState(n, allTypes, false)
f := &ast.Field{Label: ast.NewString(key), Value: expr}
state.doc(f)
f.Optional = token.Blank.Pos()
@@ -328,12 +391,16 @@
}),
p1("required", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
- if s.obj == nil {
- s.errf(n, `"required" without a "properties" field`)
- }
+ s.usedTypes |= cue.StructKind
if n.Kind() != cue.ListKind {
s.errf(n, `value of "required" must be list of strings, found %v`, n.Kind)
+ return
+ }
+
+ if s.obj == nil {
+ s.obj = &ast.StructLit{}
+ // TODO: detect that properties is defined somewhere.
+ // s.errf(n, `"required" without a "properties" field`)
}
// Create field map
@@ -346,11 +413,16 @@
}
}
- for _, n := range list(n) {
+ for _, n := range s.listItems("required", n, true) {
str, ok := s.strValue(n)
f := fields[str]
if f == nil && ok {
- s.errf(n, "required field %q not in properties", str)
+ f := &ast.Field{
+ Label: ast.NewString(str),
+ Value: ast.NewIdent("_"),
+ }
+ fields[str] = f
+ s.obj.Elts = append(s.obj.Elts, f)
continue
}
if f.Optional == token.NoPos {
@@ -361,25 +433,31 @@
}),
p0d("propertyNames", 6, func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
+
// [=~pattern]: _
- s.add(ast.NewStruct(ast.NewList(s.schema(n)), ast.NewIdent("_")))
+ if names, _ := s.schemaState(n, cue.StringKind, false); !isAny(names) {
+ s.addConjunct(ast.NewStruct(ast.NewList((names)), ast.NewIdent("_")))
+ }
}),
p0("minProperties", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
+
pkg := s.addImport("struct")
- s.add(ast.NewCall(ast.NewSel(pkg, "MinFields"), s.uint(n)))
+ s.addConjunct(ast.NewCall(ast.NewSel(pkg, "MinFields"), s.uint(n)))
}),
p0("maxProperties", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
+
pkg := s.addImport("struct")
- s.add(ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n)))
+ s.addConjunct(ast.NewCall(ast.NewSel(pkg, "MaxFields"), s.uint(n)))
}),
p0("dependencies", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
+
// Schema and property dependencies.
// TODO: the easiest implementation is with comprehensions.
// The nicer implementation is with disjunctions. This has to be done
@@ -393,7 +471,7 @@
}),
p1("patternProperties", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
if n.Kind() != cue.StructKind {
s.errf(n, `value of "patternProperties" must be an an object, found %v`, n.Kind)
}
@@ -417,7 +495,7 @@
}),
p2("additionalProperties", func(n cue.Value, s *state) {
- s.kind |= cue.StructKind
+ s.usedTypes |= cue.StructKind
switch n.Kind() {
case cue.BoolKind:
s.closeStruct = !s.boolValue(n)
@@ -451,7 +529,7 @@
// Array constraints.
p0("items", func(n cue.Value, s *state) {
- s.kind |= cue.ListKind
+ s.usedTypes |= cue.ListKind
if s.list != nil {
s.errf(n, `"items" declared more than once, previous declaration at %s`, s.list.Pos())
}
@@ -459,16 +537,16 @@
case cue.StructKind:
elem := s.schema(n)
ast.SetRelPos(elem, token.NoRelPos)
- s.add(ast.NewList(&ast.Ellipsis{Type: elem}))
+ s.addConjunct(ast.NewList(&ast.Ellipsis{Type: elem}))
case cue.ListKind:
var a []ast.Expr
- for _, n := range list(n) {
+ for _, n := range s.listItems("items", n, true) {
v := s.schema(n)
ast.SetRelPos(v, token.NoRelPos)
a = append(a, v)
}
- s.add(ast.NewList(a...))
+ s.addConjunct(ast.NewList(a...))
default:
s.errf(n, `value of "items" must be an object or array`)
@@ -476,29 +554,31 @@
}),
p0("contains", func(n cue.Value, s *state) {
- s.kind |= cue.ListKind
+ s.usedTypes |= cue.ListKind
list := s.addImport("list")
// TODO: Passing non-concrete values is not yet supported in CUE.
- s.add(ast.NewCall(ast.NewSel(list, "Contains"), clearPos(s.schema(n))))
+ if x := s.schema(n); !isAny(x) {
+ s.addConjunct(ast.NewCall(ast.NewSel(list, "Contains"), clearPos(x)))
+ }
}),
p0("minItems", func(n cue.Value, s *state) {
- s.kind |= cue.ListKind
+ s.usedTypes |= cue.ListKind
list := s.addImport("list")
- s.add(ast.NewCall(ast.NewSel(list, "MinItems"), clearPos(s.uint(n))))
+ s.addConjunct(ast.NewCall(ast.NewSel(list, "MinItems"), clearPos(s.uint(n))))
}),
p0("maxItems", func(n cue.Value, s *state) {
- s.kind |= cue.ListKind
+ s.usedTypes |= cue.ListKind
list := s.addImport("list")
- s.add(ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n))))
+ s.addConjunct(ast.NewCall(ast.NewSel(list, "MaxItems"), clearPos(s.uint(n))))
}),
p0("uniqueItems", func(n cue.Value, s *state) {
- s.kind |= cue.ListKind
+ s.usedTypes |= cue.ListKind
if s.boolValue(n) {
list := s.addImport("list")
- s.add(ast.NewCall(ast.NewSel(list, "UniqueItems")))
+ s.addConjunct(ast.NewCall(ast.NewSel(list, "UniqueItems")))
}
}),
}
diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go
index 09838e6..bf58b4b 100644
--- a/encoding/jsonschema/decode.go
+++ b/encoding/jsonschema/decode.go
@@ -20,6 +20,7 @@
import (
"fmt"
+ "math/bits"
"sort"
"strings"
@@ -111,7 +112,7 @@
inner := len(ref) - 1
name := ref[inner]
- expr, state := root.schemaState(v)
+ expr, state := root.schemaState(v, allTypes, false)
tags := []string{}
if state.jsonschema != "" {
@@ -204,13 +205,15 @@
type state struct {
*decoder
+ parent *state
+
path []string
pos cue.Value
- types []cue.Value
typeOptional bool
- kind cue.Kind
+ usedTypes cue.Kind
+ allowedTypes cue.Kind
default_ ast.Expr
examples []ast.Expr
@@ -229,51 +232,56 @@
list *ast.ListLit
}
+func (s *state) hasConstraints() bool {
+ return len(s.conjuncts) > 0 ||
+ len(s.patterns) > 0 ||
+ s.title != "" ||
+ s.description != "" ||
+ s.obj != nil ||
+ s.list != nil
+}
+
+const allTypes = cue.NullKind | cue.BoolKind | cue.NumberKind | cue.IntKind |
+ cue.StringKind | cue.ListKind | cue.StructKind
+
// finalize constructs a CUE type from the collected constraints.
func (s *state) finalize() (e ast.Expr) {
- if s.typeOptional || s.kind != 0 {
- if len(s.types) > 1 {
- s.errf(s.pos, "constraints require specific type")
- }
- s.types = nil
- }
-
conjuncts := []ast.Expr{}
disjuncts := []ast.Expr{}
- for _, n := range s.types {
- add := func(e ast.Expr) {
- disjuncts = append(disjuncts, setPos(e, n))
- }
- str, ok := s.strValue(n)
- if !ok {
- s.errf(n, "type value should be a string")
- return
- }
- switch str {
- case "null":
+
+ add := func(e ast.Expr) {
+ disjuncts = append(disjuncts, e) // TODO: use setPos
+ }
+
+ types := s.allowedTypes &^ s.usedTypes
+ if types == allTypes {
+ add(ast.NewIdent("_"))
+ types = 0
+ }
+ if types&cue.FloatKind != 0 {
+ add(ast.NewIdent("number"))
+ types &^= cue.IntKind
+ }
+ for types != 0 {
+ k := cue.Kind(1 << uint(bits.TrailingZeros(uint(types))))
+ types &^= k
+
+ switch k {
+ case cue.NullKind:
// TODO: handle OpenAPI restrictions.
add(ast.NewIdent("null"))
- case "boolean":
+ case cue.BoolKind:
add(ast.NewIdent("bool"))
- case "string":
+ case cue.StringKind:
add(ast.NewIdent("string"))
- case "number":
- add(ast.NewIdent("number"))
- case "integer":
+ case cue.IntKind:
add(ast.NewIdent("int"))
- case "array":
- if s.kind&cue.ListKind == 0 {
- add(ast.NewList(&ast.Ellipsis{}))
- }
- case "object":
+ case cue.ListKind:
+ add(ast.NewList(&ast.Ellipsis{}))
+ case cue.StructKind:
add(ast.NewStruct(&ast.Ellipsis{}))
- default:
- s.errf(n, "unknown type %q", n)
}
}
- if len(disjuncts) > 0 {
- conjuncts = append(conjuncts, ast.NewBinExpr(token.OR, disjuncts...))
- }
conjuncts = append(conjuncts, s.conjuncts...)
@@ -284,17 +292,22 @@
conjuncts = append(conjuncts, s.obj)
}
- if len(conjuncts) == 0 {
- return ast.NewString(fmt.Sprint(s.pos))
+ if len(conjuncts) > 0 {
+ disjuncts = append(disjuncts,
+ ast.NewBinExpr(token.AND, conjuncts...))
}
- e = ast.NewBinExpr(token.AND, conjuncts...)
+ if len(disjuncts) == 0 {
+ e = &ast.BottomLit{}
+ } else {
+ e = ast.NewBinExpr(token.OR, disjuncts...)
+ }
if s.default_ != nil {
// check conditions where default can be skipped.
switch x := s.default_.(type) {
case *ast.ListLit:
- if s.kind == cue.ListKind && len(x.Elts) == 0 {
+ if s.usedTypes == cue.ListKind && len(x.Elts) == 0 {
return e
}
}
@@ -303,6 +316,11 @@
return e
}
+func isAny(s ast.Expr) bool {
+ i, ok := s.(*ast.Ident)
+ return ok && i.Name == "_"
+}
+
func (s *state) comment() *ast.CommentGroup {
// Create documentation.
doc := strings.TrimSpace(s.title)
@@ -327,18 +345,30 @@
}
}
-func (s *state) add(e ast.Expr) {
- s.conjuncts = append(s.conjuncts, e)
+func (s *state) addConjunct(e ast.Expr) {
+ if !isAny(e) {
+ s.conjuncts = append(s.conjuncts, e)
+ }
}
func (s *state) schema(n cue.Value) ast.Expr {
- expr, _ := s.schemaState(n)
+ expr, _ := s.schemaState(n, allTypes, false)
// TODO: report unused doc.
return expr
}
-func (s *state) schemaState(n cue.Value) (ast.Expr, *state) {
- state := &state{path: s.path, pos: n, decoder: s.decoder}
+// schemaState is a low-level API for schema. isLogical specifies whether the
+// caller is a logical operator like anyOf, allOf, oneOf, or not.
+func (s *state) schemaState(n cue.Value, types cue.Kind, isLogical bool) (ast.Expr, *state) {
+ state := &state{
+ decoder: s.decoder,
+ allowedTypes: types,
+ path: s.path,
+ pos: n,
+ }
+ if isLogical {
+ state.parent = s
+ }
if n.Kind() != cue.StructKind {
return s.errf(n, "schema expects mapping node, found %s", n.Kind()), state
@@ -365,7 +395,10 @@
}
func (s *state) value(n cue.Value) ast.Expr {
- switch n.Kind() {
+ k := n.Kind()
+ s.usedTypes |= k
+ s.allowedTypes &= k
+ switch k {
case cue.ListKind:
a := []ast.Expr{}
for i, _ := n.List(); i.Next(); {
@@ -409,10 +442,16 @@
}
}
-func list(n cue.Value) (a []cue.Value) {
+func (s *state) listItems(name string, n cue.Value, allowEmpty bool) (a []cue.Value) {
+ if n.Kind() != cue.ListKind {
+ s.errf(n, `value of %q must be an array, found %v`, name, n.Kind())
+ }
for i, _ := n.List(); i.Next(); {
a = append(a, i.Value())
}
+ if !allowEmpty && len(a) == 0 {
+ s.errf(n, `array for %q must be non-empty`, name)
+ }
return a
}
diff --git a/encoding/jsonschema/testdata/list.txtar b/encoding/jsonschema/testdata/list.txtar
index e76ca8e..f02810e 100644
--- a/encoding/jsonschema/testdata/list.txtar
+++ b/encoding/jsonschema/testdata/list.txtar
@@ -3,20 +3,24 @@
properties:
foo:
+ type: array
items:
type: string
tuple:
+ type: array
items:
- type: string
- type: integer
- const: 2
has:
+ type: array
contains:
const: 3
size:
+ type: array
minItems: 3
maxItems: 9
uniqueItems: true
diff --git a/encoding/jsonschema/testdata/num.txtar b/encoding/jsonschema/testdata/num.txtar
index 77236a1..a7e028a 100644
--- a/encoding/jsonschema/testdata/num.txtar
+++ b/encoding/jsonschema/testdata/num.txtar
@@ -13,10 +13,12 @@
"maximum": 3
},
"exclusive": {
+ "type": "number",
"exclusiveMinimum": 2,
"exclusiveMaximum": 3
},
"cents": {
+ "type": "number",
"multipleOf": 0.05
}
},
diff --git a/encoding/jsonschema/testdata/type.txtar b/encoding/jsonschema/testdata/type.txtar
index cd97a78..e851653 100644
--- a/encoding/jsonschema/testdata/type.txtar
+++ b/encoding/jsonschema/testdata/type.txtar
@@ -18,7 +18,10 @@
"numOrList": {
"oneOf": [
{ "type": "number" },
- { "items": { "type": "number" } }
+ {
+ "type": "array",
+ "items": { "type": "number" }
+ }
],
"default": [ 1, 2, 3 ]
}
@@ -30,9 +33,9 @@
// Main schema
Schema :: {
// an integer or string.
- intString?: string | int | bool | [...] | null
+ intString?: null | bool | int | string | [...]
object?: {
- ...
+ ...
} | *{
foo: "bar"
baz: 1.3
diff --git a/encoding/jsonschema/testdata/typedis.txtar b/encoding/jsonschema/testdata/typedis.txtar
new file mode 100644
index 0000000..d718d0c
--- /dev/null
+++ b/encoding/jsonschema/testdata/typedis.txtar
@@ -0,0 +1,56 @@
+-- type.json --
+{
+ "type": "object",
+ "title": "Main schema",
+
+ "properties": {
+ "intOrString1": {
+ "type": [ "string", "integer" ]
+ },
+ "intOrString2": {
+ "oneOf": [
+ { "type": "integer" },
+ { "type": "string" }
+ ]
+ },
+ "intOrString3": {
+ "anyOf": [
+ { "type": "integer" },
+ { "type": "string" }
+ ]
+ },
+
+ "disjunction": {
+ "oneOf": [
+ {
+ "anyOf": [
+ { "type": "integer" },
+ { "type": "string" }
+ ]
+ },
+ {
+ "type": "integer",
+ "minimum": 3
+ }
+ ]
+ },
+
+
+ "empty": {
+ "allOf": [
+ { "type": "object" },
+ { "type": "string" }
+ ]
+ }
+ }
+}
+-- out.cue --
+// Main schema
+Schema :: {
+ intOrString1?: int | string
+ intOrString2?: int | string
+ intOrString3?: int | string
+ disjunction?: int | string | >=3
+ empty?: _|_
+ ...
+}