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))) }