cmd/cue/cmd: support openapi out and proto in
- Adds general support for non-CUE schema
Issue #56
Change-Id: I6ca639b2e0703cb2120f7661af24ee3111cdfe54
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5262
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index e0cf5c5..4e04652 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -405,6 +405,21 @@
"unsupported encoding %q", f.Encoding)
}
}
+
+ switch {
+ case len(p.insts) > 0 && len(p.orphanedSchema) > 0:
+ return nil, errors.Newf(token.NoPos,
+ "cannot define packages and schema")
+ case len(p.orphanedData) > 0 && len(p.orphanedSchema) > 1:
+ // TODO: allow this when schema have ID specified.
+ return nil, errors.Newf(token.NoPos,
+ "cannot define data with more than one schema")
+ case len(p.orphanedData) > 0 && len(p.orphanedSchema) == 1:
+ b.BuildFiles = append(b.BuildFiles, p.orphanedSchema...)
+ p.insts = append(p.insts, b)
+ case len(p.orphanedSchema) > 0:
+ p.orphanedData = p.orphanedSchema
+ }
}
if len(p.expressions) > 1 {
@@ -449,6 +464,7 @@
Stdout: b.cmd.OutOrStdout(),
ProtoPath: flagProtoPath.StringArray(b.cmd),
AllErrors: flagAllErrors.Bool(b.cmd),
+ PkgName: flagPackage.String(b.cmd),
}
return nil
}
diff --git a/cmd/cue/cmd/orphans.go b/cmd/cue/cmd/orphans.go
index 02b35d3..8687f8e 100644
--- a/cmd/cue/cmd/orphans.go
+++ b/cmd/cue/cmd/orphans.go
@@ -23,6 +23,7 @@
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
@@ -38,7 +39,6 @@
useList = flagList.Bool(b.cmd)
path = flagPath.StringArray(b.cmd)
useContext = flagWithContext.Bool(b.cmd)
- pkg = flagPackage.String(b.cmd)
match = flagGlob.String(b.cmd)
)
if !b.forceOrphanProcessing && !perFile && !useList && len(path) == 0 {
@@ -51,6 +51,7 @@
return false, err
}
+ pkg := b.encConfig.PkgName
if pkg == "" {
pkg = i.PkgName
} else if pkg != "" && i.PkgName != "" && i.PkgName != pkg && !flagForce.Bool(b.cmd) {
@@ -75,16 +76,13 @@
d := encoding.NewDecoder(f, b.encConfig)
defer d.Close()
- var objs []ast.Expr
+ var objs []*ast.File
for ; !d.Done(); d.Next() {
- if expr := d.Expr(); expr != nil {
+ if expr := d.File(); expr != nil {
objs = append(objs, expr)
continue
}
- f := d.File()
- f.Filename = newName(d.Filename(), d.Index())
- files = append(files, f)
}
if perFile {
@@ -126,13 +124,30 @@
return true, nil
}
-func placeOrphans(cmd *Command, filename, pkg string, objs ...ast.Expr) (*ast.File, error) {
+func toExpr(f *ast.File) (expr ast.Expr, pkg *ast.Package) {
+ var p int
+outer:
+ for i, d := range f.Decls {
+ switch x := d.(type) {
+ case *ast.Package:
+ pkg = x
+ case *ast.ImportDecl:
+ p = i + 1
+ case *ast.CommentGroup:
+ default:
+ break outer
+ }
+ }
+ return &ast.StructLit{Elts: f.Decls[p:]}, pkg
+}
+
+func placeOrphans(cmd *Command, filename, pkg string, objs ...*ast.File) (*ast.File, error) {
f := &ast.File{}
index := newIndex()
- for i, expr := range objs {
+ for i, file := range objs {
+ expr, p := toExpr(file)
- // Compute a path different from root.
var pathElems []ast.Label
var pathTokens []token.Token
@@ -200,6 +215,9 @@
field := &ast.Field{Label: pathElems[0]}
field.Token = pathTokens[0]
f.Decls = append(f.Decls, field)
+ if p != nil {
+ astutil.CopyComments(field, p)
+ }
for i, e := range pathElems[1:] {
newField := &ast.Field{Label: e}
newVal := ast.NewStruct(newField)
diff --git a/cmd/cue/cmd/testdata/script/def_openapi.txt b/cmd/cue/cmd/testdata/script/def_openapi.txt
new file mode 100644
index 0000000..cf62033
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/def_openapi.txt
@@ -0,0 +1,105 @@
+cue def foo.cue -o openapi:-
+cmp stdout expect-json
+
+cue def foo.cue -o openapi+cue:-
+cmp stdout expect-cue
+
+cue def foo.cue -o openapi+yaml:-
+cmp stdout expect-yaml
+
+-- foo.cue --
+Foo :: {
+ a: int
+ b: uint & <10
+}
+
+Bar :: {
+ foo: Foo
+}
+
+-- expect-json --
+{
+ "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 --
+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 --
+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'
diff --git a/cmd/cue/cmd/testdata/script/def_proto.txt b/cmd/cue/cmd/testdata/script/def_proto.txt
new file mode 100644
index 0000000..59fc070
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/def_proto.txt
@@ -0,0 +1,225 @@
+cue def policy.proto -p api -I include
+cmp stdout expect-stdout
+
+-- expect-stdout --
+// policy_gen.cue
+package api
+
+import "time"
+
+// Attributes defines attributes.
+Attributes: {
+ // A map of attribute name to its value.
+ attributes?: {
+ [string]: Attributes_AttributeValue
+ } @protobuf(1,type=map<string,AttributeValue>)
+}
+
+// Specifies one attribute value with different type.
+Attributes_AttributeValue: {
+}
+// The attribute value.
+Attributes_AttributeValue: {
+ stringValue: string @protobuf(2,name=string_value)
+} | {
+ int64Value: int64 @protobuf(3,name=int64_value)
+} | {
+ doubleValue: float64 @protobuf(4,type=double,name=double_value)
+} | {
+ boolValue: bool @protobuf(5,name=bool_value)
+} | {
+ bytesValue: bytes @protobuf(6,name=bytes_value)
+} | {
+ timestampValue: time.Time @protobuf(7,type=google.protobuf.Timestamp,name=timestamp_value)
+} | {
+ // Used for values of type STRING_MAP
+ stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value)
+}
+
+// Defines a string map.
+Attributes_StringMap: {
+ // Holds a set of name/value pairs.
+ entries?: {
+ [string]: string
+ } @protobuf(1,type=map<string,string>)
+}
+-- policy.proto --
+
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+
+package acme.api.v1;
+
+option go_package = "acme.com/api/v1";
+
+
+// Attributes defines attributes.
+message Attributes {
+ // A map of attribute name to its value.
+ map<string, AttributeValue> attributes = 1;
+
+ // Specifies one attribute value with different type.
+ message AttributeValue {
+ // The attribute value.
+ oneof value {
+ string string_value = 2;
+ int64 int64_value = 3;
+ double double_value = 4;
+ bool bool_value = 5;
+ bytes bytes_value = 6;
+ google.protobuf.Timestamp timestamp_value = 7;
+
+ // Used for values of type STRING_MAP
+ StringMap string_map_value = 9;
+ }
+ }
+
+ // Defines a string map.
+ message StringMap {
+ // Holds a set of name/value pairs.
+ map<string, string> entries = 1;
+ }
+}
+
+-- include/google/protobuf/timestamp.proto --
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/timestamp";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TimestampProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Timestamp represents a point in time independent of any time zone or local
+// calendar, encoded as a count of seconds and fractions of seconds at
+// nanosecond resolution. The count is relative to an epoch at UTC midnight on
+// January 1, 1970, in the proleptic Gregorian calendar which extends the
+// Gregorian calendar backwards to year one.
+//
+// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+// second table is needed for interpretation, using a [24-hour linear
+// smear](https://developers.google.com/time/smear).
+//
+// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+// restricting to that range, we ensure that we can convert to and from [RFC
+// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+//
+// # Examples
+//
+// Example 1: Compute Timestamp from POSIX `time()`.
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(time(NULL));
+// timestamp.set_nanos(0);
+//
+// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+//
+// struct timeval tv;
+// gettimeofday(&tv, NULL);
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(tv.tv_sec);
+// timestamp.set_nanos(tv.tv_usec * 1000);
+//
+// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+//
+// FILETIME ft;
+// GetSystemTimeAsFileTime(&ft);
+// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+//
+// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+// Timestamp timestamp;
+// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+//
+// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+//
+// long millis = System.currentTimeMillis();
+//
+// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+// .setNanos((int) ((millis % 1000) * 1000000)).build();
+//
+//
+// Example 5: Compute Timestamp from current time in Python.
+//
+// timestamp = Timestamp()
+// timestamp.GetCurrentTime()
+//
+// # JSON Mapping
+//
+// In JSON format, the Timestamp type is encoded as a string in the
+// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+// where {year} is always expressed using four digits while {month}, {day},
+// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+// is required. A proto3 JSON serializer should always use UTC (as indicated by
+// "Z") when printing the Timestamp type and a proto3 JSON parser should be
+// able to accept both UTC and other timezones (as indicated by an offset).
+//
+// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+// 01:30 UTC on January 15, 2017.
+//
+// In JavaScript, one can convert a Date object to this format using the
+// standard
+// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+// method. In Python, a standard `datetime.datetime` object can be converted
+// to this format using
+// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+// the Joda Time's [`ISODateTimeFormat.dateTime()`](
+// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+// ) to obtain a formatter capable of generating timestamps in this format.
+//
+//
+message Timestamp {
+ // Represents seconds of UTC time since Unix epoch
+ // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ // 9999-12-31T23:59:59Z inclusive.
+ int64 seconds = 1;
+
+ // Non-negative fractions of a second at nanosecond resolution. Negative
+ // second values with fractions must still have non-negative nanos values
+ // that count forward in time. Must be from 0 to 999,999,999
+ // inclusive.
+ int32 nanos = 2;
+}
+
+-- cue.mod --
diff --git a/cue/build/file.go b/cue/build/file.go
index 1ce636a..0e6d09b 100644
--- a/cue/build/file.go
+++ b/cue/build/file.go
@@ -35,7 +35,7 @@
YAML Encoding = "yaml"
JSONL Encoding = "jsonl"
Text Encoding = "text"
- Protobuf Encoding = "protobuf"
+ Protobuf Encoding = "proto"
// TODO:
// TOML
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
index 0217af9..04be952 100644
--- a/internal/encoding/encoder.go
+++ b/internal/encoding/encoder.go
@@ -27,6 +27,7 @@
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/format"
"cuelang.org/go/cue/token"
+ "cuelang.org/go/encoding/openapi"
"cuelang.org/go/internal/filetypes"
"cuelang.org/go/pkg/encoding/yaml"
)
@@ -62,12 +63,12 @@
switch f.Interpretation {
case "":
- // case build.OpenAPI:
- // // TODO: get encoding options
- // cfg := openapi.Config{}
- // i.interpret = func(inst *cue.Instance) (*ast.File, error) {
- // return openapi.Generate(inst, cfg)
- // }
+ case build.OpenAPI:
+ // TODO: get encoding options
+ cfg := &openapi.Config{}
+ e.interpret = func(i *cue.Instance) (*ast.File, error) {
+ return openapi.Generate(i, cfg)
+ }
// case build.JSONSchema:
// // TODO: get encoding options
// cfg := openapi.Config{}
@@ -131,7 +132,6 @@
e.encFile = func(f *ast.File) error { return format(f.Filename, f) }
case build.JSON, build.JSONL:
- // SetEscapeHTML
d := json.NewEncoder(w)
d.SetIndent("", " ")
d.SetEscapeHTML(cfg.EscapeHTML)
diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go
index f2a936a..f6187e8 100644
--- a/internal/encoding/encoding.go
+++ b/internal/encoding/encoding.go
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// TODO: make this package public in cuelang.org/go/encoding
+// once stabalized.
+
package encoding
import (
@@ -36,25 +39,47 @@
)
type Decoder struct {
- cfg *Config
- closer io.Closer
- next func() (ast.Expr, error)
- expr ast.Expr
- file *ast.File
- filename string // may change on iteration for some formats
- index int
- err error
+ cfg *Config
+ closer io.Closer
+ next func() (ast.Expr, error)
+ interpret func(*cue.Instance) (file *ast.File, id string, err error)
+ expr ast.Expr
+ file *ast.File
+ filename string // may change on iteration for some formats
+ id string
+ index int
+ err error
}
-func (i *Decoder) Expr() ast.Expr { return i.expr }
+// ID returns a canonical identifier for the decoded object or "" if no such
+// identifier could be found.
+func (i *Decoder) ID() string {
+ return i.id
+}
+
func (i *Decoder) Filename() string { return i.filename }
func (i *Decoder) Index() int { return i.index }
func (i *Decoder) Done() bool { return i.err != nil }
func (i *Decoder) Next() {
- if i.err == nil {
- i.expr, i.err = i.next()
- i.index++
+ if i.err != nil {
+ return
+ }
+ // Decoder level
+ i.expr, i.err = i.next()
+ i.index++
+ if i.err != nil {
+ return
+ }
+ // Interpretations
+ if i.interpret != nil {
+ var r cue.Runtime
+ inst, err := r.CompileFile(i.File())
+ if err != nil {
+ i.err = err
+ return
+ }
+ i.file, i.id, i.err = i.interpret(inst)
}
}
@@ -106,6 +131,8 @@
Stdin io.Reader
Stdout io.Writer
+ PkgName string // package name for files to generate
+
Force bool // overwrite existing files.
Stream bool // will potentially write more than one document per file
AllErrors bool
@@ -162,10 +189,13 @@
i.err = err
i.expr = ast.NewString(string(b))
case build.Protobuf:
- paths := &protobuf.Config{Paths: cfg.ProtoPath}
+ paths := &protobuf.Config{
+ Paths: cfg.ProtoPath,
+ PkgName: cfg.PkgName,
+ }
i.file, i.err = protobuf.Extract(path, r, paths)
default:
- i.err = fmt.Errorf("unsupported stream type %q", f.Encoding)
+ i.err = fmt.Errorf("unsupported encoding %q", f.Encoding)
}
return i