diff --git a/protoc-gen-go/grpc/grpc.go b/protoc-gen-go/grpc/grpc.go index 1ddfe836f4..9266fba15b 100644 --- a/protoc-gen-go/grpc/grpc.go +++ b/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() + // UnimplementedServer'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() diff --git a/protoc-gen-go/testdata/deprecated/deprecated.pb.go b/protoc-gen-go/testdata/deprecated/deprecated.pb.go index 5af4d22e2e..9268b4c24e 100644 --- a/protoc-gen-go/testdata/deprecated/deprecated.pb.go +++ b/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) diff --git a/protoc-gen-go/testdata/grpc/grpc.pb.go b/protoc-gen-go/testdata/grpc/grpc.pb.go index 98e4f40cd0..871ae728ef 100644 --- a/protoc-gen-go/testdata/grpc/grpc.pb.go +++ b/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) }