Skip to content

Commit

Permalink
Add UnimplementedServer for server interface (#785)
Browse files Browse the repository at this point in the history
This changes protoc-gen-go-grpc to always emit an UnimplementedServer type
that implements the service interface. Users trying to implement
the service interface can embed this type to ensure that new methods
can be added in a forwards compatible manner.
  • Loading branch information
prannayk authored and dsnet committed Mar 15, 2019
1 parent b5d812f commit 318d17d
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
68 changes: 68 additions & 0 deletions protoc-gen-go/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ const generatedCodeVersion = 4
// Paths for packages used by code generated in this file,
// relative to the import_prefix of the generator.Generator.
const (
errorPkgPath = "google.golang.org/grpc/status"
contextPkgPath = "context"
grpcPkgPath = "google.golang.org/grpc"
codePkgPath = "google.golang.org/grpc/codes"
)

func init() {
Expand All @@ -77,6 +79,8 @@ func (g *grpc) Name() string {
var (
contextPkg string
grpcPkg string
errorPkg string
codePkg string
)

// Init initializes the plugin.
Expand Down Expand Up @@ -105,13 +109,19 @@ func (g *grpc) Generate(file *generator.FileDescriptor) {
return
}

errorPkg = string(g.gen.AddImport(errorPkgPath))
codePkg = string(g.gen.AddImport(codePkgPath))
contextPkg = string(g.gen.AddImport(contextPkgPath))
grpcPkg = string(g.gen.AddImport(grpcPkgPath))

g.P("// Reference imports to suppress errors if they are not otherwise used.")
g.P("var _ ", contextPkg, ".Context")
g.P("var _ ", grpcPkg, ".ClientConn")
g.P()
g.P("func errUnimplemented(methodName string) error {")
g.P("\treturn ", errorPkg, ".Errorf(codes.Unimplemented, \"method %s not implemented\", methodName)")

This comment has been minimized.

Copy link
@dsymonds

dsymonds Mar 16, 2019

Contributor

this should be using codePkg.

This comment has been minimized.

Copy link
@dsnet

dsnet Mar 18, 2019

Member

Thanks for point this out.

g.P("}")
g.P()

// Assert version compatibility.
g.P("// This is a compile-time assertion to ensure that this generated file")
Expand Down Expand Up @@ -216,6 +226,12 @@ func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.Servi
g.P("}")
g.P()

// Server Unimplemented struct for forward compatability.
if deprecated {
g.P(deprecationComment)
}
g.generateUnimplementedServer(servName, service)

// Server registration.
if deprecated {
g.P(deprecationComment)
Expand Down Expand Up @@ -269,6 +285,34 @@ func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.Servi
g.P()
}

// generateUnimplementedServer creates the unimplemented server struct
func (g *grpc) generateUnimplementedServer(servName string, service *pb.ServiceDescriptorProto) {
serverType := servName + "Server"
g.P("// Unimplemented", serverType, " can be embedded to have forward compatible implementations.")
g.P("type Unimplemented", serverType, " struct {")
g.P("}")
g.P()
// Unimplemented<service_name>Server's concrete methods
for _, method := range service.Method {
g.P(g.generateServerMethodConcrete(servName, method))
}
g.P()
}

// generateServerMethodConcrete returns unimplemented methods which ensure forward compatibility
func (g *grpc) generateServerMethodConcrete(servName string, method *pb.MethodDescriptorProto) string {
header := g.generateServerSignatureWithParamNames(servName, method)
implementation := fmt.Sprintf("func (*Unimplemented%sServer) %s {\n", servName, header)
implementation += fmt.Sprintf("\treturn ")
if !method.GetServerStreaming() && !method.GetClientStreaming() {
implementation += "nil, "
}
origMethName := method.GetName()
methName := generator.CamelCase(origMethName)
implementation += fmt.Sprintf("errUnimplemented(%q)\n}", methName)
return implementation
}

// generateClientSignature returns the client-side signature for a method.
func (g *grpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
Expand Down Expand Up @@ -368,6 +412,30 @@ func (g *grpc) generateClientMethod(servName, fullServName, serviceDescVar strin
}
}

// generateServerSignatureWithParamNames returns the server-side signature for a method with parameter names.
func (g *grpc) generateServerSignatureWithParamNames(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
methName := generator.CamelCase(origMethName)
if reservedClientName[methName] {
methName += "_"
}

var reqArgs []string
ret := "error"
if !method.GetServerStreaming() && !method.GetClientStreaming() {
reqArgs = append(reqArgs, "ctx "+contextPkg+".Context")
ret = "(*" + g.typeName(method.GetOutputType()) + ", error)"
}
if !method.GetClientStreaming() {
reqArgs = append(reqArgs, "req *"+g.typeName(method.GetInputType()))
}
if method.GetServerStreaming() || method.GetClientStreaming() {
reqArgs = append(reqArgs, "srv "+servName+"_"+generator.CamelCase(origMethName)+"Server")
}

return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
}

// generateServerSignature returns the server-side signature for a method.
func (g *grpc) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
Expand Down
15 changes: 15 additions & 0 deletions protoc-gen-go/testdata/deprecated/deprecated.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions protoc-gen-go/testdata/grpc/grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 318d17d

Please sign in to comment.