Repository: googleapis/gapic-generator-go
Pull request #1460 (2024-02-14 16:53:18)

feat(gengapic): add support for AutoPopulatedFields UUID4

quartzmo: @noahdietz Updated with the UUID dependency, thank you.

I locally ran the bazel targets for the "usual suspects" set of gapic libraries:

```
bazelisk build //google/cloud/apigeeconnect/v1:apigeeconnect_go_gapic
bazelisk build //google/cloud/asset/v1:asset_go_gapic
bazelisk build //google/cloud/language/v1:language_go_gapic
bazelisk build //google/spanner/v1:spanner_go_gapic
bazelisk build //google/cloud/aiplatform/v1beta1:aiplatform_go_gapic
bazelisk build //google/cloud/compute/v1:compute_go_gapic
```

Patch:
diff --git a/internal/gengapic/generator.go b/internal/gengapic/generator.go
index 755ba49..5cd5399 100644
--- a/internal/gengapic/generator.go
+++ b/internal/gengapic/generator.go
@@ -28,6 +28,7 @@ import (
 	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
 	"github.com/googleapis/gapic-generator-go/internal/printer"
 	"github.com/googleapis/gapic-generator-go/internal/snippets"
+	"google.golang.org/genproto/googleapis/api/annotations"
 	"google.golang.org/genproto/googleapis/api/serviceconfig"
 	"google.golang.org/genproto/googleapis/gapic/metadata"
 	"google.golang.org/protobuf/encoding/protojson"
@@ -302,3 +303,37 @@ func (g *generator) nestedName(nested pbinfo.ProtoType) string {
 
 	return name
 }
+
+// autoPopulatedFields returns an array of FieldDescriptorProto pointers for the
+// given MethodDescriptorProto that are specified for auto-population per the
+// following restrictions:
+//
+// * The field is a top-level string field of a unary method's request message.
+// * The field is not annotated with google.api.field_behavior = REQUIRED.
+// * The field name is listed in google.api.publishing.method_settings.auto_populated_fields.
+// * The field is annotated with google.api.field_info.format = UUID4.
+func (g *generator) autoPopulatedFields(servName string, m *descriptor.MethodDescriptorProto) []*descriptor.FieldDescriptorProto {
+	var apfs []string
+	// Find the service config's AutoPopulatedFields entry by method name.
+	mfqn := g.fqn(m)
+	for _, s := range g.serviceConfig.GetPublishing().GetMethodSettings() {
+		if s.GetSelector() == mfqn {
+			apfs = s.AutoPopulatedFields
+			break
+		}
+	}
+	inType := g.descInfo.Type[m.GetInputType()].(*descriptor.DescriptorProto)
+	var validated []*descriptor.FieldDescriptorProto
+	for _, apf := range apfs {
+		field := getField(inType, apf)
+		// Do nothing and continue iterating unless all conditions above are met.
+		switch {
+		case field == nil:
+		case field.GetType() != fieldTypeString:
+		case isRequired(field):
+		case proto.GetExtension(field.GetOptions(), annotations.E_FieldInfo).(*annotations.FieldInfo).GetFormat() == annotations.FieldInfo_UUID4:
+			validated = append(validated, field)
+		}
+	}
+	return validated
+}
diff --git a/internal/gengapic/gengapic.go b/internal/gengapic/gengapic.go
index 2dad344..9afe29f 100644
--- a/internal/gengapic/gengapic.go
+++ b/internal/gengapic/gengapic.go
@@ -348,6 +348,37 @@ func (g *generator) insertRequestHeaders(m *descriptor.MethodDescriptorProto, t
 	}
 }
 
+// insertAutoPopulatedFields generates the conditional initialization of any
+// default-value request fields (for the given method) that are specified for
+// auto-population by autoPopulatedFields.
+//
+// If the field value is not equal to default value at the time of sending the
+// request, implying it was set by the user, or if the field has explicit
+// presence and is set by the user, the field must not be auto-populated by
+// the client. Values automatically populated this way must be reused for
+// retries of the same request.
+func (g *generator) initializeAutoPopulatedFields(servName string, m *descriptor.MethodDescriptorProto) {
+	apfs := g.autoPopulatedFields(servName, m)
+	if len(apfs) == 0 {
+		return
+	}
+	g.imports[pbinfo.ImportSpec{Path: "github.com/google/uuid"}] = true
+	p := g.printf
+	for _, apf := range apfs {
+		f := buildAccessor(apf.GetName(), true)
+		if apf.GetProto3Optional() {
+			// Type will be *string if field has explicit presence.
+			p("if req != nil && req%s == nil {", f)
+			p("  req%s = proto.String(uuid.NewString())", f)
+		} else {
+			// Type will be string if field does not have explicit presence.
+			p(`if req != nil && req%s == "" {`, buildAccessor(apf.GetName(), false))
+			p("  req%s = uuid.NewString()", f)
+		}
+		p("}")
+	}
+}
+
 func buildAccessor(field string, rawFinal bool) string {
 	// Corner case if passed the result of strings.Join on an empty slice.
 	if field == "" {
diff --git a/internal/gengapic/gengrpc.go b/internal/gengapic/gengrpc.go
index 922ab59..efff793 100644
--- a/internal/gengapic/gengrpc.go
+++ b/internal/gengapic/gengrpc.go
@@ -104,6 +104,7 @@ func (g *generator) unaryGRPCCall(servName string, m *descriptor.MethodDescripto
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName(), retTyp)
 
 	g.insertRequestHeaders(m, grpc)
+	g.initializeAutoPopulatedFields(servName, m)
 	g.appendCallOpts(m)
 
 	p("var resp *%s", retTyp)
@@ -142,6 +143,7 @@ func (g *generator) emptyUnaryGRPCCall(servName string, m *descriptor.MethodDesc
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName())
 
 	g.insertRequestHeaders(m, grpc)
+	g.initializeAutoPopulatedFields(servName, m)
 	g.appendCallOpts(m)
 	p("err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {")
 	p("  var err error")
diff --git a/internal/gengapic/genrest.go b/internal/gengapic/genrest.go
index e0c3c79..6ce4173 100644
--- a/internal/gengapic/genrest.go
+++ b/internal/gengapic/genrest.go
@@ -1020,6 +1020,7 @@ func (g *generator) emptyUnaryRESTCall(servName string, m *descriptor.MethodDesc
 	p("func (c *%s) %s(ctx context.Context, req *%s.%s, opts ...gax.CallOption) error {",
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName())
 
+	g.initializeAutoPopulatedFields(servName, m)
 	// TODO(dovs): handle cancellation, metadata, osv.
 	// TODO(dovs): handle http headers
 	// TODO(dovs): handle deadlines
@@ -1113,6 +1114,7 @@ func (g *generator) unaryRESTCall(servName string, m *descriptor.MethodDescripto
 	p("func (c *%s) %s(ctx context.Context, req *%s.%s, opts ...gax.CallOption) (%s, error) {",
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName(), retTyp)
 
+	g.initializeAutoPopulatedFields(servName, m)
 	// TODO(dovs): handle cancellation, metadata, osv.
 	// TODO(dovs): handle http headers
 	// TODO(dovs): handle deadlines?
diff --git a/internal/gengapic/helpers.go b/internal/gengapic/helpers.go
index 864fffa..464bb9d 100644
--- a/internal/gengapic/helpers.go
+++ b/internal/gengapic/helpers.go
@@ -110,15 +110,21 @@ func grpcClientField(reducedServName string) string {
 	return lowerFirst(reducedServName + "Client")
 }
 
-// hasField returns true if the target DescriptorProto has the given field,
-// otherwise it returns false.
-func hasField(m *descriptor.DescriptorProto, field string) bool {
+// getField returns a FieldDescriptorProto pointer if the target
+// DescriptorProto has the given field, otherwise it returns nil.
+func getField(m *descriptor.DescriptorProto, field string) *descriptor.FieldDescriptorProto {
 	for _, f := range m.GetField() {
 		if f.GetName() == field {
-			return true
+			return f
 		}
 	}
-	return false
+	return nil
+}
+
+// hasField returns true if the target DescriptorProto has the given field,
+// otherwise it returns false.
+func hasField(m *descriptor.DescriptorProto, field string) bool {
+	return getField(m, field) != nil
 }
 
 // hasMethod reports if the given service defines an RPC with the same name as
diff --git a/rules_go_gapic/go_gapic.bzl b/rules_go_gapic/go_gapic.bzl
index f1a52d9..b32c2dc 100644
--- a/rules_go_gapic/go_gapic.bzl
+++ b/rules_go_gapic/go_gapic.bzl
@@ -154,6 +154,7 @@ def go_gapic_library(
   )
 
   actual_deps = deps + [
+    "@com_github_google_uuid//:go_default_library",
     "@com_github_googleapis_gax_go_v2//:go_default_library",
     "@com_github_googleapis_gax_go_v2//apierror:go_default_library",
     "@org_golang_google_api//googleapi:go_default_library",


Test patch:
diff --git a/internal/gengapic/generator_test.go b/internal/gengapic/generator_test.go
new file mode 100644
index 0000000..c193952
--- /dev/null
+++ b/internal/gengapic/generator_test.go
@@ -0,0 +1,140 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gengapic
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/google/go-cmp/cmp"
+	"github.com/googleapis/gapic-generator-go/internal/pbinfo"
+	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/genproto/googleapis/api/serviceconfig"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protocmp"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+func TestAutoPopulatedFields(t *testing.T) {
+	optsUUID4 := &descriptorpb.FieldOptions{}
+	proto.SetExtension(optsUUID4, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_UUID4})
+
+	optsIPV4 := &descriptorpb.FieldOptions{}
+	proto.SetExtension(optsIPV4, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_IPV4})
+
+	optsRequiredAndUUID4 := &descriptorpb.FieldOptions{}
+	proto.SetExtension(optsRequiredAndUUID4, annotations.E_FieldBehavior, []annotations.FieldBehavior{annotations.FieldBehavior_REQUIRED})
+	proto.SetExtension(optsRequiredAndUUID4, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_UUID4})
+
+	requestIDField := &descriptor.FieldDescriptorProto{
+		Name:           proto.String("request_id"),
+		Type:           typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+		Label:          labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+		Proto3Optional: proto.Bool(true),
+		Options:        optsUUID4,
+	}
+	inputType := &descriptor.DescriptorProto{
+		Name: proto.String("InputType"),
+		Field: []*descriptor.FieldDescriptorProto{
+			requestIDField,
+			{
+				Name:  proto.String("invalid_auto_populated_not_in_serviceconfig"),
+				Type:  typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label: labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+			},
+			{
+				Name:  proto.String("invalid_auto_populated_no_annotation"),
+				Type:  typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label: labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+			},
+			{
+				Name:    proto.String("invalid_auto_populated_required"),
+				Type:    typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label:   labelp(descriptor.FieldDescriptorProto_LABEL_REQUIRED),
+				Options: optsRequiredAndUUID4,
+			},
+			{
+				Name:    proto.String("invalid_auto_populated_int"),
+				Type:    typep(descriptor.FieldDescriptorProto_TYPE_INT64),
+				Label:   labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+				Options: optsUUID4,
+			},
+			{
+				Name:    proto.String("invalid_auto_populated_ipv4"),
+				Type:    typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label:   labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+				Options: optsIPV4,
+			},
+		},
+	}
+	outputType := &descriptor.DescriptorProto{
+		Name: proto.String("OutputType"),
+	}
+	file := &descriptor.FileDescriptorProto{
+		Package: proto.String("my.pkg"),
+		Options: &descriptor.FileOptions{
+			GoPackage: proto.String("mypackage"),
+		},
+	}
+	serv := &descriptor.ServiceDescriptorProto{
+		Name: proto.String("Foo"),
+	}
+
+	var g generator
+	g.opts = &options{
+		pkgName: "pkg",
+	}
+	g.imports = map[pbinfo.ImportSpec]bool{}
+
+	g.serviceConfig = &serviceconfig.Service{
+		Publishing: &annotations.Publishing{
+			MethodSettings: []*annotations.MethodSettings{
+				{
+					Selector: "my.pkg.Foo.GetOneThing",
+					AutoPopulatedFields: []string{
+						"request_id",
+						"invalid_auto_populated_no_annotation",
+						"invalid_auto_populated_required",
+						"invalid_auto_populated_int",
+						"invalid_auto_populated_ipv4",
+					},
+				},
+			},
+		},
+	}
+
+	commonTypes(&g)
+	g.descInfo.Type[".my.pkg."+inputType.GetName()] = inputType
+	g.descInfo.ParentFile[inputType] = file
+	g.descInfo.Type[".my.pkg."+outputType.GetName()] = outputType
+	g.descInfo.ParentFile[outputType] = file
+	g.descInfo.ParentFile[serv] = file
+
+	m := &descriptor.MethodDescriptorProto{
+		Name:       proto.String("GetOneThing"),
+		InputType:  proto.String(".my.pkg.InputType"),
+		OutputType: proto.String(".my.pkg.OutputType"),
+		Options:    &descriptor.MethodOptions{},
+	}
+	g.descInfo.ParentElement[m] = serv
+	serv.Method = []*descriptor.MethodDescriptorProto{m}
+
+	got := g.autoPopulatedFields(serv.GetName(), m)
+
+	want := []*descriptorpb.FieldDescriptorProto{requestIDField}
+	if diff := cmp.Diff(got, want, protocmp.Transform()); diff != "" {
+		t.Errorf("got(-),want(+):\n%s", diff)
+	}
+}
diff --git a/internal/gengapic/gengapic_test.go b/internal/gengapic/gengapic_test.go
index de0c04a..35b1fc3 100644
--- a/internal/gengapic/gengapic_test.go
+++ b/internal/gengapic/gengapic_test.go
@@ -29,6 +29,7 @@ import (
 	"github.com/googleapis/gapic-generator-go/internal/snippets"
 	"github.com/googleapis/gapic-generator-go/internal/txtdiff"
 	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/genproto/googleapis/api/serviceconfig"
 	metadatapb "google.golang.org/genproto/googleapis/gapic/metadata"
 	"google.golang.org/protobuf/encoding/protojson"
 	"google.golang.org/protobuf/proto"
@@ -149,6 +150,9 @@ func TestGenGRPCMethods(t *testing.T) {
 		Name: proto.String("NestedEnum"),
 	}
 
+	optsUUID4 := &descriptorpb.FieldOptions{}
+	proto.SetExtension(optsUUID4, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_UUID4})
+
 	inputType := &descriptor.DescriptorProto{
 		Name: proto.String("InputType"),
 		Field: []*descriptor.FieldDescriptorProto{
@@ -183,6 +187,19 @@ func TestGenGRPCMethods(t *testing.T) {
 				Type:     typep(descriptor.FieldDescriptorProto_TYPE_ENUM),
 				TypeName: proto.String(".my.pkg.InputType.NestedEnum"),
 			},
+			{
+				Name:           proto.String("request_id"),
+				Type:           typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label:          labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+				Proto3Optional: proto.Bool(true),
+				Options:        optsUUID4,
+			},
+			{
+				Name:    proto.String("non_proto3optional_request_id"),
+				Type:    typep(descriptor.FieldDescriptorProto_TYPE_STRING),
+				Label:   labelp(descriptor.FieldDescriptorProto_LABEL_OPTIONAL),
+				Options: optsUUID4,
+			},
 		},
 		EnumType: []*descriptor.EnumDescriptorProto{
 			nestedEnum,
@@ -321,6 +338,27 @@ func TestGenGRPCMethods(t *testing.T) {
 		"google.iam.v1.IAMPolicy":         iamPolicyMethods(),
 	}
 	g.imports = map[pbinfo.ImportSpec]bool{}
+
+	g.serviceConfig = &serviceconfig.Service{
+		Publishing: &annotations.Publishing{
+			MethodSettings: []*annotations.MethodSettings{
+				{
+					Selector: "my.pkg.Foo.GetEmptyThing",
+					AutoPopulatedFields: []string{
+						"request_id",
+					},
+				},
+				{
+					Selector: "my.pkg.Foo.GetOneThing",
+					AutoPopulatedFields: []string{
+						"request_id",
+						"non_proto3optional_request_id",
+					},
+				},
+			},
+		},
+	}
+
 	cpb := &conf.ServiceConfig{
 		MethodConfig: []*conf.MethodConfig{
 			{
@@ -371,6 +409,7 @@ func TestGenGRPCMethods(t *testing.T) {
 			},
 			imports: map[pbinfo.ImportSpec]bool{
 				{Path: "fmt"}:                            true,
+				{Path: "github.com/google/uuid"}:         true,
 				{Path: "net/url"}:                        true,
 				{Name: "mypackagepb", Path: "mypackage"}: true,
 			},
@@ -384,6 +423,7 @@ func TestGenGRPCMethods(t *testing.T) {
 			},
 			imports: map[pbinfo.ImportSpec]bool{
 				{Path: "fmt"}:                            true,
+				{Path: "github.com/google/uuid"}:         true,
 				{Path: "net/url"}:                        true,
 				{Name: "mypackagepb", Path: "mypackage"}: true,
 			},
diff --git a/internal/gengapic/genrest_test.go b/internal/gengapic/genrest_test.go
index f5d1d12..f28b009 100644
--- a/internal/gengapic/genrest_test.go
+++ b/internal/gengapic/genrest_test.go
@@ -449,10 +449,18 @@ func TestGenRestMethod(t *testing.T) {
 		Type:           descriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
 		Proto3Optional: proto.Bool(true),
 	}
+	infoOpts := &descriptorpb.FieldOptions{}
+	proto.SetExtension(infoOpts, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_UUID4})
+	requestIDField := &descriptor.FieldDescriptorProto{
+		Name:           proto.String("request_id"),
+		Type:           descriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
+		Proto3Optional: proto.Bool(true),
+		Options:        infoOpts,
+	}
 
 	foo := &descriptor.DescriptorProto{
 		Name:  proto.String("Foo"),
-		Field: []*descriptor.FieldDescriptorProto{sizeField, otherField},
+		Field: []*descriptor.FieldDescriptorProto{sizeField, otherField, requestIDField},
 	}
 	foofqn := fmt.Sprintf(".%s.Foo", pkg)
 
@@ -764,9 +772,10 @@ func TestGenRestMethod(t *testing.T) {
 			method:  emptyRPC,
 			options: &options{},
 			imports: map[pbinfo.ImportSpec]bool{
-				{Path: "fmt"}: true,
-				{Path: "google.golang.org/api/googleapi"}:                        true,
-				{Path: "net/url"}:                                                true,
+				{Path: "fmt"}:                             true,
+				{Path: "github.com/google/uuid"}:          true,
+				{Path: "google.golang.org/api/googleapi"}: true,
+				{Path: "net/url"}:                         true,
 				{Name: "foopb", Path: "google.golang.org/genproto/cloud/foo/v1"}: true,
 			},
 		},
@@ -775,8 +784,9 @@ func TestGenRestMethod(t *testing.T) {
 			method:  unaryRPC,
 			options: &options{restNumericEnum: true},
 			imports: map[pbinfo.ImportSpec]bool{
-				{Path: "bytes"}: true,
-				{Path: "fmt"}:   true,
+				{Path: "bytes"}:                  true,
+				{Path: "fmt"}:                    true,
+				{Path: "github.com/google/uuid"}: true,
 				{Path: "google.golang.org/protobuf/encoding/protojson"}: true,
 				{Path: "io"}: true,
 				{Path: "google.golang.org/api/googleapi"}: true,
@@ -891,6 +901,22 @@ func TestGenRestMethod(t *testing.T) {
 						},
 					},
 				},
+				Publishing: &annotations.Publishing{
+					MethodSettings: []*annotations.MethodSettings{
+						{
+							Selector: "google.cloud.foo.v1.FooService.UnaryRPC",
+							AutoPopulatedFields: []string{
+								"request_id",
+							},
+						},
+						{
+							Selector: "google.cloud.foo.v1.FooService.EmptyRPC",
+							AutoPopulatedFields: []string{
+								"request_id",
+							},
+						},
+					},
+				},
 			}
 
 			if err := g.genRESTMethod("Foo", s, tst.method); err != nil {
diff --git a/internal/gengapic/testdata/method_GetEmptyThing.want b/internal/gengapic/testdata/method_GetEmptyThing.want
index 756cc49..eaf6c42 100644
--- a/internal/gengapic/testdata/method_GetEmptyThing.want
+++ b/internal/gengapic/testdata/method_GetEmptyThing.want
@@ -3,6 +3,9 @@ func (c *fooGRPCClient) GetEmptyThing(ctx context.Context, req *mypackagepb.Inpu
 
 	hds = append(c.xGoogHeaders, hds...)
 	ctx = gax.InsertMetadataIntoOutgoingContext(ctx, hds...)
+	if req != nil && req.RequestId == nil {
+		req.RequestId = proto.String(uuid.NewString())
+	}
 	opts = append((*c.CallOptions).GetEmptyThing[0:len((*c.CallOptions).GetEmptyThing):len((*c.CallOptions).GetEmptyThing)], opts...)
 	err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
 		var err error
diff --git a/internal/gengapic/testdata/method_GetOneThing.want b/internal/gengapic/testdata/method_GetOneThing.want
index ac0ba44..54ba5d7 100644
--- a/internal/gengapic/testdata/method_GetOneThing.want
+++ b/internal/gengapic/testdata/method_GetOneThing.want
@@ -3,6 +3,12 @@ func (c *fooGRPCClient) GetOneThing(ctx context.Context, req *mypackagepb.InputT
 
 	hds = append(c.xGoogHeaders, hds...)
 	ctx = gax.InsertMetadataIntoOutgoingContext(ctx, hds...)
+	if req != nil && req.RequestId == nil {
+		req.RequestId = proto.String(uuid.NewString())
+	}
+	if req != nil && req.GetNonProto3optionalRequestId() == "" {
+		req.NonProto3optionalRequestId = uuid.NewString()
+	}
 	opts = append((*c.CallOptions).GetOneThing[0:len((*c.CallOptions).GetOneThing):len((*c.CallOptions).GetOneThing)], opts...)
 	var resp *mypackagepb.OutputType
 	err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
diff --git a/internal/gengapic/testdata/rest_CustomOp.want b/internal/gengapic/testdata/rest_CustomOp.want
index 7b0cecc..faa858f 100644
--- a/internal/gengapic/testdata/rest_CustomOp.want
+++ b/internal/gengapic/testdata/rest_CustomOp.want
@@ -9,6 +9,9 @@ func (c *fooRESTClient) CustomOp(ctx context.Context, req *foopb.Foo, opts ...ga
 	if req != nil && req.Other != nil {
 		params.Add("other", fmt.Sprintf("%v", req.GetOther()))
 	}
+	if req != nil && req.RequestId != nil {
+		params.Add("requestId", fmt.Sprintf("%v", req.GetRequestId()))
+	}
 	params.Add("size", fmt.Sprintf("%v", req.GetSize()))
 
 	baseUrl.RawQuery = params.Encode()
diff --git a/internal/gengapic/testdata/rest_EmptyRPC.want b/internal/gengapic/testdata/rest_EmptyRPC.want
index a3cde50..1cdcc02 100644
--- a/internal/gengapic/testdata/rest_EmptyRPC.want
+++ b/internal/gengapic/testdata/rest_EmptyRPC.want
@@ -1,4 +1,7 @@
 func (c *fooRESTClient) EmptyRPC(ctx context.Context, req *foopb.Foo, opts ...gax.CallOption) error {
+	if req != nil && req.RequestId == nil {
+		req.RequestId = proto.String(uuid.NewString())
+	}
 	baseUrl, err := url.Parse(c.endpoint)
 	if err != nil {
 		return err
@@ -6,6 +9,9 @@ func (c *fooRESTClient) EmptyRPC(ctx context.Context, req *foopb.Foo, opts ...ga
 	baseUrl.Path += fmt.Sprintf("/v1/foo/%v", req.GetOther())
 
 	params := url.Values{}
+	if req != nil && req.RequestId != nil {
+		params.Add("requestId", fmt.Sprintf("%v", req.GetRequestId()))
+	}
 	params.Add("size", fmt.Sprintf("%v", req.GetSize()))
 
 	baseUrl.RawQuery = params.Encode()
diff --git a/internal/gengapic/testdata/rest_UnaryRPC.want b/internal/gengapic/testdata/rest_UnaryRPC.want
index 625f36a..473af58 100644
--- a/internal/gengapic/testdata/rest_UnaryRPC.want
+++ b/internal/gengapic/testdata/rest_UnaryRPC.want
@@ -1,4 +1,7 @@
 func (c *fooRESTClient) UnaryRPC(ctx context.Context, req *foopb.Foo, opts ...gax.CallOption) (*foopb.Foo, error) {
+	if req != nil && req.RequestId == nil {
+		req.RequestId = proto.String(uuid.NewString())
+	}
 	m := protojson.MarshalOptions{AllowPartial: true, UseEnumNumbers: true}
 	jsonReq, err := m.Marshal(req)
 	if err != nil {