Skip to content

Commit

Permalink
feat: add service renaming (#1580)
Browse files Browse the repository at this point in the history
* feat: add service renaming

* fix other uses of ReduceServName

* fix comments

* fix comments and style

* switch to package relative renaming

* switch to official genproto package

* check for zero length lang settings

* override rest client naming

* add test for grpc client renaming

* call proper rest client name
  • Loading branch information
hongalex authored Dec 16, 2024
1 parent 4ce94d8 commit 8c5bc3d
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 10 deletions.
3 changes: 2 additions & 1 deletion internal/gengapic/doc_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions internal/gengapic/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions internal/gengapic/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
}
7 changes: 5 additions & 2 deletions internal/gengapic/gengapic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 8 additions & 3 deletions internal/gengapic/gengrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down Expand Up @@ -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)

Expand Down
126 changes: 126 additions & 0 deletions internal/gengapic/gengrpc_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
10 changes: 8 additions & 2 deletions internal/gengapic/genrest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
87 changes: 87 additions & 0 deletions internal/gengapic/testdata/service_rename.want
Original file line number Diff line number Diff line change
@@ -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
}

9 changes: 9 additions & 0 deletions internal/pbinfo/pbinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 8c5bc3d

Please sign in to comment.