encoding/protobuf: provide default import path and short name

Put a proto file in googleapis.com by default if the
go_packagname is not provided.

googleapis.com without a subdomain is an invalid
domain and should thus be safe to use and will
always be resolved within a local pkg directory.
Alternatively, I could name it proto.cuelang.org
or proto.googleapis.com.

Note that the go_package mechanism is still
necessary to be able to determine a specific
destination of a package, for instance if it needs
to be merged with other CUE files within the same
directory (as the genoapi tool does).

Change-Id: I65484a0c940409b024160fa5d086bf27e3500894
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2820
Reviewed-by: Jason Wang <jasonwzm@google.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/encoding/protobuf/parse.go b/encoding/protobuf/parse.go
index 3c8713d..9647df4 100644
--- a/encoding/protobuf/parse.go
+++ b/encoding/protobuf/parse.go
@@ -101,16 +101,26 @@
 					failf(x.Position, "unquoting package filed: %v", err)
 				}
 				split := strings.Split(str, ";")
-				p.goPkgPath = split[0]
-				switch len(split) {
-				case 1:
-					p.goPkg = path.Base(str)
-				case 2:
-					p.goPkg = split[1]
+				switch {
+				case strings.Contains(split[0], "."):
+					p.cuePkgPath = split[0]
+					switch len(split) {
+					case 1:
+						p.shortPkgName = path.Base(str)
+					case 2:
+						p.shortPkgName = split[1]
+					default:
+						failf(x.Position, "unexpected ';' in %q", str)
+					}
+					p.file.Name = ast.NewIdent(p.shortPkgName)
+
+				case len(split) == 1:
+					p.shortPkgName = split[0]
+					p.file.Name = ast.NewIdent(p.shortPkgName)
+
 				default:
-					failf(x.Position, "unexpected ';' in %q", str)
+					failf(x.Position, "malformed go_package clause %s", str)
 				}
-				p.file.Name = ast.NewIdent(p.goPkg)
 				// name.AddComment(comment(x.Comment, true))
 				// name.AddComment(comment(x.InlineComment, false))
 			}
@@ -163,10 +173,10 @@
 
 	proto3 bool
 
-	id        string
-	protoPkg  string
-	goPkg     string
-	goPkgPath string
+	id           string
+	protoPkg     string
+	shortPkgName string
+	cuePkgPath   string
 
 	// w bytes.Buffer
 	file   *ast.File
@@ -187,6 +197,23 @@
 	pkg   *protoConverter
 }
 
+func (p *protoConverter) importPath() string {
+	if p.cuePkgPath == "" && p.protoPkg != "" {
+		dir := strings.Replace(p.protoPkg, ".", "/", -1)
+		p.cuePkgPath = path.Join("googleapis.com", dir)
+	}
+	return p.cuePkgPath
+}
+
+func (p *protoConverter) shortName() string {
+	if p.shortPkgName == "" && p.protoPkg != "" {
+		split := strings.Split(p.protoPkg, ".")
+		p.shortPkgName = split[len(split)-1]
+		p.file.Name = ast.NewIdent(p.shortPkgName)
+	}
+	return p.shortPkgName
+}
+
 func (p *protoConverter) toCUEPos(pos scanner.Position) token.Pos {
 	return p.tfile.Pos(pos.Offset, 0)
 }
@@ -297,7 +324,7 @@
 		}
 		if m, ok := p.scope[0][name[:i]]; ok {
 			if m.pkg != nil {
-				p.imported[m.pkg.goPkgPath] = true
+				p.imported[m.pkg.importPath()] = true
 				// TODO: do something more principled.
 			}
 			cueName := strings.Replace(name[i:], ".", "_", -1)
@@ -340,8 +367,8 @@
 	}
 
 	prefix := ""
-	if imp.goPkgPath != p.goPkgPath {
-		prefix = imp.goPkg + "."
+	if imp.importPath() != p.importPath() {
+		prefix = imp.shortName() + "."
 	}
 
 	pkgNamespace := strings.Split(imp.protoPkg, ".")
@@ -354,7 +381,7 @@
 			}
 			if _, ok := p.scope[0][ref]; !ok {
 				pkg := imp
-				if imp.goPkgPath == p.goPkgPath {
+				if imp.importPath() == p.importPath() {
 					pkg = nil
 				}
 				p.scope[0][ref] = mapping{prefix + k, "", pkg}
diff --git a/encoding/protobuf/protobuf.go b/encoding/protobuf/protobuf.go
index a2a1413..0baed41 100644
--- a/encoding/protobuf/protobuf.go
+++ b/encoding/protobuf/protobuf.go
@@ -16,8 +16,24 @@
 // 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.
+// discussed in https://developers.google.com/protocol-buffers/docs/proto3, and
+// carries some of the mapping further when possible with CUE.
+//
+//
+// Package Paths
+//
+// If a .proto file contains a go_package directive, it will be used as the
+// destination package fo the generated .cue files. A common use case is to
+// generate the CUE in the same directory as the .proto definition. If a
+// destination package is not within the current CUE module, it will be written
+// relative to the pkg directory.
+//
+// If a .proto file does not specify go_package, it will convert a proto package
+// "google.parent.sub" to the import path "googleapis.com/google/parent/sub".
+// It is safe to mix package with and without a go_package within the same
+// project.
+//
+// Type Mappings
 //
 // The following type mappings of defintions apply:
 //
@@ -49,8 +65,8 @@
 //   Timestamp      time.Time        See struct.proto.
 //   Duration       time.Duration    See struct.proto.
 //
-// Protobuf definitions can be annotated with CUE constraints that are
-// included in the generated CUE:
+// 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
 //                                field. The string may refer to other fields
 //                                in a message definition using their JSON name.
@@ -286,10 +302,10 @@
 	if b.errs != nil {
 		return nil
 	}
-	importPath := p.goPkgPath
+	importPath := p.importPath()
 	if importPath == "" {
 		err := errors.Newf(token.NoPos,
-			"no go_package for proto package %q in file %s", p.id, p.file.Filename)
+			"no package clause for proto package %q in file %s", p.id, p.file.Filename)
 		b.errs = errors.Append(b.errs, err)
 		// TODO: find an alternative. Is proto package good enough?
 		return nil
@@ -320,7 +336,7 @@
 			Root:        b.root,
 			Dir:         dir,
 			ImportPath:  importPath,
-			PkgName:     p.goPkg,
+			PkgName:     p.shortPkgName,
 			DisplayPath: p.protoPkg,
 		}
 		b.imports[importPath] = inst
diff --git a/encoding/protobuf/testdata/acme/test.proto b/encoding/protobuf/testdata/acme/test.proto
new file mode 100644
index 0000000..64de468
--- /dev/null
+++ b/encoding/protobuf/testdata/acme/test.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+
+package acme.test;
+
+message Test {
+  int32 test = 1;
+}
diff --git a/encoding/protobuf/testdata/acme/test/test.proto b/encoding/protobuf/testdata/acme/test/test.proto
new file mode 100644
index 0000000..186ce1a
--- /dev/null
+++ b/encoding/protobuf/testdata/acme/test/test.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package acme.test.test;
+
+// Override the short name only of this package. This notation is seen in some
+// gogoproto files.
+option go_package = "test_test"
+
+message AnotherTest {
+  int32 test = 1;
+}
diff --git a/encoding/protobuf/testdata/attributes.proto.out.cue b/encoding/protobuf/testdata/attributes.proto.out.cue
index d678760..4911be4 100644
--- a/encoding/protobuf/testdata/attributes.proto.out.cue
+++ b/encoding/protobuf/testdata/attributes.proto.out.cue
@@ -14,7 +14,11 @@
 //  limitations under the License.
 package v1
 
-import "time"
+import (
+	"googleapis.com/acme/test"
+	"googleapis.com/acme/test/test"
+	"time"
+)
 
 StructWrap: {
 	struct?:    {}     @protobuf(1,type=google.protobuf.Struct)
@@ -42,24 +46,6 @@
 //  target.service: example
 //  ```
 // 
-//  A given Istio deployment has a fixed vocabulary of attributes that it understands.
-//  The specific vocabulary is determined by the set of attribute producers being used
-//  in the deployment. The primary attribute producer in Istio is Envoy, although
-//  specialized Mixer adapters and services can also generate attributes.
-// 
-//  The common baseline set of attributes available in most Istio deployments is defined
-//  [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/).
-// 
-//  Attributes are strongly typed. The supported attribute types are defined by
-//  [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto).
-//  Each type of value is encoded into one of the so-called transport types present
-//  in this message.
-// 
-//  Defines a map of attributes in uncompressed format.
-//  Following places may use this message:
-//  1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid.
-//  2) Service IDL definition to extract api attributes for active requests.
-//  3) Forward attributes from client proxy to server proxy for HTTP requests.
 Attributes: {
 	//  A map of attribute name to its value.
 	attributes: {
@@ -95,6 +81,10 @@
 } | {
 	//  Used for values of type STRING_MAP
 	stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value)
+} | {
+	testValue: test.Test @protobuf(10,type=acme.test.Test,name=test_value)
+} | {
+	testValue: test_test.AnotherTest @protobuf(11,type=acme.test.test.AnotherTest,name=test_value)
 }
 
 //  Defines a string map.
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 ddc1b94..83c9644 100644
--- a/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto
+++ b/encoding/protobuf/testdata/istio.io/api/mixer/v1/attributes.proto
@@ -22,6 +22,8 @@
 import "google/protobuf/duration.proto";
 import "google/protobuf/timestamp.proto";
 import "google/protobuf/struct.proto";
+import "acme/test.proto";
+import "acme/test/test.proto";
 
 option (gogoproto.goproto_getters_all) = false;
 option (gogoproto.equal_all) = false;
@@ -56,24 +58,6 @@
 // target.service: example
 // ```
 //
-// A given Istio deployment has a fixed vocabulary of attributes that it understands.
-// The specific vocabulary is determined by the set of attribute producers being used
-// in the deployment. The primary attribute producer in Istio is Envoy, although
-// specialized Mixer adapters and services can also generate attributes.
-//
-// The common baseline set of attributes available in most Istio deployments is defined
-// [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/).
-//
-// Attributes are strongly typed. The supported attribute types are defined by
-// [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto).
-// Each type of value is encoded into one of the so-called transport types present
-// in this message.
-//
-// Defines a map of attributes in uncompressed format.
-// Following places may use this message:
-// 1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid.
-// 2) Service IDL definition to extract api attributes for active requests.
-// 3) Forward attributes from client proxy to server proxy for HTTP requests.
 message Attributes {
   // A map of attribute name to its value.
   map<string, AttributeValue> attributes = 1;
@@ -105,6 +89,9 @@
 
       // Used for values of type STRING_MAP
       StringMap string_map_value = 9;
+
+      acme.test.Test test_value = 10;
+      acme.test.test.AnotherTest test_value = 11;
     }
   }
 
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 feced59..cf13972 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
@@ -14,7 +14,11 @@
 //  limitations under the License.
 package v1
 
-import "time"
+import (
+	"googleapis.com/acme/test"
+	"googleapis.com/acme/test/test"
+	"time"
+)
 
 StructWrap: {
 	struct?: {} @protobuf(1,type=google.protobuf.Struct)
@@ -42,24 +46,6 @@
 //  target.service: example
 //  ```
 // 
-//  A given Istio deployment has a fixed vocabulary of attributes that it understands.
-//  The specific vocabulary is determined by the set of attribute producers being used
-//  in the deployment. The primary attribute producer in Istio is Envoy, although
-//  specialized Mixer adapters and services can also generate attributes.
-// 
-//  The common baseline set of attributes available in most Istio deployments is defined
-//  [here](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/).
-// 
-//  Attributes are strongly typed. The supported attribute types are defined by
-//  [ValueType](https://github.com/istio/api/blob/master/policy/v1beta1/value_type.proto).
-//  Each type of value is encoded into one of the so-called transport types present
-//  in this message.
-// 
-//  Defines a map of attributes in uncompressed format.
-//  Following places may use this message:
-//  1) Configure Istio/Proxy with static per-proxy attributes, such as source.uid.
-//  2) Service IDL definition to extract api attributes for active requests.
-//  3) Forward attributes from client proxy to server proxy for HTTP requests.
 Attributes: {
 	//  A map of attribute name to its value.
 	attributes: {
@@ -95,6 +81,10 @@
 } | {
 	//  Used for values of type STRING_MAP
 	stringMapValue: Attributes_StringMap @protobuf(9,type=StringMap,name=string_map_value)
+} | {
+	testValue: test.Test @protobuf(10,type=acme.test.Test,name=test_value)
+} | {
+	testValue: test_test.AnotherTest @protobuf(11,type=acme.test.test.AnotherTest,name=test_value)
 }
 
 //  Defines a string map.
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue
new file mode 100644
index 0000000..efe315a
--- /dev/null
+++ b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test/test_proto_gen.cue
@@ -0,0 +1,5 @@
+package test_test
+
+AnotherTest: {
+	test?: int32 @protobuf(1)
+}
diff --git a/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue
new file mode 100644
index 0000000..329db3e
--- /dev/null
+++ b/encoding/protobuf/testdata/istio.io/api/pkg/googleapis.com/acme/test/test_proto_gen.cue
@@ -0,0 +1,5 @@
+package test
+
+Test: {
+	test?: int32 @protobuf(1)
+}
diff --git a/encoding/protobuf/types.go b/encoding/protobuf/types.go
index 2191afe..687819b 100644
--- a/encoding/protobuf/types.go
+++ b/encoding/protobuf/types.go
@@ -53,8 +53,8 @@
 }
 
 var (
-	pkgTime   = &protoConverter{goPkgPath: "time"}
-	pkgStruct = &protoConverter{goPkgPath: "struct"}
+	pkgTime   = &protoConverter{cuePkgPath: "time"}
+	pkgStruct = &protoConverter{cuePkgPath: "struct"}
 )
 
 func (p *protoConverter) mapBuiltinPackage(pos scanner.Position, file string, required bool) (generate bool) {