From 15f243b1d23d5f60400f56eb677fc628b7844e5e Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Fri, 10 Jan 2025 17:24:10 +0000 Subject: [PATCH] encoding/jsonschema: add Config.AllowNonExistentRoot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AllowNonExistentRoot` prevents an error when there is no value at the above Root path. Such an error can be useful to signal that the data may not be a JSON Schema, but is not always a good idea. When extracting CUE from OpenAPI documents, they are not required to contain a .components.schemas member but in the usual case of using the command line, it's probably an error if there is no such member (because it might mean that we're not using the right kind of file at all, and we're probably expecting some schemas to be present otherwise we wouldn't be invoking the cue command to extract schemas). However, in some cases, we don't want to consider it an error, so add an option to cause us to ignore a missing root. Another possibility might be to change jsonschema.Extract to return a custom error type/value, but this means that it would be harder to change the cue command to import openapi files even as CUE even when there are no definitions. Also in passing fix an error message that was not including the error that it was about. Signed-off-by: Roger Peppe Change-Id: Ib9d6ef9be834a869000762bff9bc9cd4bc9bd115 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1207007 Reviewed-by: Daniel Martí TryBot-Result: CUEcueckoo Unity-Result: CUE porcuepine --- encoding/jsonschema/decode.go | 6 ++++-- encoding/jsonschema/decode_test.go | 1 + encoding/jsonschema/jsonschema.go | 6 ++++++ .../jsonschema/testdata/txtar/openapi_nonexistent.txtar | 9 +++++++++ .../testdata/txtar/openapi_nonexistent_error.txtar | 9 +++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 encoding/jsonschema/testdata/txtar/openapi_nonexistent.txtar create mode 100644 encoding/jsonschema/testdata/txtar/openapi_nonexistent_error.txtar diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go index ecd748bca4e..4c03bb32615 100644 --- a/encoding/jsonschema/decode.go +++ b/encoding/jsonschema/decode.go @@ -127,7 +127,9 @@ func (d *decoder) decode(v cue.Value) *ast.File { return nil } defsRoot = v.LookupPath(defsPath) - if kind := defsRoot.Kind(); kind != cue.StructKind { + if !defsRoot.Exists() && d.cfg.AllowNonExistentRoot { + defsRoot = v.Context().CompileString("{}") + } else if defsRoot.Kind() != cue.StructKind { d.errf(defsRoot, "value at path %v must be struct containing definitions but is actually %v", d.cfg.Root, defsRoot) return nil } @@ -904,7 +906,7 @@ func (s *state) addDefinition(n cue.Value) *definedSchema { loc.Path = relPath(n, s.root) importPath, path, err := s.cfg.MapRef(loc) if err != nil { - s.errf(n, "cannot get reference for %v", loc) + s.errf(n, "cannot get reference for %v: %v", loc, err) return nil } def = &definedSchema{ diff --git a/encoding/jsonschema/decode_test.go b/encoding/jsonschema/decode_test.go index 0b99efbda05..e1cf6ca34a1 100644 --- a/encoding/jsonschema/decode_test.go +++ b/encoding/jsonschema/decode_test.go @@ -100,6 +100,7 @@ func TestDecode(t *testing.T) { } cfg.Strict = t.HasTag("strict") cfg.StrictKeywords = cfg.StrictKeywords || t.HasTag("strictKeywords") + cfg.AllowNonExistentRoot = t.HasTag("allowNonExistentRoot") cfg.StrictFeatures = t.HasTag("strictFeatures") cfg.PkgName, _ = t.Value("pkgName") diff --git a/encoding/jsonschema/jsonschema.go b/encoding/jsonschema/jsonschema.go index 0dc7b14eb6b..14231f648da 100644 --- a/encoding/jsonschema/jsonschema.go +++ b/encoding/jsonschema/jsonschema.go @@ -124,6 +124,12 @@ type Config struct { // only. Just `#` is preferred. Root string + // AllowNonExistentRoot holds whether it's an error when there + // is no value at the above Root path. For example, when extracting + // an OpenAPI schema, the #/components/schemas path might not + // exist, but that could be considered OK even so. + AllowNonExistentRoot bool + // Map maps the locations of schemas and definitions to a new location. // References are updated accordingly. A returned label must be // an identifier or string literal. diff --git a/encoding/jsonschema/testdata/txtar/openapi_nonexistent.txtar b/encoding/jsonschema/testdata/txtar/openapi_nonexistent.txtar new file mode 100644 index 00000000000..5a8666e8d8a --- /dev/null +++ b/encoding/jsonschema/testdata/txtar/openapi_nonexistent.txtar @@ -0,0 +1,9 @@ +Test what happens when there's an OpenAPI schema that has no +/components/schemas entry but AllowNonExistentRoot is true + +#allowNonExistentRoot +#version: openapi + +-- schema.yaml -- +-- out/decode/extract -- + diff --git a/encoding/jsonschema/testdata/txtar/openapi_nonexistent_error.txtar b/encoding/jsonschema/testdata/txtar/openapi_nonexistent_error.txtar new file mode 100644 index 00000000000..573b3dd2aaf --- /dev/null +++ b/encoding/jsonschema/testdata/txtar/openapi_nonexistent_error.txtar @@ -0,0 +1,9 @@ +Test what happens when there's an OpenAPI schema that has no +/components/schemas entry and AllowNonExistentRoot is false + +#version: openapi + +-- schema.yaml -- +-- out/decode/extract -- +ERROR: +value at path #/components/schemas/ must be struct containing definitions but is actually _|_ // field not found: components