diff --git a/internal/gengapic/doc_file.go b/internal/gengapic/doc_file.go index 5f8e53bb178..2c07ec2c656 100644 --- a/internal/gengapic/doc_file.go +++ b/internal/gengapic/doc_file.go @@ -81,7 +81,8 @@ func (g *generator) genDocFile(year int, serv *descriptorpb.ServiceDescriptorPro p("//") p("// To get started with this package, create a client.") // Code block for client creation - servName := pbinfo.ReduceServName(serv.GetName(), g.opts.pkgName) + override := g.getServiceNameOverride(serv) + servName := pbinfo.ReduceServNameWithOverride(serv.GetName(), g.opts.pkgName, override) tmpClient := g.pt g.pt = printer.P{} g.exampleInitClientWithOpts(g.opts.pkgName, servName, true) diff --git a/internal/gengapic/example.go b/internal/gengapic/example.go index 2061dd05a0f..218c98b7c9e 100644 --- a/internal/gengapic/example.go +++ b/internal/gengapic/example.go @@ -26,7 +26,8 @@ import ( func (g *generator) genExampleFile(serv *descriptorpb.ServiceDescriptorProto) error { pkgName := g.opts.pkgName - servName := pbinfo.ReduceServName(serv.GetName(), pkgName) + override := g.getServiceNameOverride(serv) + servName := pbinfo.ReduceServNameWithOverride(serv.GetName(), pkgName, override) g.exampleClientFactory(pkgName, servName) @@ -42,7 +43,8 @@ func (g *generator) genExampleFile(serv *descriptorpb.ServiceDescriptorProto) er func (g *generator) genExampleIteratorFile(serv *descriptorpb.ServiceDescriptorProto) error { pkgName := g.opts.pkgName - servName := pbinfo.ReduceServName(serv.GetName(), pkgName) + override := g.getServiceNameOverride(serv) + servName := pbinfo.ReduceServNameWithOverride(serv.GetName(), pkgName, override) methods := append(serv.GetMethod(), g.getMixinMethods()...) for _, m := range methods { // Don't need streaming RPCs diff --git a/internal/gengapic/generator.go b/internal/gengapic/generator.go index 731bd7b93fc..77d9644063b 100644 --- a/internal/gengapic/generator.go +++ b/internal/gengapic/generator.go @@ -350,3 +350,19 @@ func (g *generator) autoPopulatedFields(_ string, m *descriptorpb.MethodDescript } return validated } + +// getServiceNameOverride checks to see if the service has a defined service name override. +func (g *generator) getServiceNameOverride(s *descriptorpb.ServiceDescriptorProto) string { + ls := g.serviceConfig.GetPublishing().GetLibrarySettings() + if len(ls) == 0 { + return "" + } + + renamedServices := ls[0].GetGoSettings().GetRenamedServices() + + if v, ok := renamedServices[s.GetName()]; ok { + return v + } + + return "" +} diff --git a/internal/gengapic/gengapic.go b/internal/gengapic/gengapic.go index 1475a70e9d2..dcc1feb6357 100644 --- a/internal/gengapic/gengapic.go +++ b/internal/gengapic/gengapic.go @@ -111,7 +111,8 @@ func gen(genReq *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse // so even though the client for LoggingServiceV2 is just "Client" // the file name is "logging_client.go". // Keep the current behavior for now, but we could revisit this later. - servName := pbinfo.ReduceServName(s.GetName(), "") + override := g.getServiceNameOverride(s) + servName := pbinfo.ReduceServNameWithOverride(s.GetName(), "", override) outFile := camelToSnake(servName) outFile = filepath.Join(g.opts.outDir, outFile) @@ -334,7 +335,9 @@ func (g *generator) genAndCommitHelpers(scopes []string) error { // gen generates client for the given service. func (g *generator) gen(serv *descriptorpb.ServiceDescriptorProto) error { - servName := pbinfo.ReduceServName(serv.GetName(), g.opts.pkgName) + // If using service name overrides, use that directly for the rest of generation. + override := g.getServiceNameOverride(serv) + servName := pbinfo.ReduceServNameWithOverride(serv.GetName(), g.opts.pkgName, override) g.clientHook(servName) if err := g.clientOptions(serv, servName); err != nil { diff --git a/internal/gengapic/gengrpc.go b/internal/gengapic/gengrpc.go index 51f1671541e..5baf1d66eef 100644 --- a/internal/gengapic/gengrpc.go +++ b/internal/gengapic/gengrpc.go @@ -160,8 +160,9 @@ func (g *generator) emptyUnaryGRPCCall(servName string, m *descriptorpb.MethodDe } func (g *generator) grpcStubCall(method *descriptorpb.MethodDescriptorProto) string { - service := g.descInfo.ParentElement[method] - stub := pbinfo.ReduceServName(service.GetName(), g.opts.pkgName) + service := g.descInfo.ParentElement[method].(*descriptorpb.ServiceDescriptorProto) + override := g.getServiceNameOverride(service) + stub := pbinfo.ReduceServNameWithOverride(service.GetName(), g.opts.pkgName, override) return fmt.Sprintf("executeRPC(ctx, c.%s.%s, req, settings.GRPC, c.logger, %q)", grpcClientField(stub), method.GetName(), method.GetName()) } @@ -311,7 +312,11 @@ func (g *generator) grpcClientInit(serv *descriptorpb.ServiceDescriptorProto, se func (g *generator) grpcClientUtilities(serv *descriptorpb.ServiceDescriptorProto, servName string, imp pbinfo.ImportSpec, hasRPCForLRO bool) { p := g.printf - clientName := camelToSnake(serv.GetName()) + clientName := serv.GetName() + if override := g.getServiceNameOverride(serv); override != "" { + clientName = override + } + clientName = camelToSnake(clientName) clientName = strings.Replace(clientName, "_", " ", -1) lowcaseServName := lowcaseGRPCClientName(servName) diff --git a/internal/gengapic/gengrpc_test.go b/internal/gengapic/gengrpc_test.go new file mode 100644 index 00000000000..b9498c5862e --- /dev/null +++ b/internal/gengapic/gengrpc_test.go @@ -0,0 +1,126 @@ +// 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 ( + "bytes" + "path/filepath" + "testing" + + conf "github.com/googleapis/gapic-generator-go/internal/grpc_service_config" + "github.com/googleapis/gapic-generator-go/internal/pbinfo" + "github.com/googleapis/gapic-generator-go/internal/txtdiff" + "google.golang.org/genproto/googleapis/api/annotations" + "google.golang.org/genproto/googleapis/api/serviceconfig" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" + duration "google.golang.org/protobuf/types/known/durationpb" +) + +func TestServiceRenaming(t *testing.T) { + inputType := &descriptorpb.DescriptorProto{ + Name: proto.String("InputType"), + } + outputType := &descriptorpb.DescriptorProto{ + Name: proto.String("OutputType"), + } + metadataType := &descriptorpb.DescriptorProto{ + Name: proto.String("MetadataType"), + } + + file := &descriptorpb.FileDescriptorProto{ + Package: proto.String("my.pkg"), + Options: &descriptorpb.FileOptions{ + GoPackage: proto.String("mypackage"), + }, + } + serv := &descriptorpb.ServiceDescriptorProto{ + Name: proto.String("Foo"), + } + + var g generator + g.imports = map[pbinfo.ImportSpec]bool{} + g.opts = &options{ + pkgName: "pkg", + transports: []transport{grpc}, + } + cpb := &conf.ServiceConfig{ + MethodConfig: []*conf.MethodConfig{ + { + Name: []*conf.MethodConfig_Name{ + { + Service: "my.pkg.Foo", + }, + }, + Timeout: &duration.Duration{Seconds: 10}, + }, + }, + } + data, err := protojson.Marshal(cpb) + if err != nil { + t.Error(err) + } + in := bytes.NewReader(data) + g.grpcConf, err = conf.New(in) + if err != nil { + t.Error(err) + } + + commonTypes(&g) + for _, typ := range []*descriptorpb.DescriptorProto{ + inputType, outputType, metadataType, + } { + g.descInfo.Type[".my.pkg."+*typ.Name] = typ + g.descInfo.ParentFile[typ] = file + } + g.descInfo.ParentFile[serv] = file + g.descInfo.ParentElement = map[pbinfo.ProtoType]pbinfo.ProtoType{} + g.serviceConfig = &serviceconfig.Service{ + Publishing: &annotations.Publishing{ + LibrarySettings: []*annotations.ClientLibrarySettings{ + { + GoSettings: &annotations.GoSettings{ + RenamedServices: map[string]string{"Foo": "Bar"}, + }, + }, + }, + }, + } + + m := &descriptorpb.MethodDescriptorProto{ + Name: proto.String("Baz"), + InputType: proto.String(".my.pkg.InputType"), + OutputType: proto.String(emptyType), + } + + serv.Method = []*descriptorpb.MethodDescriptorProto{ + m, + } + g.descInfo.ParentFile[serv] = file + g.descInfo.ParentElement[m] = serv + imp, err := g.descInfo.ImportSpec(serv) + if err != nil { + t.Fatal(err) + } + + // Test the generation of the client boilerplate and single gRPC method rename. + g.grpcClientInit(serv, "Bar", imp, false) + if err := g.genGRPCMethod("Bar", serv, m); err != nil { + t.Fatal(err) + } + + txtdiff.Diff(t, g.pt.String(), filepath.Join("testdata", "service_rename.want")) +} diff --git a/internal/gengapic/genrest.go b/internal/gengapic/genrest.go index b4efd7d1e9a..44df5e53db1 100644 --- a/internal/gengapic/genrest.go +++ b/internal/gengapic/genrest.go @@ -147,9 +147,15 @@ func (g *generator) restClientOptions(serv *descriptorpb.ServiceDescriptorProto, func (g *generator) restClientUtilities(serv *descriptorpb.ServiceDescriptorProto, servName string, hasRPCForLRO bool) { p := g.printf - lowcaseServName := lowcaseRestClientName(servName) - clientName := camelToSnake(serv.GetName()) + + clientName := serv.GetName() + if override := g.getServiceNameOverride(serv); override != "" { + clientName = override + } + clientName = camelToSnake(clientName) clientName = strings.Replace(clientName, "_", " ", -1) + lowcaseServName := lowcaseRestClientName(servName) + opServ, hasCustomOp := g.customOpServices[serv] p("// New%sRESTClient creates a new %s rest client.", servName, clientName) diff --git a/internal/gengapic/testdata/service_rename.want b/internal/gengapic/testdata/service_rename.want new file mode 100644 index 00000000000..d452b940b90 --- /dev/null +++ b/internal/gengapic/testdata/service_rename.want @@ -0,0 +1,87 @@ +// barGRPCClient is a client for interacting with over gRPC transport. +// +// Methods, except Close, may be called concurrently. However, fields must not be modified concurrently with method calls. +type barGRPCClient struct { + // Connection pool of gRPC connections to the service. + connPool gtransport.ConnPool + + // Points back to the CallOptions field of the containing BarClient + CallOptions **BarCallOptions + + // The gRPC API client. + barClient mypackagepb.FooClient + + // The x-goog-* metadata to be sent with each request. + xGoogHeaders []string + + logger *slog.Logger +} + +// NewBarClient creates a new bar client based on gRPC. +// The returned client must be Closed when it is done being used to clean up its underlying connections. +func NewBarClient(ctx context.Context, opts ...option.ClientOption) (*BarClient, error) { + clientOpts := defaultBarGRPCClientOptions() + if newBarClientHook != nil { + hookOpts, err := newBarClientHook(ctx, clientHookParams{}) + if err != nil { + return nil, err + } + clientOpts = append(clientOpts, hookOpts...) + } + + connPool, err := gtransport.DialPool(ctx, append(clientOpts, opts...)...) + if err != nil { + return nil, err + } + client := BarClient{CallOptions: defaultBarCallOptions()} + + c := &barGRPCClient{ + connPool: connPool, + barClient: mypackagepb.NewFooClient(connPool), + CallOptions: &client.CallOptions, + logger: internaloption.GetLogger(opts), + + } + c.setGoogleClientInfo() + + client.internalClient = c + + return &client, nil +} + +// Connection returns a connection to the API service. +// +// Deprecated: Connections are now pooled so this method does not always +// return the same resource. +func (c *barGRPCClient) Connection() *grpc.ClientConn { + return c.connPool.Conn() +} + +// setGoogleClientInfo sets the name and version of the application in +// the `x-goog-api-client` header passed on each request. Intended for +// use by Google-written clients. +func (c *barGRPCClient) 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...), + } +} + +// Close closes the connection to the API service. The user should invoke this when +// the client is no longer required. +func (c *barGRPCClient) Close() error { + return c.connPool.Close() +} + +func (c *barGRPCClient) Baz(ctx context.Context, req *mypackagepb.InputType, opts ...gax.CallOption) error { + ctx = gax.InsertMetadataIntoOutgoingContext(ctx, c.xGoogHeaders...) + opts = append((*c.CallOptions).Baz[0:len((*c.CallOptions).Baz):len((*c.CallOptions).Baz)], opts...) + err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error { + var err error + _, err = executeRPC(ctx, c.barClient.Baz, req, settings.GRPC, c.logger, "Baz") + return err + }, opts...) + return err +} + diff --git a/internal/pbinfo/pbinfo.go b/internal/pbinfo/pbinfo.go index a0566d60e94..e7499dcd9cd 100644 --- a/internal/pbinfo/pbinfo.go +++ b/internal/pbinfo/pbinfo.go @@ -197,6 +197,15 @@ func (in *Info) ImportSpec(e ProtoType) (ImportSpec, error) { return imp, err } +// ReduceServNameWithOverride returns the override string if present, +// otherwise calls ReduceServName. +func ReduceServNameWithOverride(svc, pkg, override string) string { + if override != "" { + return override + } + return ReduceServName(svc, pkg) +} + // ReduceServName removes redundant components from the service name. // For example, FooServiceV2 -> Foo. // The returned name is used as part of longer names, like FooClient.