Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modelling service errors - gRPC Vs Google API

Handling errors in a gRPC service commonly requires both a status message and error codes. Both have two definitions:

  • Google APIs definition (googleapis/go-genproto) - the generated Go packages for common protocol buffer types, and the generated gRPC code for Google's gRPC APIs
  • gRPC definition (grpc/grpc-go) - the Go implementation of gRPC

The Go packages for both definitions of Status and Codes are:

Google APIs

  • Status: google.golang.org/genproto/googleapis/rpc/status
  • Codes: google.golang.org/genproto/googleapis/rpc/code

gRPC

  • Status: google.golang.org/grpc/status
  • Codes: google.golang.org/grpc/codes

Since I'm a client of my own gRPC service and not a client of an existing Google gRPC API, I want to use the gRPC definitions of Status and Code.

However, the gRPC proto file for Status is actually copied from Google APIs definition. See https://github.com/grpc/grpc/tree/master/src/proto/grpc/status. The go_package of status.proto is also unchanged, so both the Google API and gRPC definitions use the following Go package

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

The upshot is the only way to use Status when defining an API is by importing it with

import "google/rpc/status.proto";

...and importing the language bindings in Go with

import (
   "google.golang.org/genproto/googleapis/rpc/status"
)
// Go server code...

But as stated earlier, this is wrong since I'm not a client of a Google API, but rather my own gRPC service. Therefore the language bindings should be imported with

import (
   "google.golang.org/grpc/status"
)
// Go server code...

As expected if I switch to importing the gRPC language bindings and try and return a Status message to the API client, I get a compile error

cannot use &(status.Status literal)
(value of type *"google.golang.org/grpc/internal/status".Status) as 
*"google.golang.org/genproto/googleapis/rpc/status".Status value

This is caused by my .proto file using the Google API definition of Status while the server implementation (in Go) uses the gRPC definition.

The problem impacts error codes since Google APIs uses signed 32 bit integers (int32) whereas gRPC uses unsigned 32 bit integers (uint32).

Questions

  • Is my assertion that I should be using the gRPC definition of Status and Codes correct?
  • If my assertion is correct, how can I use the gRPC definition of Status when it's packaged for Google APIs?
like image 434
Jack Avatar asked Apr 21 '26 05:04

Jack


1 Answers

We need to distinguish a few cases. Some of them are obvious, some are not.

Just returning Status from a gRPC handler

If your proto schema (.proto files) doesn't define messages that use Status or Code directly, then the gRPC handlers can satisfy the return type error simply with "google.golang.org/grpc/status".Error(), or Newf().Err(). And that's about it.

Example:

// implements SomeServiceServer unary RPC GetFoo
func (s *SomeService) GetFoo(ctx context.Context, req *grpc.FooRequest) (*grpc.FooResponse, error) {
    // status is "google.golang.org/grpc/status"
    return nil, status.Error(codes.Unimplemented, "coming soon")

Using Status in your .proto files

In this case, you are forced to use the googleapis implementation. As you already have seen, the status.proto Go package is defined as:

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

So let's say you have the following .proto file, where the imported status.proto is just a copy-paste of the gRPC status.proto as per this question:

syntax = "proto3";

package test;

import "status.proto";

option go_package = ".;main";

message Foo {
  string a = 1;
  google.rpc.Status status = 2;
}

with directory structure as:

/protos
|_ status.proto
|_ test.proto

and you compile the above with:

cd protos && protoc -I=. --go_out=. test.proto

breathe ...then the generated Go code will have the following import

import (
    status "google.golang.org/genproto/googleapis/rpc/status"
)

and you must satisfy that by go get google.golang.org/genproto.

So about your first question, you can only use Status from googleapis in proto files, because that's how status.proto declares its Go package.

Using generated googleapis Status in Go code

Since the imported Go package is from googleapis that is what you must use in your Go code in order to initialize such messages:

package main

import (
    "fmt"
    googleapis "google.golang.org/genproto/googleapis/rpc/status"
)

func main() {
    foo := &Foo{
        A: "foo",
        Status: &googleapis.Status{
            Code:    int32(code.Code_OK),
            Message: "all good",
        },
    }
    // fmt.Println(foo)
}

Yes but I must use grpc-go Status in my Go code

You can't. protoc generates code with the packages described above. If you absolutely NEED to construct these Status fields using grpc-go, you can use Status.Proto:

package main

import (
    "fmt"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

func main() {
    foo := &Foo{
        A: "foo",
        Status: status.New(codes.OK, "all good").Proto(),
    }
    fmt.Println(foo)
}

Just for the record, the opposite is also possible with status.FromProto:

package main

import (
    "fmt"
    googleapis_codes "google.golang.org/genproto/googleapis/rpc/code"
    googleapis "google.golang.org/genproto/googleapis/rpc/status"
    "google.golang.org/grpc/status"
)

func main() {
    gapisStatus := &googleapis.Status{
        Code: int32(googleapis_codes.Code_OK),
        Message: "all good",
    }
    grpcStatus := status.FromProto(gapisStatus)
    fmt.Println(grpcStatus)
}

As a less well-behaved alternative, you can simply copy-paste the status.proto sources into your project and manually change the go_package:

option go_package = "google.golang.org/grpc/status;status";

This way protoc will generate the Go code with this import, and your own sources will be able to follow suit. Of course this means you now have your own fork of status.proto.

like image 111
blackgreen Avatar answered Apr 24 '26 05:04

blackgreen