Skip to content

Commit de41586

Browse files
committed
encoding/jsonschema: export Version
When running the external tests, we want to be able to control which schema version is used even when there is no `$schema` field present, and this functionality is likely to be useful anyway, so export the `Version` type and provide a way to set the default version in the config. Also align the names more closely with the names used in the external JSON Schema test suite, as those are likely to be more conventional. Signed-off-by: Roger Peppe <[email protected]> Change-Id: I442e7eb4d8f26c2709458f7a733180acfc804c97 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200162 TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent df2c869 commit de41586

10 files changed

+112
-82
lines changed

encoding/jsonschema/constraints.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,31 @@ func init() {
5454
const numPhases = 5
5555

5656
var constraints = []*constraint{
57-
p2d("$comment", constraintComment, vfrom(versionDraft07)),
57+
p2d("$comment", constraintComment, vfrom(VersionDraft7)),
5858
p2("$defs", constraintAddDefinitions),
59-
p1d("$id", constraintID, vfrom(versionDraft06)),
59+
p1d("$id", constraintID, vfrom(VersionDraft6)),
6060
p0("$schema", constraintSchema),
6161
p2("$ref", constraintRef),
6262
p2("additionalItems", constraintAdditionalItems),
6363
p4("additionalProperties", constraintAdditionalProperties),
6464
p3("allOf", constraintAllOf),
6565
p3("anyOf", constraintAnyOf),
66-
p2d("const", constraintConst, vfrom(versionDraft06)),
67-
p1d("minContains", constraintMinContains, vfrom(version2019_09)),
68-
p1d("maxContains", constraintMaxContains, vfrom(version2019_09)),
69-
p2d("contains", constraintContains, vfrom(versionDraft06)),
70-
p2d("contentEncoding", constraintContentEncoding, vfrom(versionDraft07)),
71-
p2d("contentMediaType", constraintContentMediaType, vfrom(versionDraft07)),
66+
p2d("const", constraintConst, vfrom(VersionDraft6)),
67+
p1d("minContains", constraintMinContains, vfrom(VersionDraft2019_09)),
68+
p1d("maxContains", constraintMaxContains, vfrom(VersionDraft2019_09)),
69+
p2d("contains", constraintContains, vfrom(VersionDraft6)),
70+
p2d("contentEncoding", constraintContentEncoding, vfrom(VersionDraft7)),
71+
p2d("contentMediaType", constraintContentMediaType, vfrom(VersionDraft7)),
7272
p2("default", constraintDefault),
7373
p2("definitions", constraintAddDefinitions),
7474
p2("dependencies", constraintDependencies),
7575
p2("deprecated", constraintDeprecated),
7676
p2("description", constraintDescription),
7777
p2("enum", constraintEnum),
78-
p2d("examples", constraintExamples, vfrom(versionDraft06)),
78+
p2d("examples", constraintExamples, vfrom(VersionDraft6)),
7979
p2("exclusiveMaximum", constraintExclusiveMaximum),
8080
p2("exclusiveMinimum", constraintExclusiveMinimum),
81-
p1d("id", constraintID, vto(versionDraft04)),
81+
p1d("id", constraintID, vto(VersionDraft4)),
8282
p2("items", constraintItems),
8383
p2("minItems", constraintMinItems),
8484
p2("maxItems", constraintMaxItems),
@@ -93,7 +93,7 @@ var constraints = []*constraint{
9393
p2("pattern", constraintPattern),
9494
p3("patternProperties", constraintPatternProperties),
9595
p2("properties", constraintProperties),
96-
p2d("propertyNames", constraintPropertyNames, vfrom(versionDraft06)),
96+
p2d("propertyNames", constraintPropertyNames, vfrom(VersionDraft6)),
9797
p3("required", constraintRequired),
9898
p2("title", constraintTitle),
9999
p2("type", constraintType),

encoding/jsonschema/constraints_meta.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func constraintSchema(key string, n cue.Value, s *state) {
5151
// If there's no $schema value, use the default.
5252
return
5353
}
54-
sv, err := parseSchemaVersion(str)
54+
sv, err := ParseVersion(str)
5555
if err != nil {
5656
s.errf(n, "invalid $schema URL %q: %v", str, err)
5757
return

encoding/jsonschema/decode.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (d *decoder) decode(v cue.Value) *ast.File {
111111
func (d *decoder) schema(ref []ast.Label, v cue.Value) (a []ast.Decl) {
112112
root := state{
113113
decoder: d,
114-
schemaVersion: defaultVersion,
114+
schemaVersion: d.cfg.DefaultVersion,
115115
}
116116

117117
var name ast.Label
@@ -379,7 +379,7 @@ type state struct {
379379
minContains *uint64
380380
maxContains *uint64
381381

382-
schemaVersion schemaVersion
382+
schemaVersion Version
383383
schemaVersionPresent bool
384384

385385
id *url.URL // base URI for $ref
@@ -653,8 +653,8 @@ func (s *state) schemaState(n cue.Value, types cue.Kind, idRef []label, isLogica
653653
state.parent = s
654654
}
655655
if n.Kind() == cue.BoolKind {
656-
if vfrom(versionDraft06).contains(state.schemaVersion) {
657-
// From draft-06 onwards, boolean values signify a schema that always passes or fails.
656+
if vfrom(VersionDraft6).contains(state.schemaVersion) {
657+
// From draft6 onwards, boolean values signify a schema that always passes or fails.
658658
if state.boolValue(n) {
659659
return top(), state
660660
}

encoding/jsonschema/decode_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ func TestDecode(t *testing.T) {
7575
return []ast.Label{ast.NewIdent("#" + a[len(a)-1])}, nil
7676
}
7777
}
78+
if versStr, ok := t.Value("version"); ok {
79+
vers, err := jsonschema.ParseVersion(versStr)
80+
qt.Assert(t, qt.IsNil(err))
81+
cfg.DefaultVersion = vers
82+
}
7883
cfg.Strict = t.HasTag("strict")
7984

8085
ctx := t.CueContext()

encoding/jsonschema/jsonschema.go

+18-3
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ import (
4444
// The generated CUE schema is guaranteed to deem valid any value that is
4545
// a valid instance of the source JSON schema.
4646
func Extract(data cue.InstanceOrValue, cfg *Config) (f *ast.File, err error) {
47+
cfg = ref(*cfg)
4748
if cfg.MapURL == nil {
48-
cfg1 := *cfg
49-
cfg = &cfg1
50-
cfg1.MapURL = DefaultMapURL
49+
cfg.MapURL = DefaultMapURL
50+
}
51+
if cfg.DefaultVersion == VersionUnknown {
52+
cfg.DefaultVersion = VersionDraft7
5153
}
5254
d := &decoder{
5355
cfg: cfg,
@@ -61,6 +63,10 @@ func Extract(data cue.InstanceOrValue, cfg *Config) (f *ast.File, err error) {
6163
return f, nil
6264
}
6365

66+
// DefaultVersion defines the default schema version used when
67+
// there is no $schema field and no explicit [Config.DefaultVersion].
68+
const DefaultVersion = VersionDraft7
69+
6470
// A Config configures a JSON Schema encoding or decoding.
6571
type Config struct {
6672
PkgName string
@@ -100,5 +106,14 @@ type Config struct {
100106
// them.
101107
Strict bool
102108

109+
// DefaultVersion holds the default schema version to use
110+
// when no $schema field is present. If it is zero, [DefaultVersion]
111+
// will be used.
112+
DefaultVersion Version
113+
103114
_ struct{} // prohibit casting from different type.
104115
}
116+
117+
func ref[T any](x T) *T {
118+
return &x
119+
}

encoding/jsonschema/schemaversion_string.go

-29
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#version: http://json-schema.org/draft-04/schema#
2+
#strict
3+
-- schema.json --
4+
{
5+
"$id": "http://example.test",
6+
"type": "string"
7+
}
8+
-- out/decode/extract --
9+
ERROR:
10+
constraint "$id" is not supported in JSON schema version http://json-schema.org/draft-04/schema#:
11+
schema.json:2:3

encoding/jsonschema/version.go

+18-19
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,58 @@ import (
44
"fmt"
55
)
66

7-
//go:generate go run golang.org/x/tools/cmd/stringer -type=schemaVersion -linecomment
7+
//go:generate go run golang.org/x/tools/cmd/stringer -type=Version -linecomment
88

9-
type schemaVersion int
9+
type Version int
1010

1111
const (
12-
versionUnknown schemaVersion = iota // unknown
13-
versionDraft04 // http://json-schema.org/draft-04/schema#
14-
// Note: draft 05 never existed and should not be used.
15-
versionDraft06 // http://json-schema.org/draft-06/schema#
16-
versionDraft07 // http://json-schema.org/draft-07/schema#
17-
version2019_09 // https://json-schema.org/draft/2019-09/schema
18-
version2020_12 // https://json-schema.org/draft/2020-12/schema
12+
VersionUnknown Version = iota // unknown
13+
VersionDraft4 // http://json-schema.org/draft-04/schema#
14+
// Note: draft 5 never existed and should not be used.
15+
VersionDraft6 // http://json-schema.org/draft-06/schema#
16+
VersionDraft7 // http://json-schema.org/draft-07/schema#
17+
VersionDraft2019_09 // https://json-schema.org/draft/2019-09/schema
18+
VersionDraft2020_12 // https://json-schema.org/draft/2020-12/schema
1919

2020
numVersions // unknown
2121
)
2222

23-
const defaultVersion = versionDraft07
24-
2523
type versionSet int
2624

27-
const allVersions = versionSet(1<<numVersions-1) &^ (1 << versionUnknown)
25+
const allVersions = versionSet(1<<numVersions-1) &^ (1 << VersionUnknown)
2826

2927
// contains reports whether m contains the version v.
30-
func (m versionSet) contains(v schemaVersion) bool {
28+
func (m versionSet) contains(v Version) bool {
3129
return (m & vset(v)) != 0
3230
}
3331

3432
// vset returns the version set containing exactly v.
35-
func vset(v schemaVersion) versionSet {
33+
func vset(v Version) versionSet {
3634
return 1 << v
3735
}
3836

3937
// vfrom returns the set of all versions starting at v.
40-
func vfrom(v schemaVersion) versionSet {
38+
func vfrom(v Version) versionSet {
4139
return allVersions &^ (vset(v) - 1)
4240
}
4341

4442
// vbetween returns the set of all versions between
4543
// v0 and v1 inclusive.
46-
func vbetween(v0, v1 schemaVersion) versionSet {
44+
func vbetween(v0, v1 Version) versionSet {
4745
return vfrom(v0) & vto(v1)
4846
}
4947

5048
// vto returns the set of all versions up to
5149
// and including v.
52-
func vto(v schemaVersion) versionSet {
50+
func vto(v Version) versionSet {
5351
return allVersions & (vset(v+1) - 1)
5452
}
5553

56-
func parseSchemaVersion(sv string) (schemaVersion, error) {
54+
// ParseVersion parses a version URI that defines a JSON Schema version.
55+
func ParseVersion(sv string) (Version, error) {
5756
// If this linear search is ever a performance issue, we could
5857
// build a map, but it doesn't seem worthwhile for now.
59-
for i := schemaVersion(1); i < numVersions; i++ {
58+
for i := Version(1); i < numVersions; i++ {
6059
if sv == i.String() {
6160
return i, nil
6261
}

encoding/jsonschema/version_string.go

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

encoding/jsonschema/version_test.go

+15-15
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,28 @@ import (
77
)
88

99
func TestVFrom(t *testing.T) {
10-
qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(versionDraft04)))
11-
qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(versionDraft06)))
12-
qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(version2020_12)))
13-
qt.Assert(t, qt.IsFalse(vfrom(versionDraft06).contains(versionDraft04)))
10+
qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft4)))
11+
qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft6)))
12+
qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft2020_12)))
13+
qt.Assert(t, qt.IsFalse(vfrom(VersionDraft6).contains(VersionDraft4)))
1414
}
1515

1616
func TestVTo(t *testing.T) {
17-
qt.Assert(t, qt.IsTrue(vto(versionDraft04).contains(versionDraft04)))
18-
qt.Assert(t, qt.IsFalse(vto(versionDraft04).contains(versionDraft06)))
19-
qt.Assert(t, qt.IsTrue(vto(versionDraft06).contains(versionDraft04)))
20-
qt.Assert(t, qt.IsFalse(vto(versionDraft06).contains(versionDraft07)))
17+
qt.Assert(t, qt.IsTrue(vto(VersionDraft4).contains(VersionDraft4)))
18+
qt.Assert(t, qt.IsFalse(vto(VersionDraft4).contains(VersionDraft6)))
19+
qt.Assert(t, qt.IsTrue(vto(VersionDraft6).contains(VersionDraft4)))
20+
qt.Assert(t, qt.IsFalse(vto(VersionDraft6).contains(VersionDraft7)))
2121
}
2222

2323
func TestVBetween(t *testing.T) {
24-
qt.Assert(t, qt.IsFalse(vbetween(versionDraft06, version2019_09).contains(versionDraft04)))
25-
qt.Assert(t, qt.IsTrue(vbetween(versionDraft06, version2019_09).contains(versionDraft06)))
26-
qt.Assert(t, qt.IsTrue(vbetween(versionDraft06, version2019_09).contains(version2019_09)))
27-
qt.Assert(t, qt.IsFalse(vbetween(versionDraft06, version2019_09).contains(version2020_12)))
24+
qt.Assert(t, qt.IsFalse(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft4)))
25+
qt.Assert(t, qt.IsTrue(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft6)))
26+
qt.Assert(t, qt.IsTrue(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft2019_09)))
27+
qt.Assert(t, qt.IsFalse(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft2020_12)))
2828
}
2929

3030
func TestVSet(t *testing.T) {
31-
qt.Assert(t, qt.IsTrue(vset(versionDraft06).contains(versionDraft06)))
32-
qt.Assert(t, qt.IsFalse(vset(versionDraft06).contains(versionDraft04)))
33-
qt.Assert(t, qt.IsFalse(vset(versionDraft06).contains(versionDraft07)))
31+
qt.Assert(t, qt.IsTrue(vset(VersionDraft6).contains(VersionDraft6)))
32+
qt.Assert(t, qt.IsFalse(vset(VersionDraft6).contains(VersionDraft4)))
33+
qt.Assert(t, qt.IsFalse(vset(VersionDraft6).contains(VersionDraft7)))
3434
}

0 commit comments

Comments
 (0)