cmd/cue/cmd: allow decoding JSON Schema and OpenAPI
Also added a test to ensure that format simplification
is not triggered inadventendly with this change.
Issue #56
Change-Id: Ie925de45a1cfe95f05f922dd04c47d0a8b42dd6c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5252
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/def_jsonschema.txt b/cmd/cue/cmd/testdata/script/def_jsonschema.txt
new file mode 100644
index 0000000..cfb587b
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/def_jsonschema.txt
@@ -0,0 +1,44 @@
+cue def jsonschema: schema.json -p schema -l '"Person"::'
+cmp stdout expect-stdout
+
+-- expect-stdout --
+package schema
+
+Person :: {
+ // Person
+ Schema :: {
+ // The person's first name.
+ firstName?: string
+
+ // The person's last name.
+ lastName?: string
+
+ // Age in years which must be equal to or greater than zero.
+ age?: >=0
+ ...
+ }
+}
+-- schema.json --
+{
+ "$id": "https://example.com/person.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Person",
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "description": "The person's first name."
+ },
+ "lastName": {
+ "type": "string",
+ "description": "The person's last name."
+ },
+ "age": {
+ "description": "Age in years which must be equal to or greater than zero.",
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+}
+
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/def_openapi.txt b/cmd/cue/cmd/testdata/script/def_openapi.txt
index cf62033..fa9cd51 100644
--- a/cmd/cue/cmd/testdata/script/def_openapi.txt
+++ b/cmd/cue/cmd/testdata/script/def_openapi.txt
@@ -1,11 +1,14 @@
cue def foo.cue -o openapi:-
-cmp stdout expect-json
+cmp stdout expect-json-out
cue def foo.cue -o openapi+cue:-
-cmp stdout expect-cue
+cmp stdout expect-cue-out
cue def foo.cue -o openapi+yaml:-
-cmp stdout expect-yaml
+cmp stdout expect-yaml-out
+
+cue def -p foo openapi: expect-json-out
+cmp stdout expect-cue
-- foo.cue --
Foo :: {
@@ -17,7 +20,7 @@
foo: Foo
}
--- expect-json --
+-- expect-json-out --
{
"openapi": "3.0.0",
"info": {},
@@ -55,30 +58,7 @@
}
}
}
--- expect-cue --
-openapi: "3.0.0"
-info: {}
-paths: {}
-components: schemas: {
- Foo: {
- type: "object"
- required: ["a", "b"]
- properties: {
- a: type: "integer"
- b: {
- type: "integer"
- minimum: 0
- exclusiveMaximum: 10
- }
- }
- }
- Bar: {
- type: "object"
- required: ["foo"]
- properties: foo: $ref: "#/components/schemas/Foo"
- }
-}
--- expect-yaml --
+-- expect-yaml-out --
openapi: 3.0.0
info: {}
paths: {}
@@ -103,3 +83,38 @@
properties:
foo:
$ref: '#/components/schemas/Foo'
+-- expect-cue-out --
+openapi: "3.0.0"
+info: {}
+paths: {}
+components: schemas: {
+ Foo: {
+ type: "object"
+ required: ["a", "b"]
+ properties: {
+ a: type: "integer"
+ b: {
+ type: "integer"
+ minimum: 0
+ exclusiveMaximum: 10
+ }
+ }
+ }
+ Bar: {
+ type: "object"
+ required: ["foo"]
+ properties: foo: $ref: "#/components/schemas/Foo"
+ }
+}
+-- expect-cue --
+package foo
+
+Foo :: {
+ a: int
+ b: >=0 & <10
+ ...
+}
+Bar :: {
+ foo: Foo
+ ...
+}
diff --git a/cmd/cue/cmd/testdata/script/fmt_stdin.txt b/cmd/cue/cmd/testdata/script/fmt_stdin.txt
index 4421470..99a0008 100644
--- a/cmd/cue/cmd/testdata/script/fmt_stdin.txt
+++ b/cmd/cue/cmd/testdata/script/fmt_stdin.txt
@@ -4,6 +4,8 @@
-- feed --
foo : 2
+a: {b: 3} // a comment
-- expect-stdout --
foo: 2
+a: {b: 3} // a comment
-- fmt/cue.mod --
diff --git a/cue/format/format.go b/cue/format/format.go
index 99767c0..7a4d557 100644
--- a/cue/format/format.go
+++ b/cue/format/format.go
@@ -15,6 +15,13 @@
// Package format implements standard formatting of CUE configurations.
package format // import "cuelang.org/go/cue/format"
+// TODO: this package is in need of a rewrite. When doing so, the API should
+// allow for reformatting an AST, without actually writing bytes.
+//
+// In essence, formatting determines the relative spacing to tokens. It should
+// be possible to have an abstract implementation providing such information
+// that can be used to either format or update an AST in a single walk.
+
import (
"bytes"
"fmt"
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index 04be952..2098102 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -204,6 +204,7 @@
if interpret == nil && e.encFile != nil {
return e.encFile(f)
}
+ e.autoSimplify = true
var r cue.Runtime
inst, err := r.CompileFile(f)
if err != nil {
diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go
index f6187e8..83de8a3 100644
--- a/internal/encoding/encoding.go
+++ b/internal/encoding/encoding.go
@@ -22,6 +22,7 @@
"fmt"
"io"
"io/ioutil"
+ "net/url"
"os"
"strings"
@@ -33,6 +34,8 @@
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
"cuelang.org/go/encoding/json"
+ "cuelang.org/go/encoding/jsonschema"
+ "cuelang.org/go/encoding/openapi"
"cuelang.org/go/encoding/protobuf"
"cuelang.org/go/internal/filetypes"
"cuelang.org/go/internal/third_party/yaml"
@@ -171,6 +174,39 @@
return i
}
+ switch f.Interpretation {
+ case "":
+ case build.OpenAPI:
+ i.interpret = func(i *cue.Instance) (file *ast.File, id string, err error) {
+ cfg := &openapi.Config{PkgName: cfg.PkgName}
+ file, err = simplify(openapi.Extract(i, cfg))
+ return file, "", err
+ }
+ case build.JSONSchema:
+ i.interpret = func(i *cue.Instance) (file *ast.File, id string, err error) {
+ id = f.Tags["id"]
+ if id == "" {
+ id, _ = i.Lookup("$id").String()
+ }
+ if id != "" {
+ u, err := url.Parse(id)
+ if err != nil {
+ return nil, "", errors.Wrapf(err, token.NoPos, "invalid id")
+ }
+ u.Scheme = ""
+ id = strings.TrimPrefix(u.String(), "//")
+ }
+ cfg := &jsonschema.Config{
+ ID: id,
+ PkgName: cfg.PkgName,
+ }
+ file, err = simplify(jsonschema.Extract(i, cfg))
+ return file, id, err
+ }
+ default:
+ i.err = fmt.Errorf("unsupported interpretation %q", f.Interpretation)
+ }
+
path := f.Filename
switch f.Encoding {
case build.CUE:
@@ -341,3 +377,19 @@
}
return ok
}
+
+// simplify reformats a File. To be used as a wrapper for Extract functions.
+//
+// It currently does so by formatting the file using fmt.Format and then
+// reparsing it. This is not ideal, but the package format does not provide a
+// way to do so differently.
+func simplify(f *ast.File, err error) (*ast.File, error) {
+ if err != nil {
+ return nil, err
+ }
+ b, err := format.Node(f, format.Simplify())
+ if err != nil {
+ return nil, err
+ }
+ return parser.ParseFile(f.Filename, b, parser.ParseComments)
+}