Browse Source

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.
Prannay Khosla 7 years ago
parent
commit
318d17de72

+ 68 - 0
protoc-gen-go/grpc/grpc.go

@@ -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() {
@@ -77,6 +79,8 @@ func (g *grpc) Name() string {
 var (
 	contextPkg string
 	grpcPkg    string
+	errorPkg   string
+	codePkg    string
 )
 
 // Init initializes the plugin.
@@ -105,6 +109,8 @@ 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))
 
@@ -112,6 +118,10 @@ func (g *grpc) Generate(file *generator.FileDescriptor) {
 	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)")
+	g.P("}")
+	g.P()
 
 	// Assert version compatibility.
 	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
 	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)
@@ -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()
@@ -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()

+ 15 - 0
protoc-gen-go/testdata/deprecated/deprecated.pb.go

@@ -10,6 +10,8 @@ import (
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -194,6 +196,10 @@ var fileDescriptor_f64ba265cd7eae3f = []byte{
 var _ context.Context
 var _ grpc.ClientConn
 
+func errUnimplemented(methodName string) error {
+	return status.Errorf(codes.Unimplemented, "method %s not implemented", methodName)
+}
+
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the grpc package it is being compiled against.
 const _ = grpc.SupportPackageIsVersion4
@@ -235,6 +241,15 @@ type DeprecatedServiceServer interface {
 	DeprecatedCall(context.Context, *DeprecatedRequest) (*DeprecatedResponse, error)
 }
 
+// Deprecated: Do not use.
+// UnimplementedDeprecatedServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedDeprecatedServiceServer struct {
+}
+
+func (*UnimplementedDeprecatedServiceServer) DeprecatedCall(ctx context.Context, req *DeprecatedRequest) (*DeprecatedResponse, error) {
+	return nil, errUnimplemented("DeprecatedCall")
+}
+
 // Deprecated: Do not use.
 func RegisterDeprecatedServiceServer(s *grpc.Server, srv DeprecatedServiceServer) {
 	s.RegisterService(&_DeprecatedService_serviceDesc, srv)

+ 23 - 0
protoc-gen-go/testdata/grpc/grpc.pb.go

@@ -8,6 +8,8 @@ import (
 	fmt "fmt"
 	proto "github.com/golang/protobuf/proto"
 	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
 	math "math"
 )
 
@@ -179,6 +181,10 @@ var fileDescriptor_81ea47a3f88c2082 = []byte{
 var _ context.Context
 var _ grpc.ClientConn
 
+func errUnimplemented(methodName string) error {
+	return status.Errorf(codes.Unimplemented, "method %s not implemented", methodName)
+}
+
 // This is a compile-time assertion to ensure that this generated file
 // is compatible with the grpc package it is being compiled against.
 const _ = grpc.SupportPackageIsVersion4
@@ -321,6 +327,23 @@ type TestServer interface {
 	Bidi(Test_BidiServer) error
 }
 
+// UnimplementedTestServer can be embedded to have forward compatible implementations.
+type UnimplementedTestServer struct {
+}
+
+func (*UnimplementedTestServer) UnaryCall(ctx context.Context, req *SimpleRequest) (*SimpleResponse, error) {
+	return nil, errUnimplemented("UnaryCall")
+}
+func (*UnimplementedTestServer) Downstream(req *SimpleRequest, srv Test_DownstreamServer) error {
+	return errUnimplemented("Downstream")
+}
+func (*UnimplementedTestServer) Upstream(srv Test_UpstreamServer) error {
+	return errUnimplemented("Upstream")
+}
+func (*UnimplementedTestServer) Bidi(srv Test_BidiServer) error {
+	return errUnimplemented("Bidi")
+}
+
 func RegisterTestServer(s *grpc.Server, srv TestServer) {
 	s.RegisterService(&_Test_serviceDesc, srv)
 }