Repository: googleapis/gapic-generator-go
Pull request #1498 (2024-04-26 17:38:39)

feat(gengapic): add x-goog-api-version header
* Verifies request headers in trailing metadata as enabled by https://github.com/googleapis/gapic-showcase/pull/1501.


Patch:
diff --git a/internal/gengapic/gengrpc.go b/internal/gengapic/gengrpc.go
index 325a4ee..6ec8e93 100644
--- a/internal/gengapic/gengrpc.go
+++ b/internal/gengapic/gengrpc.go
@@ -375,6 +375,8 @@ func (g *generator) grpcClientUtilities(serv *descriptorpb.ServiceDescriptorProt
 	p("}")
 	p("")
 
+	apiVersion := proto.GetExtension(serv.Options, annotations.E_ApiVersion).(string)
+
 	// setGoogleClientInfo method
 	p("// setGoogleClientInfo sets the name and version of the application in")
 	p("// the `x-goog-api-client` header passed on each request. Intended for")
@@ -382,7 +384,12 @@ func (g *generator) grpcClientUtilities(serv *descriptorpb.ServiceDescriptorProt
 	p("func (c *%s) setGoogleClientInfo(keyval ...string) {", lowcaseServName)
 	p(`  kv := append([]string{"gl-go", gax.GoVersion}, keyval...)`)
 	p(`  kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)`)
-	p(`  c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}`)
+	p(`  c.xGoogHeaders = []string{`)
+	p(`    "x-goog-api-client", gax.XGoogHeader(kv...),`)
+	if apiVersion != "" {
+		p(`    "x-goog-api-version", %q,`, apiVersion)
+	}
+	p("  }")
 	p("}")
 	p("")
 
diff --git a/internal/gengapic/genrest.go b/internal/gengapic/genrest.go
index 67179f5..23131d3 100644
--- a/internal/gengapic/genrest.go
+++ b/internal/gengapic/genrest.go
@@ -199,6 +199,8 @@ func (g *generator) restClientUtilities(serv *descriptorpb.ServiceDescriptorProt
 
 	g.restClientOptions(serv, servName)
 
+	apiVersion := proto.GetExtension(serv.Options, annotations.E_ApiVersion).(string)
+
 	// setGoogleClientInfo method
 	p("// setGoogleClientInfo sets the name and version of the application in")
 	p("// the `x-goog-api-client` header passed on each request. Intended for")
@@ -206,7 +208,12 @@ func (g *generator) restClientUtilities(serv *descriptorpb.ServiceDescriptorProt
 	p("func (c *%s) setGoogleClientInfo(keyval ...string) {", lowcaseServName)
 	p(`  kv := append([]string{"gl-go", gax.GoVersion}, keyval...)`)
 	p(`  kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "rest", "UNKNOWN")`)
-	p(`  c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}`)
+	p(`  c.xGoogHeaders = []string{`)
+	p(`    "x-goog-api-client", gax.XGoogHeader(kv...),`)
+	if apiVersion != "" {
+		p(`    "x-goog-api-version", %q,`, apiVersion)
+	}
+	p("  }")
 	p("}")
 	p("")
 


Test patch:
diff --git a/internal/gengapic/client_init_test.go b/internal/gengapic/client_init_test.go
index 24fa144..f2d6194 100644
--- a/internal/gengapic/client_init_test.go
+++ b/internal/gengapic/client_init_test.go
@@ -330,7 +330,10 @@ func TestClientInit(t *testing.T) {
 				Options:    &descriptorpb.MethodOptions{},
 			},
 		},
+		Options: &descriptorpb.ServiceOptions{},
 	}
+	proto.SetExtension(servPlain.Options, annotations.E_ApiVersion, "v1_20240425")
+
 	servLRO := &descriptorpb.ServiceDescriptorProto{
 		Name: proto.String("Foo"),
 		Method: []*descriptorpb.MethodDescriptorProto{
@@ -341,7 +344,10 @@ func TestClientInit(t *testing.T) {
 				Options:    &descriptorpb.MethodOptions{},
 			},
 		},
+		Options: &descriptorpb.ServiceOptions{},
 	}
+	proto.SetExtension(servLRO.Options, annotations.E_ApiVersion, "v1_20240425")
+
 	servDeprecated := &descriptorpb.ServiceDescriptorProto{
 		Name: proto.String("Foo"),
 		Method: []*descriptorpb.MethodDescriptorProto{
diff --git a/internal/gengapic/testdata/custom_op_init.want b/internal/gengapic/testdata/custom_op_init.want
index f7bd848..5ab35d2 100644
--- a/internal/gengapic/testdata/custom_op_init.want
+++ b/internal/gengapic/testdata/custom_op_init.want
@@ -102,7 +102,9 @@ func NewRESTClient(ctx context.Context, opts ...option.ClientOption) (*Client, e
 func (c *restClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "rest", "UNKNOWN")
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/internal/gengapic/testdata/deprecated_client_init.want b/internal/gengapic/testdata/deprecated_client_init.want
index afaf1d7..ff8f940 100644
--- a/internal/gengapic/testdata/deprecated_client_init.want
+++ b/internal/gengapic/testdata/deprecated_client_init.want
@@ -115,7 +115,9 @@ func (c *gRPCClient) Connection() *grpc.ClientConn {
 func (c *gRPCClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
@@ -168,7 +170,9 @@ func NewRESTClient(ctx context.Context, opts ...option.ClientOption) (*Client, e
 func (c *restClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "rest", "UNKNOWN")
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/internal/gengapic/testdata/empty_client_init.want b/internal/gengapic/testdata/empty_client_init.want
index 25f585c..1bafa25 100644
--- a/internal/gengapic/testdata/empty_client_init.want
+++ b/internal/gengapic/testdata/empty_client_init.want
@@ -111,7 +111,10 @@ func (c *gRPCClient) Connection() *grpc.ClientConn {
 func (c *gRPCClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+		"x-goog-api-version", "v1_20240425",
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
@@ -162,7 +165,10 @@ func NewRESTClient(ctx context.Context, opts ...option.ClientOption) (*Client, e
 func (c *restClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "rest", "UNKNOWN")
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+		"x-goog-api-version", "v1_20240425",
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/internal/gengapic/testdata/foo_client_init.want b/internal/gengapic/testdata/foo_client_init.want
index d552419..e32c9ea 100644
--- a/internal/gengapic/testdata/foo_client_init.want
+++ b/internal/gengapic/testdata/foo_client_init.want
@@ -142,7 +142,10 @@ func (c *fooGRPCClient) Connection() *grpc.ClientConn {
 func (c *fooGRPCClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+		"x-goog-api-version", "v1_20240425",
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/internal/gengapic/testdata/foo_rest_client_init.want b/internal/gengapic/testdata/foo_rest_client_init.want
index 18542e2..c24c9ef 100644
--- a/internal/gengapic/testdata/foo_rest_client_init.want
+++ b/internal/gengapic/testdata/foo_rest_client_init.want
@@ -114,7 +114,10 @@ func NewFooRESTClient(ctx context.Context, opts ...option.ClientOption) (*FooCli
 func (c *fooRESTClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "rest", "UNKNOWN")
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+		"x-goog-api-version", "v1_20240425",
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/internal/gengapic/testdata/lro_client_init.want b/internal/gengapic/testdata/lro_client_init.want
index 09a0d62..feb4dc2 100644
--- a/internal/gengapic/testdata/lro_client_init.want
+++ b/internal/gengapic/testdata/lro_client_init.want
@@ -167,7 +167,10 @@ func (c *fooGRPCClient) Connection() *grpc.ClientConn {
 func (c *fooGRPCClient) setGoogleClientInfo(keyval ...string) {
 	kv := append([]string{"gl-go", gax.GoVersion}, keyval...)
 	kv = append(kv, "gapic", getVersionClient(), "gax", gax.Version, "grpc", grpc.Version)
-	c.xGoogHeaders = []string{"x-goog-api-client", gax.XGoogHeader(kv...)}
+	c.xGoogHeaders = []string{
+		"x-goog-api-client", gax.XGoogHeader(kv...),
+		"x-goog-api-version", "v1_20240425",
+	}
 }
 
 // Close closes the connection to the API service. The user should invoke this when
diff --git a/showcase/echo_test.go b/showcase/echo_test.go
index 50441e4..1df6ae3 100644
--- a/showcase/echo_test.go
+++ b/showcase/echo_test.go
@@ -145,6 +145,18 @@ func TestEchoHeader(t *testing.T) {
 	}
 }
 
+// Test gRPC trailers for required headers.
+func TestEchoTrailers(t *testing.T) {
+	defer check(t)
+	req := &showcasepb.EchoRequest{Header: "potato"}
+	mdForTrailers := metadata.New(map[string]string{})
+	echo.Echo(context.Background(), req, gax.WithGRPCOptions(grpc.Trailer(&mdForTrailers)))
+	apiVersion := mdForTrailers.Get("x-goog-api-version")
+	if len(apiVersion) < 1 || apiVersion[0] == "" {
+		t.Error("Echo() x-goog-api-version header not present or empty")
+	}
+}
+
 func TestEchoHeaderREST(t *testing.T) {
 	defer check(t)
 	for _, tst := range []struct {
@@ -199,15 +211,22 @@ func TestXGoogeHeaders(t *testing.T) {
 	info := y.FieldByName("xGoogHeaders")
 
 	var goVersion string
+	var apiVersion string
 	vals := make([]string, 0)
 	for i := 0; i < info.Len(); i++ {
 		key := info.Index(i)
 		// Only check for the client info set by the generated setGoogleClientInfo()
-		if key.String() != "x-goog-api-client" {
-			continue
+		if key.String() == "x-goog-api-client" {
+			vals = append(vals, info.Index(i+1).String())
 		}
+		if key.String() == "x-goog-api-version" {
+			apiVersion = info.Index(i + 1).String()
+		}
+
+	}
 
-		vals = append(vals, info.Index(i+1).String())
+	if apiVersion == "" {
+		t.Error("expected x-goog-api-version to be present and non-empty")
 	}
 
 	for i := 0; goVersion == "" || i < len(vals); i++ {