Skip to content

Commit

Permalink
encoding/jsonschema: allow impossible subschemas
Browse files Browse the repository at this point in the history
Some real-world schemas contain self-contradictory elements, despite
being useful in practice. We change `encoding/jsonschema` so that it's
not necessarily an error to have a self-contradictory schema as long as
the top level schema itself is not self-contradictory.

This allows us to generate CUE for more real-world schemas, such as this
one:

https://github.com/SchemaStore/schemastore/blob/c084075dbfa7eb7da2c4c81456d04543b7be5744/src/schemas/json/stylelintrc.json#L173

We might add functionality to enable stricter vetting of such schemas in
the future, but for now, schema linting is not what we're aiming for.

Fixes #3455

Signed-off-by: Roger Peppe <[email protected]>
Change-Id: I60f5b56b7afd59d9e42ca93906f6f335b4500a39
Dispatch-Trailer: {"type":"trybot","CL":1201396,"patchset":4,"ref":"refs/changes/96/1201396/4","targetBranch":"master"}
  • Loading branch information
rogpeppe authored and cueckoo committed Sep 18, 2024
1 parent 2fceb5c commit cebcac7
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ import (

// The type of machine to run the job on. The machine can be
// either a GitHub-hosted runner, or a self-hosted runner.
"runs-on"!: matchN(>=1, [string, matchN(>=1, [[string] & [_, ...]]), {
"runs-on"!: matchN(>=1, [string, [string] & [_, ...] & [...], {
group?: string
labels?: matchN(1, [string, [...string]])
...
Expand Down
17 changes: 17 additions & 0 deletions encoding/jsonschema/constraints_combinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,23 @@ func constraintAnyOf(key string, n cue.Value, s *state) {
a := make([]ast.Expr, 0, len(items))
for _, v := range items {
x, sub := s.schemaState(v, s.allowedTypes, nil, true)
if sub.allowedTypes == 0 {
// Nothing is allowed; omit.
continue
}
types |= sub.allowedTypes
knownTypes |= sub.knownTypes
a = append(a, x)
}
if len(a) == 0 {
// Nothing at all is allowed.
s.allowedTypes = 0
return
}
if len(a) == 1 {
s.all.add(n, a[0])
return
}
s.allowedTypes &= types
s.knownTypes &= knownTypes
s.all.add(n, ast.NewCall(
Expand Down Expand Up @@ -106,6 +119,10 @@ func constraintOneOf(key string, n cue.Value, s *state) {
a := make([]ast.Expr, 0, len(items))
for _, v := range items {
x, sub := s.schemaState(v, s.allowedTypes, nil, true)
if sub.allowedTypes == 0 {
// Nothing is allowed; omit
continue
}
types |= sub.allowedTypes

// TODO: make more finegrained by making it two pass.
Expand Down
7 changes: 5 additions & 2 deletions encoding/jsonschema/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ func (d *decoder) schema(ref []ast.Label, v cue.Value) (a []ast.Decl) {
}

expr, state := root.schemaState(v, allTypes, nil, false)
if state.allowedTypes == 0 {
d.addErr(errors.Newf(state.pos.Pos(), "constraints are not possible to satisfy"))
}

tags := []string{}
if state.schemaVersionPresent {
Expand Down Expand Up @@ -485,8 +488,8 @@ const allTypes = cue.NullKind | cue.BoolKind | cue.NumberKind | cue.IntKind |
// finalize constructs a CUE type from the collected constraints.
func (s *state) finalize() (e ast.Expr) {
if s.allowedTypes == 0 {
// Nothing is possible.
s.addErr(errors.Newf(s.pos.Pos(), "constraints are not possible to satisfy"))
// Nothing is possible. This isn't a necessarily a problem, as
// we might be inside an allOf or oneOf with other valid constraints.
return bottom()
}
s.addIfThenElse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@
"$ref": "child1#my_anchor"
},
"skip": {
"v2": "extract error: keyword \"$anchor\" not yet implemented (and 4 more errors)",
"v3": "extract error: keyword \"$anchor\" not yet implemented (and 4 more errors)"
"v2": "extract error: keyword \"$anchor\" not yet implemented (and 2 more errors)",
"v3": "extract error: keyword \"$anchor\" not yet implemented (and 2 more errors)"
},
"tests": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@
"$ref": "child1#my_anchor"
},
"skip": {
"v2": "extract error: keyword \"$anchor\" not yet implemented (and 4 more errors)",
"v3": "extract error: keyword \"$anchor\" not yet implemented (and 4 more errors)"
"v2": "extract error: keyword \"$anchor\" not yet implemented (and 2 more errors)",
"v3": "extract error: keyword \"$anchor\" not yet implemented (and 2 more errors)"
},
"tests": [
{
Expand Down
17 changes: 17 additions & 0 deletions encoding/jsonschema/testdata/txtar/anyof_with_one_possible.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- schema.json --
{
"anyOf": [
{
"type": "string",
"enum": [
1,
2
]
},
{
"type": "boolean"
}
]
}
-- out/decode/extract --
bool
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- schema.json --
{
"anyOf": [
{
"type": "string",
"enum": [
1,
2
]
},
{
"type": "boolean"
},
{
"type": "number"
}
]
}
-- out/decode/extract --
matchN(>=1, [bool, number])
2 changes: 0 additions & 2 deletions encoding/jsonschema/testdata/txtar/impossibleallof.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@
ERROR:
constraints are not possible to satisfy:
schema.json:1:1
constraints are not possible to satisfy:
schema.json:4:9
17 changes: 17 additions & 0 deletions encoding/jsonschema/testdata/txtar/oneof_with_one_possible.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- schema.json --
{
"oneOf": [
{
"type": "string",
"enum": [
1,
2
]
},
{
"type": "boolean"
}
]
}
-- out/decode/extract --
bool

0 comments on commit cebcac7

Please sign in to comment.