From de41586a37fbf6284f7a77f6b2e4e7101f523402 Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Wed, 28 Aug 2024 15:39:29 +0100 Subject: [PATCH] encoding/jsonschema: export Version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Change-Id: I442e7eb4d8f26c2709458f7a733180acfc804c97 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200162 TryBot-Result: CUEcueckoo Unity-Result: CUE porcuepine Reviewed-by: Daniel Martí --- encoding/jsonschema/constraints.go | 22 +++++------ encoding/jsonschema/constraints_meta.go | 2 +- encoding/jsonschema/decode.go | 8 ++-- encoding/jsonschema/decode_test.go | 5 +++ encoding/jsonschema/jsonschema.go | 21 +++++++++-- encoding/jsonschema/schemaversion_string.go | 29 --------------- .../testdata/txtar/defaultversion.txtar | 11 ++++++ encoding/jsonschema/version.go | 37 +++++++++---------- encoding/jsonschema/version_string.go | 29 +++++++++++++++ encoding/jsonschema/version_test.go | 30 +++++++-------- 10 files changed, 112 insertions(+), 82 deletions(-) delete mode 100644 encoding/jsonschema/schemaversion_string.go create mode 100644 encoding/jsonschema/testdata/txtar/defaultversion.txtar create mode 100644 encoding/jsonschema/version_string.go diff --git a/encoding/jsonschema/constraints.go b/encoding/jsonschema/constraints.go index 3f8e14bf236..b5446b71afe 100644 --- a/encoding/jsonschema/constraints.go +++ b/encoding/jsonschema/constraints.go @@ -54,31 +54,31 @@ func init() { const numPhases = 5 var constraints = []*constraint{ - p2d("$comment", constraintComment, vfrom(versionDraft07)), + p2d("$comment", constraintComment, vfrom(VersionDraft7)), p2("$defs", constraintAddDefinitions), - p1d("$id", constraintID, vfrom(versionDraft06)), + p1d("$id", constraintID, vfrom(VersionDraft6)), p0("$schema", constraintSchema), p2("$ref", constraintRef), p2("additionalItems", constraintAdditionalItems), p4("additionalProperties", constraintAdditionalProperties), p3("allOf", constraintAllOf), p3("anyOf", constraintAnyOf), - p2d("const", constraintConst, vfrom(versionDraft06)), - p1d("minContains", constraintMinContains, vfrom(version2019_09)), - p1d("maxContains", constraintMaxContains, vfrom(version2019_09)), - p2d("contains", constraintContains, vfrom(versionDraft06)), - p2d("contentEncoding", constraintContentEncoding, vfrom(versionDraft07)), - p2d("contentMediaType", constraintContentMediaType, vfrom(versionDraft07)), + p2d("const", constraintConst, vfrom(VersionDraft6)), + p1d("minContains", constraintMinContains, vfrom(VersionDraft2019_09)), + p1d("maxContains", constraintMaxContains, vfrom(VersionDraft2019_09)), + p2d("contains", constraintContains, vfrom(VersionDraft6)), + p2d("contentEncoding", constraintContentEncoding, vfrom(VersionDraft7)), + p2d("contentMediaType", constraintContentMediaType, vfrom(VersionDraft7)), p2("default", constraintDefault), p2("definitions", constraintAddDefinitions), p2("dependencies", constraintDependencies), p2("deprecated", constraintDeprecated), p2("description", constraintDescription), p2("enum", constraintEnum), - p2d("examples", constraintExamples, vfrom(versionDraft06)), + p2d("examples", constraintExamples, vfrom(VersionDraft6)), p2("exclusiveMaximum", constraintExclusiveMaximum), p2("exclusiveMinimum", constraintExclusiveMinimum), - p1d("id", constraintID, vto(versionDraft04)), + p1d("id", constraintID, vto(VersionDraft4)), p2("items", constraintItems), p2("minItems", constraintMinItems), p2("maxItems", constraintMaxItems), @@ -93,7 +93,7 @@ var constraints = []*constraint{ p2("pattern", constraintPattern), p3("patternProperties", constraintPatternProperties), p2("properties", constraintProperties), - p2d("propertyNames", constraintPropertyNames, vfrom(versionDraft06)), + p2d("propertyNames", constraintPropertyNames, vfrom(VersionDraft6)), p3("required", constraintRequired), p2("title", constraintTitle), p2("type", constraintType), diff --git a/encoding/jsonschema/constraints_meta.go b/encoding/jsonschema/constraints_meta.go index 149ea4103e3..811f07bfd1e 100644 --- a/encoding/jsonschema/constraints_meta.go +++ b/encoding/jsonschema/constraints_meta.go @@ -51,7 +51,7 @@ func constraintSchema(key string, n cue.Value, s *state) { // If there's no $schema value, use the default. return } - sv, err := parseSchemaVersion(str) + sv, err := ParseVersion(str) if err != nil { s.errf(n, "invalid $schema URL %q: %v", str, err) return diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go index d52ea128617..1469df570ac 100644 --- a/encoding/jsonschema/decode.go +++ b/encoding/jsonschema/decode.go @@ -111,7 +111,7 @@ func (d *decoder) decode(v cue.Value) *ast.File { func (d *decoder) schema(ref []ast.Label, v cue.Value) (a []ast.Decl) { root := state{ decoder: d, - schemaVersion: defaultVersion, + schemaVersion: d.cfg.DefaultVersion, } var name ast.Label @@ -379,7 +379,7 @@ type state struct { minContains *uint64 maxContains *uint64 - schemaVersion schemaVersion + schemaVersion Version schemaVersionPresent bool id *url.URL // base URI for $ref @@ -653,8 +653,8 @@ func (s *state) schemaState(n cue.Value, types cue.Kind, idRef []label, isLogica state.parent = s } if n.Kind() == cue.BoolKind { - if vfrom(versionDraft06).contains(state.schemaVersion) { - // From draft-06 onwards, boolean values signify a schema that always passes or fails. + if vfrom(VersionDraft6).contains(state.schemaVersion) { + // From draft6 onwards, boolean values signify a schema that always passes or fails. if state.boolValue(n) { return top(), state } diff --git a/encoding/jsonschema/decode_test.go b/encoding/jsonschema/decode_test.go index 9f64bcd5374..30788ed59ed 100644 --- a/encoding/jsonschema/decode_test.go +++ b/encoding/jsonschema/decode_test.go @@ -75,6 +75,11 @@ func TestDecode(t *testing.T) { return []ast.Label{ast.NewIdent("#" + a[len(a)-1])}, nil } } + if versStr, ok := t.Value("version"); ok { + vers, err := jsonschema.ParseVersion(versStr) + qt.Assert(t, qt.IsNil(err)) + cfg.DefaultVersion = vers + } cfg.Strict = t.HasTag("strict") ctx := t.CueContext() diff --git a/encoding/jsonschema/jsonschema.go b/encoding/jsonschema/jsonschema.go index e70f6e1cfb0..93e8bcadf7a 100644 --- a/encoding/jsonschema/jsonschema.go +++ b/encoding/jsonschema/jsonschema.go @@ -44,10 +44,12 @@ import ( // The generated CUE schema is guaranteed to deem valid any value that is // a valid instance of the source JSON schema. func Extract(data cue.InstanceOrValue, cfg *Config) (f *ast.File, err error) { + cfg = ref(*cfg) if cfg.MapURL == nil { - cfg1 := *cfg - cfg = &cfg1 - cfg1.MapURL = DefaultMapURL + cfg.MapURL = DefaultMapURL + } + if cfg.DefaultVersion == VersionUnknown { + cfg.DefaultVersion = VersionDraft7 } d := &decoder{ cfg: cfg, @@ -61,6 +63,10 @@ func Extract(data cue.InstanceOrValue, cfg *Config) (f *ast.File, err error) { return f, nil } +// DefaultVersion defines the default schema version used when +// there is no $schema field and no explicit [Config.DefaultVersion]. +const DefaultVersion = VersionDraft7 + // A Config configures a JSON Schema encoding or decoding. type Config struct { PkgName string @@ -100,5 +106,14 @@ type Config struct { // them. Strict bool + // DefaultVersion holds the default schema version to use + // when no $schema field is present. If it is zero, [DefaultVersion] + // will be used. + DefaultVersion Version + _ struct{} // prohibit casting from different type. } + +func ref[T any](x T) *T { + return &x +} diff --git a/encoding/jsonschema/schemaversion_string.go b/encoding/jsonschema/schemaversion_string.go deleted file mode 100644 index 17dd57bf0b2..00000000000 --- a/encoding/jsonschema/schemaversion_string.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by "stringer -type=schemaVersion -linecomment"; DO NOT EDIT. - -package jsonschema - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[versionUnknown-0] - _ = x[versionDraft04-1] - _ = x[versionDraft06-2] - _ = x[versionDraft07-3] - _ = x[version2019_09-4] - _ = x[version2020_12-5] - _ = x[numVersions-6] -} - -const _schemaVersion_name = "unknownhttp://json-schema.org/draft-04/schema#http://json-schema.org/draft-06/schema#http://json-schema.org/draft-07/schema#https://json-schema.org/draft/2019-09/schemahttps://json-schema.org/draft/2020-12/schemaunknown" - -var _schemaVersion_index = [...]uint8{0, 7, 46, 85, 124, 168, 212, 219} - -func (i schemaVersion) String() string { - if i < 0 || i >= schemaVersion(len(_schemaVersion_index)-1) { - return "schemaVersion(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _schemaVersion_name[_schemaVersion_index[i]:_schemaVersion_index[i+1]] -} diff --git a/encoding/jsonschema/testdata/txtar/defaultversion.txtar b/encoding/jsonschema/testdata/txtar/defaultversion.txtar new file mode 100644 index 00000000000..8a398963bce --- /dev/null +++ b/encoding/jsonschema/testdata/txtar/defaultversion.txtar @@ -0,0 +1,11 @@ +#version: http://json-schema.org/draft-04/schema# +#strict +-- schema.json -- +{ + "$id": "http://example.test", + "type": "string" +} +-- out/decode/extract -- +ERROR: +constraint "$id" is not supported in JSON schema version http://json-schema.org/draft-04/schema#: + schema.json:2:3 diff --git a/encoding/jsonschema/version.go b/encoding/jsonschema/version.go index 43a0e8df69f..fe8bb477f00 100644 --- a/encoding/jsonschema/version.go +++ b/encoding/jsonschema/version.go @@ -4,59 +4,58 @@ import ( "fmt" ) -//go:generate go run golang.org/x/tools/cmd/stringer -type=schemaVersion -linecomment +//go:generate go run golang.org/x/tools/cmd/stringer -type=Version -linecomment -type schemaVersion int +type Version int const ( - versionUnknown schemaVersion = iota // unknown - versionDraft04 // http://json-schema.org/draft-04/schema# - // Note: draft 05 never existed and should not be used. - versionDraft06 // http://json-schema.org/draft-06/schema# - versionDraft07 // http://json-schema.org/draft-07/schema# - version2019_09 // https://json-schema.org/draft/2019-09/schema - version2020_12 // https://json-schema.org/draft/2020-12/schema + VersionUnknown Version = iota // unknown + VersionDraft4 // http://json-schema.org/draft-04/schema# + // Note: draft 5 never existed and should not be used. + VersionDraft6 // http://json-schema.org/draft-06/schema# + VersionDraft7 // http://json-schema.org/draft-07/schema# + VersionDraft2019_09 // https://json-schema.org/draft/2019-09/schema + VersionDraft2020_12 // https://json-schema.org/draft/2020-12/schema numVersions // unknown ) -const defaultVersion = versionDraft07 - type versionSet int -const allVersions = versionSet(1<= Version(len(_Version_index)-1) { + return "Version(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Version_name[_Version_index[i]:_Version_index[i+1]] +} diff --git a/encoding/jsonschema/version_test.go b/encoding/jsonschema/version_test.go index 0c2dbc46f68..fe1a143627b 100644 --- a/encoding/jsonschema/version_test.go +++ b/encoding/jsonschema/version_test.go @@ -7,28 +7,28 @@ import ( ) func TestVFrom(t *testing.T) { - qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(versionDraft04))) - qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(versionDraft06))) - qt.Assert(t, qt.IsTrue(vfrom(versionDraft04).contains(version2020_12))) - qt.Assert(t, qt.IsFalse(vfrom(versionDraft06).contains(versionDraft04))) + qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft4))) + qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft6))) + qt.Assert(t, qt.IsTrue(vfrom(VersionDraft4).contains(VersionDraft2020_12))) + qt.Assert(t, qt.IsFalse(vfrom(VersionDraft6).contains(VersionDraft4))) } func TestVTo(t *testing.T) { - qt.Assert(t, qt.IsTrue(vto(versionDraft04).contains(versionDraft04))) - qt.Assert(t, qt.IsFalse(vto(versionDraft04).contains(versionDraft06))) - qt.Assert(t, qt.IsTrue(vto(versionDraft06).contains(versionDraft04))) - qt.Assert(t, qt.IsFalse(vto(versionDraft06).contains(versionDraft07))) + qt.Assert(t, qt.IsTrue(vto(VersionDraft4).contains(VersionDraft4))) + qt.Assert(t, qt.IsFalse(vto(VersionDraft4).contains(VersionDraft6))) + qt.Assert(t, qt.IsTrue(vto(VersionDraft6).contains(VersionDraft4))) + qt.Assert(t, qt.IsFalse(vto(VersionDraft6).contains(VersionDraft7))) } func TestVBetween(t *testing.T) { - qt.Assert(t, qt.IsFalse(vbetween(versionDraft06, version2019_09).contains(versionDraft04))) - qt.Assert(t, qt.IsTrue(vbetween(versionDraft06, version2019_09).contains(versionDraft06))) - qt.Assert(t, qt.IsTrue(vbetween(versionDraft06, version2019_09).contains(version2019_09))) - qt.Assert(t, qt.IsFalse(vbetween(versionDraft06, version2019_09).contains(version2020_12))) + qt.Assert(t, qt.IsFalse(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft4))) + qt.Assert(t, qt.IsTrue(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft6))) + qt.Assert(t, qt.IsTrue(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft2019_09))) + qt.Assert(t, qt.IsFalse(vbetween(VersionDraft6, VersionDraft2019_09).contains(VersionDraft2020_12))) } func TestVSet(t *testing.T) { - qt.Assert(t, qt.IsTrue(vset(versionDraft06).contains(versionDraft06))) - qt.Assert(t, qt.IsFalse(vset(versionDraft06).contains(versionDraft04))) - qt.Assert(t, qt.IsFalse(vset(versionDraft06).contains(versionDraft07))) + qt.Assert(t, qt.IsTrue(vset(VersionDraft6).contains(VersionDraft6))) + qt.Assert(t, qt.IsFalse(vset(VersionDraft6).contains(VersionDraft4))) + qt.Assert(t, qt.IsFalse(vset(VersionDraft6).contains(VersionDraft7))) }