Skip to content

Commit 318d17d

Browse files
prannaykdsnet
authored andcommitted
Add UnimplementedServer for server interface (#785)
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.
1 parent b5d812f commit 318d17d

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

protoc-gen-go/grpc/grpc.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ const generatedCodeVersion = 4
5252
// Paths for packages used by code generated in this file,
5353
// relative to the import_prefix of the generator.Generator.
5454
const (
55+
errorPkgPath = "google.golang.org/grpc/status"
5556
contextPkgPath = "context"
5657
grpcPkgPath = "google.golang.org/grpc"
58+
codePkgPath = "google.golang.org/grpc/codes"
5759
)
5860

5961
func init() {
@@ -77,6 +79,8 @@ func (g *grpc) Name() string {
7779
var (
7880
contextPkg string
7981
grpcPkg string
82+
errorPkg string
83+
codePkg string
8084
)
8185

8286
// Init initializes the plugin.
@@ -105,13 +109,19 @@ func (g *grpc) Generate(file *generator.FileDescriptor) {
105109
return
106110
}
107111

112+
errorPkg = string(g.gen.AddImport(errorPkgPath))
113+
codePkg = string(g.gen.AddImport(codePkgPath))
108114
contextPkg = string(g.gen.AddImport(contextPkgPath))
109115
grpcPkg = string(g.gen.AddImport(grpcPkgPath))
110116

111117
g.P("// Reference imports to suppress errors if they are not otherwise used.")
112118
g.P("var _ ", contextPkg, ".Context")
113119
g.P("var _ ", grpcPkg, ".ClientConn")
114120
g.P()
121+
g.P("func errUnimplemented(methodName string) error {")
122+
g.P("\treturn ", errorPkg, ".Errorf(codes.Unimplemented, \"method %s not implemented\", methodName)")
123+
g.P("}")
124+
g.P()
115125

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

229+
// Server Unimplemented struct for forward compatability.
230+
if deprecated {
231+
g.P(deprecationComment)
232+
}
233+
g.generateUnimplementedServer(servName, service)
234+
219235
// Server registration.
220236
if deprecated {
221237
g.P(deprecationComment)
@@ -269,6 +285,34 @@ func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.Servi
269285
g.P()
270286
}
271287

288+
// generateUnimplementedServer creates the unimplemented server struct
289+
func (g *grpc) generateUnimplementedServer(servName string, service *pb.ServiceDescriptorProto) {
290+
serverType := servName + "Server"
291+
g.P("// Unimplemented", serverType, " can be embedded to have forward compatible implementations.")
292+
g.P("type Unimplemented", serverType, " struct {")
293+
g.P("}")
294+
g.P()
295+
// Unimplemented<service_name>Server's concrete methods
296+
for _, method := range service.Method {
297+
g.P(g.generateServerMethodConcrete(servName, method))
298+
}
299+
g.P()
300+
}
301+
302+
// generateServerMethodConcrete returns unimplemented methods which ensure forward compatibility
303+
func (g *grpc) generateServerMethodConcrete(servName string, method *pb.MethodDescriptorProto) string {
304+
header := g.generateServerSignatureWithParamNames(servName, method)
305+
implementation := fmt.Sprintf("func (*Unimplemented%sServer) %s {\n", servName, header)
306+
implementation += fmt.Sprintf("\treturn ")
307+
if !method.GetServerStreaming() && !method.GetClientStreaming() {
308+
implementation += "nil, "
309+
}
310+
origMethName := method.GetName()
311+
methName := generator.CamelCase(origMethName)
312+
implementation += fmt.Sprintf("errUnimplemented(%q)\n}", methName)
313+
return implementation
314+
}
315+
272316
// generateClientSignature returns the client-side signature for a method.
273317
func (g *grpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string {
274318
origMethName := method.GetName()
@@ -368,6 +412,30 @@ func (g *grpc) generateClientMethod(servName, fullServName, serviceDescVar strin
368412
}
369413
}
370414

415+
// generateServerSignatureWithParamNames returns the server-side signature for a method with parameter names.
416+
func (g *grpc) generateServerSignatureWithParamNames(servName string, method *pb.MethodDescriptorProto) string {
417+
origMethName := method.GetName()
418+
methName := generator.CamelCase(origMethName)
419+
if reservedClientName[methName] {
420+
methName += "_"
421+
}
422+
423+
var reqArgs []string
424+
ret := "error"
425+
if !method.GetServerStreaming() && !method.GetClientStreaming() {
426+
reqArgs = append(reqArgs, "ctx "+contextPkg+".Context")
427+
ret = "(*" + g.typeName(method.GetOutputType()) + ", error)"
428+
}
429+
if !method.GetClientStreaming() {
430+
reqArgs = append(reqArgs, "req *"+g.typeName(method.GetInputType()))
431+
}
432+
if method.GetServerStreaming() || method.GetClientStreaming() {
433+
reqArgs = append(reqArgs, "srv "+servName+"_"+generator.CamelCase(origMethName)+"Server")
434+
}
435+
436+
return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
437+
}
438+
371439
// generateServerSignature returns the server-side signature for a method.
372440
func (g *grpc) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string {
373441
origMethName := method.GetName()

protoc-gen-go/testdata/deprecated/deprecated.pb.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protoc-gen-go/testdata/grpc/grpc.pb.go

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)