Repository: googleapis/gapic-generator-go
Pull request #13 (2018-09-24 23:28:36)

sample: print types
Like before, this works only for structs, not arrays and maps.
Enums don't work yet.


Patch:
diff --git a/cmd/gen-go-sample/inittree.go b/cmd/gen-go-sample/inittree.go
index 416fd28..b5cce54 100644
--- a/cmd/gen-go-sample/inittree.go
+++ b/cmd/gen-go-sample/inittree.go
@@ -16,16 +16,34 @@ package main
 
 import (
 	"bufio"
+	"fmt"
 	"io"
 	"strings"
 	"text/scanner"
 	"unicode"
 
+	"github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"github.com/googleapis/gapic-generator-go/internal/errors"
+	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
 )
 
+// initType represents a type of a value in initialization tree.
+//
+// In protobuf, array-ness and map-ness are properties of fields, not types,
+// and primitives are treated differently than message types.
+// initType treats a type in a way similar to how Go treats them,
+// so that it's easy to generate initialization code.
+type initType struct {
+	// If the type is a message or enum, desc describes the type.
+	// If the type is a primitive, primValid reports whether the token is valid for the type.
+	desc      pbinfo.ProtoType
+	primValid func(string) bool
+}
+
 // initTree represents a node in the initialization tree.
 type initTree struct {
+	typ initType
+
 	// initTree is either a composite value (struct/array/map), where keys and vals are set,
 	// or a simple value, where leafVal is set.
 
@@ -38,22 +56,67 @@ type initTree struct {
 	leafVal string
 }
 
-func (t *initTree) get(k string) *initTree {
+func (t *initTree) get(k string, info pbinfo.Info) (*initTree, error) {
 	for i, key := range t.keys {
 		if k == key {
-			return t.vals[i]
+			return t.vals[i], nil
 		}
 	}
 
+	var fields []*descriptor.FieldDescriptorProto
+	if msg, ok := t.typ.desc.(*descriptor.DescriptorProto); ok {
+		fields = msg.Field
+	} else {
+		return nil, errors.E(nil, "type does not have fields: %T", t.typ.desc)
+	}
+
 	v := new(initTree)
+	for _, f := range fields {
+		if f.GetName() != k {
+			continue
+		}
+
+		if tn := f.GetTypeName(); tn != "" {
+			typ := info.Type[f.GetTypeName()]
+			if typ == nil {
+				return nil, errors.E(nil, "cannot find descriptor of %q", f.GetTypeName())
+			}
+			v.typ.desc = typ
+		} else {
+			// Since the tokens are given to us by scanner, they must already be a valid token of some type,
+			// no need to check exhaustively.
+			switch f.GetType() {
+			case descriptor.FieldDescriptorProto_TYPE_BOOL:
+				v.typ.primValid = func(s string) bool { return s == "true" || s == "false" }
+			case descriptor.FieldDescriptorProto_TYPE_BYTES, descriptor.FieldDescriptorProto_TYPE_STRING:
+				v.typ.primValid = func(s string) bool { return s[0] == '"' }
+			case descriptor.FieldDescriptorProto_TYPE_DOUBLE, descriptor.FieldDescriptorProto_TYPE_FLOAT:
+				v.typ.primValid = func(s string) bool { return s == "inf" || s == "nan" || strings.Trim(s, "+-.0123456789") == "" }
+			default:
+				v.typ.primValid = func(s string) bool { return strings.Trim(s, "0123456789") == "" }
+			}
+		}
+	}
+	if v.typ.desc == nil && v.typ.primValid == nil {
+		return nil, errors.E(nil, "type %q does not have field %q", t.typ.desc.GetName(), k)
+	}
+
 	t.keys = append(t.keys, k)
 	t.vals = append(t.vals, v)
-	return v
+	return v, nil
 }
 
-func (t *initTree) Parse(txt string) error {
+// Just do simple structs for now.
+// TODO(pongad): allow map and array index.
+//
+// spec = ident { '.' ident } [ '=' value ] .
+func (t *initTree) Parse(txt string, info pbinfo.Info) error {
 	var sc scanner.Scanner
-	sc.Init(strings.NewReader(txt))
+
+	// The first ident is treated specially to be a member of the root.
+	// Since we know the root is a struct, a dot is the only legal token anyway,
+	// so just insert a dot here so we don't need to treat the first token specially.
+	sc.Init(strings.NewReader("." + txt))
 	sc.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings
 
 	// Scanner reports error by calling sc.Error; we save the error so we always report
@@ -71,15 +134,6 @@ func (t *initTree) Parse(txt string) error {
 		report(e)
 	}
 
-	// Just do simple structs for now.
-	// TODO(pongad): allow map and array index.
-	// spec = ident { '.' ident } [ '=' value ] .
-
-	if sc.Scan() != scanner.Ident {
-		return report(errors.E(nil, "expected ident, found %q", sc.TokenText()))
-	}
-	t = t.get(sc.TokenText())
-
 	for {
 		switch sc.Scan() {
 		case '=':
@@ -92,21 +146,36 @@ func (t *initTree) Parse(txt string) error {
 			if sc.Scan() != scanner.Ident {
 				return report(errors.E(nil, "expected ident, found %q", sc.TokenText()))
 			}
-			t = t.get(sc.TokenText())
+			if t2, err := t.get(sc.TokenText(), info); err != nil {
+				return report(err)
+			} else {
+				t = t2
+			}
 
 		default:
 			return report(errors.E(nil, "unexpected %q", sc.TokenText()))
 		}
 	}
 
-	// TODO(pongad): check that value and type match
-	// TODO(pongad): check that we don't write two values onto the same node
 	// TODO(pongad): handle resource names
 	// TODO(pongad): properly validate and print enums
+	// TODO(pongad): properly print oneof fields
 equal:
 	switch r := sc.Scan(); r {
 	case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
-		t.leafVal = sc.TokenText()
+		if lv := t.leafVal; lv != "" {
+			return report(errors.E(nil, "value already set to %q", lv))
+		}
+		if t.typ.primValid == nil {
+			return report(errors.E(nil, "not a primitive field"))
+		}
+
+		tok := sc.TokenText()
+		if !t.typ.primValid(tok) {
+			// TODO(pongad): we should probably tell user what type the field is.
+			return report(errors.E(nil, "invalid value for type: %q", tok))
+		}
+		t.leafVal = tok
 	default:
 		return report(errors.E(nil, "expected value, found %q", sc.TokenText()))
 	}
@@ -117,30 +186,42 @@ equal:
 	return err
 }
 
-func (t *initTree) Print(w io.Writer) error {
+func (t *initTree) Print(w io.Writer, g *generator) error {
 	bw := bufio.NewWriter(w)
-	t.print(bw, 1)
-	return bw.Flush()
+	err := t.print(bw, g, 1)
+	if err2 := bw.Flush(); err == nil {
+		err = err2
+	}
+	return err
 }
 
-func (t *initTree) print(w *bufio.Writer, ind int) {
+func (t *initTree) print(w *bufio.Writer, g *generator, ind int) error {
 	if v := t.leafVal; v != "" {
 		w.WriteString(v)
-		return
+		return nil
 	}
 
-	// TODO(pongad): Figure out how to print type.
-	w.WriteString("TYPE{\n")
+	desc := t.typ.desc
+	impSpec, err := g.descInfo.ImportSpec(desc)
+	if err != nil {
+		return err
+	}
+	g.imports[impSpec] = true
+
+	fmt.Fprintf(w, "%s.%s{\n", impSpec.Name, desc.GetName())
 	for i, k := range t.keys {
 		for i := 0; i < ind; i++ {
 			w.WriteByte('\t')
 		}
 		snakeToCapCamel(w, k)
 		w.WriteString(": ")
-		t.vals[i].print(w, ind+1)
+		if err := t.vals[i].print(w, g, ind+1); err != nil {
+			return err
+		}
 		w.WriteString(",\n")
 	}
 	w.WriteString("}")
+	return nil
 }
 
 func snakeToCapCamel(w *bufio.Writer, s string) {
diff --git a/cmd/gen-go-sample/main.go b/cmd/gen-go-sample/main.go
index 048b475..088d68f 100644
--- a/cmd/gen-go-sample/main.go
+++ b/cmd/gen-go-sample/main.go
@@ -216,9 +216,11 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV
 		return errors.E(err, "can't import input type: %q", inType)
 	}
 
-	var itree initTree
+	itree := initTree{
+		typ: initType{desc: inType},
+	}
 	for _, def := range valSet.Parameters.Defaults {
-		if err := itree.Parse(def); err != nil {
+		if err := itree.Parse(def, g.descInfo); err != nil {
 			return errors.E(err, "can't set default value: %q", def)
 		}
 	}
@@ -228,7 +230,7 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV
 		if _, err := w.Write([]byte("req := ")); err != nil {
 			return err
 		}
-		if err := itree.Print(g.pt.Writer()); err != nil {
+		if err := itree.Print(g.pt.Writer(), g); err != nil {
 			return err
 		}
 		if _, err := w.Write([]byte{'\n'}); err != nil {


Test patch:
diff --git a/cmd/gen-go-sample/inittree_test.go b/cmd/gen-go-sample/inittree_test.go
index cf18665..d9b1880 100644
--- a/cmd/gen-go-sample/inittree_test.go
+++ b/cmd/gen-go-sample/inittree_test.go
@@ -17,19 +17,47 @@ package main
 import (
 	"strings"
 	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
 )
 
+func typep(t descriptor.FieldDescriptorProto_Type) *descriptor.FieldDescriptorProto_Type {
+	return &t
+}
+
 func TestTree(t *testing.T) {
 	specs := []string{
 		`a.b = 1`,
 		`a.c = "xyz"`,
 		`a.d = 2.718281828`,
-		`x = 123`,
+		`x = true`,
+	}
+
+	info := pbinfo.Info{
+		Type: map[string]pbinfo.ProtoType{
+			"RootType": &descriptor.DescriptorProto{
+				Field: []*descriptor.FieldDescriptorProto{
+					{Name: proto.String("a"), TypeName: proto.String("AType")},
+					{Name: proto.String("x"), Type: typep(descriptor.FieldDescriptorProto_TYPE_BOOL)},
+				},
+			},
+			"AType": &descriptor.DescriptorProto{
+				Field: []*descriptor.FieldDescriptorProto{
+					{Name: proto.String("b"), Type: typep(descriptor.FieldDescriptorProto_TYPE_INT64)},
+					{Name: proto.String("c"), Type: typep(descriptor.FieldDescriptorProto_TYPE_STRING)},
+					{Name: proto.String("d"), Type: typep(descriptor.FieldDescriptorProto_TYPE_DOUBLE)},
+				},
+			},
+		},
 	}
 
-	var root initTree
+	root := initTree{
+		typ: initType{desc: info.Type["RootType"]},
+	}
 	for _, s := range specs {
-		if err := root.Parse(s); err != nil {
+		if err := root.Parse(s, info); err != nil {
 			t.Fatal(err)
 		}
 	}
@@ -41,25 +69,47 @@ func TestTree(t *testing.T) {
 		{[]string{"a", "b"}, "1"},
 		{[]string{"a", "c"}, `"xyz"`},
 		{[]string{"a", "d"}, "2.718281828"},
-		{[]string{"x"}, "123"},
+		{[]string{"x"}, "true"},
 	} {
 		node := &root
 		for _, p := range tst.path {
-			node = node.get(p)
+			var err error
+			node, err = node.get(p, info)
+			if err != nil {
+				t.Error(err)
+			}
 		}
 		if node.leafVal != tst.val {
 			t.Errorf("%s = %q, want %q", strings.Join(tst.path, "->"), node.leafVal, tst.val)
 		}
 	}
+}
 
-	if t.Failed() {
-		t.SkipNow()
+func TestTreeErrors(t *testing.T) {
+	info := pbinfo.Info{
+		Type: map[string]pbinfo.ProtoType{
+			"RootType": &descriptor.DescriptorProto{
+				Field: []*descriptor.FieldDescriptorProto{
+					{Name: proto.String("a"), Type: typep(descriptor.FieldDescriptorProto_TYPE_INT64)},
+				},
+			},
+		},
 	}
 
-	var buf strings.Builder
-	buf.WriteByte('\n')
-	if err := root.Print(&buf); err != nil {
-		t.Error(err)
+testcase:
+	for _, tst := range [][]string{
+		{`3=4`}, // bad field name
+
+		{`a=1`, `a=2`},      // sets same node twice
+		{`a="abc"`},         // type is int64, value is string
+		{`unknown_field=3`}, // field doesn't exist
+	} {
+		root := initTree{typ: initType{desc: info.Type["RootType"]}}
+		for _, txt := range tst {
+			if root.Parse(txt, info) != nil {
+				continue testcase
+			}
+		}
+		t.Errorf("initTree.parse succeeded, want error: %v", tst)
 	}
-	t.Log(buf.String())
 }
diff --git a/cmd/gen-go-sample/main_test.go b/cmd/gen-go-sample/main_test.go
index 9d7d325..fd1cf12 100644
--- a/cmd/gen-go-sample/main_test.go
+++ b/cmd/gen-go-sample/main_test.go
@@ -48,7 +48,19 @@ func diff(t *testing.T, name, got, goldenFile string) {
 func TestSample(t *testing.T) {
 	inType := &descriptor.DescriptorProto{
 		Name: proto.String("InputType"),
+		Field: []*descriptor.FieldDescriptorProto{
+			{Name: proto.String("a"), TypeName: proto.String("AType")},
+			{Name: proto.String("b"), Type: typep(descriptor.FieldDescriptorProto_TYPE_STRING)},
+		},
+	}
+	aType := &descriptor.DescriptorProto{
+		Name: proto.String("AType"),
+		Field: []*descriptor.FieldDescriptorProto{
+			{Name: proto.String("x"), Type: typep(descriptor.FieldDescriptorProto_TYPE_INT64)},
+			{Name: proto.String("y"), Type: typep(descriptor.FieldDescriptorProto_TYPE_FLOAT)},
+		},
 	}
+
 	serv := &descriptor.ServiceDescriptorProto{
 		Name: proto.String("MyService"),
 		Method: []*descriptor.MethodDescriptorProto{
@@ -74,9 +86,11 @@ func TestSample(t *testing.T) {
 				serv:           file,
 				serv.Method[0]: file,
 				inType:         file,
+				aType:          file,
 			},
 			Type: map[string]pbinfo.ProtoType{
 				"InputType": inType,
+				"AType":     aType,
 			},
 		},
 	}
diff --git a/cmd/gen-go-sample/testdata/sample.want b/cmd/gen-go-sample/testdata/sample.want
index 0e1ca8b..3c97f49 100644
--- a/cmd/gen-go-sample/testdata/sample.want
+++ b/cmd/gen-go-sample/testdata/sample.want
@@ -4,8 +4,8 @@ func sampleMyMethod() {
 	ctx := context.Background()
 	c := mypackagepb.NewClient(ctx)
 
-req := TYPE{
-	A: TYPE{
+req := mypackagepb.InputType{
+	A: mypackagepb.AType{
 		X: 42,
 		Y: 3.14159,
 },