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