Repository: googleapis/gapic-generator-go
Pull request #1631 (2025-05-30 19:27:59)

fix(gengapic): add support for AutoPopulatedFields UUID4 to LROs
When adding the auto-populated fields feature in [feat(gengapic): add support for AutoPopulatedFields UUID4 #1460](https://github.com/googleapis/gapic-generator-go/pull/1460), based on the spec below (from  go/client-populate-request-id-design), I did not support auto-populated fields in the generation of long-running operation (LRO) RPCs. This was a mistake. I confirmed today with @noahdietz that LROs should indeed be treated as unary RPCs for this feature.

> Client library generators **should** enable automatic population of request fields at the time of sending the request in the specified format if and only if all of the following conditions hold:
>
>  * The field is a top-level string field of a unary method's request message.
>  * ...


Patch:
diff --git a/internal/gengapic/genrest.go b/internal/gengapic/genrest.go
index dc3bceb..c04d395 100644
--- a/internal/gengapic/genrest.go
+++ b/internal/gengapic/genrest.go
@@ -904,6 +904,7 @@ func (g *generator) lroRESTCall(servName string, m *descriptorpb.MethodDescripto
 	p("func (c *%s) %s(ctx context.Context, req *%s.%s, opts ...gax.CallOption) (*%s, error) {",
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName(), opWrapperType)
 
+	g.initializeAutoPopulatedFields(servName, m)
 	// TODO(noahdietz): handle cancellation, metadata, osv.
 	// TODO(noahdietz): handle http headers
 	// TODO(noahdietz): handle deadlines?
diff --git a/internal/gengapic/lro.go b/internal/gengapic/lro.go
index 71bcecf..b52a1f4 100644
--- a/internal/gengapic/lro.go
+++ b/internal/gengapic/lro.go
@@ -44,6 +44,7 @@ func (g *generator) lroCall(servName string, m *descriptorpb.MethodDescriptorPro
 		lowcaseServName, m.GetName(), inSpec.Name, inType.GetName(), lroType)
 
 	g.insertRequestHeaders(m, grpc)
+	g.initializeAutoPopulatedFields(servName, m)
 	g.appendCallOpts(m)
 
 	p("  var resp *%s.%s", outSpec.Name, outType.GetName())


Test patch:
diff --git a/internal/gengapic/gengapic_test.go b/internal/gengapic/gengapic_test.go
index 5786c04..77b1e57 100644
--- a/internal/gengapic/gengapic_test.go
+++ b/internal/gengapic/gengapic_test.go
@@ -674,8 +674,25 @@ func TestMethodDoc(t *testing.T) {
 }
 
 func TestGenOperationBuilders(t *testing.T) {
+	optsUUID4 := &descriptorpb.FieldOptions{}
+	proto.SetExtension(optsUUID4, annotations.E_FieldInfo, &annotations.FieldInfo{Format: annotations.FieldInfo_UUID4})
+
 	inputType := &descriptorpb.DescriptorProto{
 		Name: proto.String("InputType"),
+		Field: []*descriptorpb.FieldDescriptorProto{
+			{
+				Name:  proto.String("other"),
+				Type:  typep(descriptorpb.FieldDescriptorProto_TYPE_STRING),
+				Label: labelp(descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL),
+			},
+			{
+				Name:           proto.String("request_id"),
+				Type:           typep(descriptorpb.FieldDescriptorProto_TYPE_STRING),
+				Label:          labelp(descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL),
+				Proto3Optional: proto.Bool(true),
+				Options:        optsUUID4,
+			},
+		},
 	}
 	outputType := &descriptorpb.DescriptorProto{
 		Name: proto.String("OutputType"),
@@ -696,6 +713,19 @@ func TestGenOperationBuilders(t *testing.T) {
 
 	var g generator
 	g.imports = map[pbinfo.ImportSpec]bool{}
+	g.serviceConfig = &serviceconfig.Service{
+		Publishing: &annotations.Publishing{
+			MethodSettings: []*annotations.MethodSettings{
+				{
+					// Enable auto-populated field request_id only for EmptyLRO, not RespLRO.
+					Selector: "my.pkg.Foo.EmptyLRO",
+					AutoPopulatedFields: []string{
+						"request_id",
+					},
+				},
+			},
+		},
+	}
 	g.mixins = mixins{
 		"google.longrunning.Operations":   operationsMethods(),
 		"google.cloud.location.Locations": locationMethods(),
diff --git a/internal/gengapic/genrest_test.go b/internal/gengapic/genrest_test.go
index 9333b8d..36535c3 100644
--- a/internal/gengapic/genrest_test.go
+++ b/internal/gengapic/genrest_test.go
@@ -835,7 +835,8 @@ func TestGenRestMethod(t *testing.T) {
 			imports: map[pbinfo.ImportSpec]bool{
 				{Path: "bytes"}: true,
 				{Path: "cloud.google.com/go/longrunning"}: true,
-				{Path: "fmt"}: true,
+				{Path: "fmt"}:                    true,
+				{Path: "github.com/google/uuid"}: true,
 				{Path: "google.golang.org/protobuf/encoding/protojson"}: true,
 				{Path: "net/url"}: true,
 				{Name: "longrunningpb", Path: "cloud.google.com/go/longrunning/autogen/longrunningpb"}: true,
@@ -898,6 +899,12 @@ func TestGenRestMethod(t *testing.T) {
 								"request_id",
 							},
 						},
+						{
+							Selector: "google.cloud.foo.v1.FooService.LongrunningRPC",
+							AutoPopulatedFields: []string{
+								"request_id",
+							},
+						},
 					},
 				},
 			}
diff --git a/internal/gengapic/testdata/method_EmptyLRO.want b/internal/gengapic/testdata/method_EmptyLRO.want
index bf16102..b2e1ed9 100644
--- a/internal/gengapic/testdata/method_EmptyLRO.want
+++ b/internal/gengapic/testdata/method_EmptyLRO.want
@@ -1,5 +1,8 @@
 func (c *fooGRPCClient) EmptyLRO(ctx context.Context, req *mypackagepb.InputType, opts ...gax.CallOption) (*EmptyLROOperation, error) {
 	ctx = gax.InsertMetadataIntoOutgoingContext(ctx, c.xGoogHeaders...)
+	if req != nil && req.RequestId == nil {
+		req.RequestId = proto.String(uuid.NewString())
+	}
 	opts = append((*c.CallOptions).EmptyLRO[0:len((*c.CallOptions).EmptyLRO):len((*c.CallOptions).EmptyLRO)], opts...)
 	var resp *longrunningpb.Operation
 	err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
diff --git a/internal/gengapic/testdata/rest_LongrunningRPC.want b/internal/gengapic/testdata/rest_LongrunningRPC.want
index 3cd9638..ac7003a 100644
--- a/internal/gengapic/testdata/rest_LongrunningRPC.want
+++ b/internal/gengapic/testdata/rest_LongrunningRPC.want
@@ -1,4 +1,7 @@
 func (c *fooRESTClient) LongrunningRPC(ctx context.Context, req *foopb.Foo, opts ...gax.CallOption) (*LongrunningRPCOperation, 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 {
diff --git a/showcase/compliance_test.go b/showcase/compliance_test.go
index 74102ad..b739fde 100644
--- a/showcase/compliance_test.go
+++ b/showcase/compliance_test.go
@@ -70,7 +70,7 @@ func TestComplianceSuite(t *testing.T) {
 
 	suite, err := getComplianceSuite()
 	if err != nil {
-		t.Fatalf(err.Error())
+		t.Fatal(err.Error())
 	}
 
 	for _, group := range suite.GetGroup() {