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