cue: observe JSON mappings for google.protobuf.Struct

Change-Id: I234767c9bcdb67dee3492827cf598f5a45258959
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2681
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go
index b3e7451..48617c9 100644
--- a/encoding/protobuf/parse.go
+++ b/encoding/protobuf/parse.go
@@ -330,7 +330,9 @@
 		return err
 	}
 
-	p.mapBuiltinPackage(v.Position, v.Filename, filename == "")
+	if !p.mapBuiltinPackage(v.Position, v.Filename, filename == "") {
+		return nil
+	}
 
 	imp, err := p.state.parse(filename, nil)
 	if err != nil {
diff --git a/encoding/protobuf/protobuf.go b/encoding/protobuf/protobuf.go
index d813438..d1cdad0 100644
--- a/encoding/protobuf/protobuf.go
+++ b/encoding/protobuf/protobuf.go
@@ -15,6 +15,36 @@
 // Package protobuf defines functionality for parsing protocol buffer
 // definitions and instances.
 //
+// Proto definition mapping follows the guidelines of mapping Proto to JSON as
+// discussed in https://developers.google.com/protocol-buffers/docs/proto3,
+// and carries some of the mapping further when possible with CUE.
+//
+// The following type mappings of defintions apply:
+//
+//   Proto type     CUE type/def   Comments
+//   message        struct         Message fields become CUE fields, whereby
+//                                 names are mapped to lowerCamelCase.
+//   enum           e1 | e2 | ...  Where ex are strings. A separate mapping is
+//                                 generated to obtain the numeric values.
+//   map<K, V>      { <>: V }      All keys are converted to strings.
+//   repeated V     [...V]         null is accepted as the empty list [].
+//   bool           bool
+//   string         string
+//   bytes          bytes          A base64-encoded string when converted to JSON.
+//   int32, fixed32 int32          An integer with bounds as defined by int32.
+//   uint32         uint32         An integer with bounds as defined by uint32.
+//   int64, fixed64 int64          An integer with bounds as defined by int64.
+//   uint64         uint64         An integer with bounds as defined by uint64.
+//   float          float32        A number with bounds as defined by float32.
+//   double         float64        A number with bounds as defined by float64.
+//   Struct         struct         See struct.proto.
+//   Value          _              See struct.proto.
+//   ListValue      [...]          See struct.proto.
+//   BoolValue      bool           See struct.proto.
+//   StringValue    string         See struct.proto.
+//   NumberValue    number         See struct.proto.
+//   StringValue    string         See struct.proto.
+//
 // Protobuf definitions can be annotated with CUE constraints that are
 // included in the generated CUE:
 //    (cue.val)     string        CUE expression defining a constraint for this
@@ -27,6 +57,14 @@
 //
 package protobuf
 
+// TODO mappings:
+// Timestamp	string	"1972-01-01T10:00:20.021Z"	Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits. Offsets other than "Z" are also accepted.
+// Duration	string	"1.000340012s", "1s"	Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision, followed by the suffix "s". Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision and the suffix "s" is required.
+// Empty	object	{}
+//
+// Wrapper types	various types	2, "2", "foo", true, "true", null, 0, …	Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer.
+// FieldMask	string	"f.fooBar,h"	See field_mask.proto.
+
 import (
 	"os"
 	"path/filepath"
diff --git a/encoding/protobuf/testdata/attributes.proto.out.cue b/encoding/protobuf/testdata/attributes.proto.out.cue
index 9c13d65..dc5a1b1 100644
--- a/encoding/protobuf/testdata/attributes.proto.out.cue
+++ b/encoding/protobuf/testdata/attributes.proto.out.cue
@@ -19,6 +19,15 @@
 	"github.com/golang/protobuf/ptypes/timestamp"
 )
 
+StructWrap: {
+	struct?:    {}     @protobuf(1,type=google.protobuf.Struct)
+	any?:       _      @protobuf(2,type=google.protobuf.Value)
+	listVal?:   [...]  @protobuf(3,type=google.protobuf.ListValue)
+	boolVal?:   bool   @protobuf(4,type=google.protobuf.BoolValue)
+	stringVal?: string @protobuf(5,type=google.protobuf.StringValue)
+	numberVal?: number @protobuf(6,type=google.protobuf.NumberValue)
+}
+
 //  Attributes represents a set of typed name/value pairs. Many of Mixer's
 //  API either consume and/or return attributes.
 // 
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto
index 32fa4fc..780005c 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto
@@ -21,6 +21,7 @@
 import "gogoproto/gogo.proto";
 import "google/protobuf/duration.proto";
 import "google/protobuf/timestamp.proto";
+import "google/protobuf/struct.proto";
 
 option (gogoproto.goproto_getters_all) = false;
 option (gogoproto.equal_all) = false;
@@ -28,6 +29,16 @@
 option (gogoproto.stable_marshaler_all) = true;
 option cc_enable_arenas = true;
 
+message StructWrap {
+  google.protobuf.Struct struct = 1;
+
+  google.protobuf.Value any = 2;
+  google.protobuf.ListValue listVal = 3;
+  google.protobuf.BoolValue boolVal = 4;
+  google.protobuf.StringValue stringVal = 5;
+  google.protobuf.NumberValue numberVal = 6;
+}
+
 // Attributes represents a set of typed name/value pairs. Many of Mixer's
 // API either consume and/or return attributes.
 //
diff --git a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
index 9c13d65..df7d3ae 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes_proto_gen.cue
@@ -19,6 +19,15 @@
 	"github.com/golang/protobuf/ptypes/timestamp"
 )
 
+StructWrap: {
+	struct?: {} @protobuf(1,type=google.protobuf.Struct)
+	any?: _ @protobuf(2,type=google.protobuf.Value)
+	listVal?: [...] @protobuf(3,type=google.protobuf.ListValue)
+	boolVal?:   bool   @protobuf(4,type=google.protobuf.BoolValue)
+	stringVal?: string @protobuf(5,type=google.protobuf.StringValue)
+	numberVal?: number @protobuf(6,type=google.protobuf.NumberValue)
+}
+
 //  Attributes represents a set of typed name/value pairs. Many of Mixer's
 //  API either consume and/or return attributes.
 // 
diff --git a/encoding/protobuf/types.go b/encoding/protobuf/types.go
index c6c8548..f54708e 100644
--- a/encoding/protobuf/types.go
+++ b/encoding/protobuf/types.go
@@ -48,24 +48,40 @@
 	"bytes":  "bytes",
 }
 
-var timePkg = &protoConverter{
-	id:        "time",
-	goPkg:     "time",
-	goPkgPath: "time",
-}
-
 func (p *protoConverter) setBuiltin(from, to string, pkg *protoConverter) {
 	p.scope[0][from] = mapping{to, "", pkg}
 }
 
-func (p *protoConverter) mapBuiltinPackage(pos scanner.Position, file string, required bool) {
+func (p *protoConverter) mapBuiltinPackage(pos scanner.Position, file string, required bool) (generate bool) {
 	// Map some builtin types to their JSON/CUE mappings.
 	switch file {
 	case "gogoproto/gogo.proto":
 
+	case "google/protobuf/struct.proto":
+		p.setBuiltin("google.protobuf.Struct", "{}", nil)
+		p.setBuiltin("google.protobuf.Value", "_", nil)
+		p.setBuiltin("google.protobuf.NullValue", "null", nil)
+		p.setBuiltin("google.protobuf.ListValue", "[...]", nil)
+		p.setBuiltin("google.protobuf.StringValue", "string", nil)
+		p.setBuiltin("google.protobuf.BoolValue", "bool", nil)
+		p.setBuiltin("google.protobuf.NumberValue", "number", nil)
+		return false
+
+	// TODO: consider mapping the following:
+
+	// case "google/protobuf/duration.proto":
+	// 	p.setBuiltin("google.protobuf.Duration", "time.Duration", "time")
+
+	// case "google/protobuf/timestamp.proto":
+	// 	p.setBuiltin("google.protobuf.Timestamp", "time.Time", "time")
+
+	// case "google/protobuf/empty.proto":
+	// 	p.setBuiltin("google.protobuf.Empty", "struct.MaxFields(0)", nil)
+
 	default:
 		if required {
 			failf(pos, "import %q not found", file)
 		}
 	}
+	return true
 }