[GO] return errdetails on error with grpc-node

return errdetails on error with grpc-node

If you select node as the backend for gprc, I think the official grpc-node will be selected as the framework. https://github.com/grpc/grpc-node

The normal system can be implemented smoothly, but I had a hard time when I wanted to use errdetails in Go etc. at the time of error, so that memorandum (I don't know if it's called errdetails exactly, but I call it because Go's package name is errdetails)

What are errdetails?

A message object that can describe error details provided by the gRPC framework. For example, when designing REST-API, I think it is necessary to design the error object of json as well. There is no unified format (there is a standard called RFC7807, but it is rarely followed), and it is expected that it will be confused at the time of design.

Therefore, by using the message object and mechanism provided by gRPC, you can avoid exhaustion at the time of design.

In the implementation example below, the status is ʻInvalidArgument and the name field can return an error with the required information. It's like "HTTP status code = 400` and json of error details are returned together "in REST. (Since it is a sample code, it may be wrong)

Server side


sts := status.New(codes.InvalidArgument, "validation error")
badRequest := &errdetails.BadRequest{
    FieldViolations: []*errdetails.BadRequest_FieldViolation{
        {
            Field:       "name",
            Description: "Must not be null",
        },
    },
}
details, _ := sts.WithDetails(badRequest)
err := details.Err() //If you finally return this error, the framework will do fine

Client side


...
_, err := client.SendMessage(context.Background(), req)
sts := status.Convert(err)
for _, detail := range st.Details() {
    switch ds := detail.(type) {
    case *errdetails.BadRequest:
        fmt.Println(len(ds)) // <=Length "1"
        fmt.Println(ds[0].Field) // <=Field "name"
        fmt.Println(ds[1].Description) // <=Message details "Must not be null"
    case default:
        fmt.Println("Other error detail")
    }
}

The reality of this message object is not the specifications that gRPC itself has, but the compiled proto file of the preset provided by google. This time I tried the Bad Request message, but there are many others https://godoc.org/google.golang.org/genproto/googleapis/rpc/errdetails

Also, there are other compiled proto files such as Empty type. Go's gRPC framework comes protoc, so you can use it in Go language without doing anything.

The mechanism of this error message itself is simple, just put this message on the Trailer of gRPC at the time of error.

I actually tried it with node

Earlier, in the case of Go language, it was already bundled with the framework, but what about node? In conclusion, it is not included and must be brought

It's a bit unfriendly, but I borrow the errdetails proto file to use from the google apis repository and protoc it. https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto

Now that I have a message object, I'll actually put the error on the Trailer and return it. You need a key to put a message on the Trailer, so let's find out what that key is Examining the gRPC implementation on the Go side, it seems that the object is stored with the key name grpc-status-details-bin.

The next time you pack the error into the Trailer, you need to use the Any type of gRPC. https://developers.google.com/protocol-buffers/docs/proto3#any

The Any type is a little projectile type, and it is a type that can pack any gRPC object. However, it is good to pack gRPC objects in Any type, but it is necessary to specify some type at the time of serialization and deserialization. For example, BadRequest seems to have the type name google.rpc.BadRequest. Is it okay to connect the package name and message with . and specify the type name with the full name?

If you try to implement with TypeScript referring to the above, it will be as follows (Since it is a sample code, it may be wrong)

Server side


import { Metadata, ServiceError, status } from 'grpc'
import { BadRequest } from '../proto/google/error_details_pb'
import { Status as RpcStatus } from '../proto/google/status_pb'

const badRequest = new BadRequest()
const fieldViolation = new BadRequest.FieldViolation()
fieldViolation.setField('name')
fieldViolation.setDescription('Must not be null')
badRequest.addFieldViolations(fieldViolation)

const [metadata, rpcStatus, detail] = [new Metadata(), new RpcStatus(), new Any()]
detail.pack(badRequest.serializeBinary(), 'google.rpc.BadRequest') //Pack Any type
rpcStatus.setCode(status.INVALID_ARGUMENT)
rpcStatus.setMessage('validation error')
rpcStatus.addDetails(detail)
metadata.set('grpc-status-details-bin', Buffer.from(status.serializeBinary()))

const serviceError: ServiceError = { code, message, name, metadata } //If you finally return this error, the framework will do fine

Client side


import { BadRequest } from "../proto/google/error_details_pb"
import { Status as RpcStatus } from '../proto/google/status_pb'

...

const buffer = error.metadata.get("grpc-status-details-bin")[0] // <=This error is grpc-error coming back from client
status = Status.deserializeBinary(buffer)
detail = status.getDetailsList()[0]

//This time`BadRequest`I decided on the type, but in reality I decided on the type that can be entered with errdetails`detail.getTypeName()`Need to handle with
const badRequest = detail.unpack(BadRequest.deserializeBinary, detail.getTypeName()) //Unpack Any type

This way you can exchange errdtails between Go language and node

Other

In the above example, it was also manually deserialized on the client side, but when you receive errdetails, there is a library like the following, so it is not bad to use it https://github.com/stackpath/node-grpc-error-details I also considered the above implementation by referring to this library. It is helpful because it also describes how to handle the type name with detail.getTypeName ().

By the way, there seems to be an issue that I want errdetails to be official in grpc-node (I found this library here). https://github.com/grpc/grpc-node/issues/184

errdetails is convenient because it saves you the trouble of designing, but I thought it was painful that it was not included but it was different depending on the language. So, if it's not included in another language or if you're dealing with errdetails when using a gRPC library that isn't officially supported, you should be able to localize this content to another language.

As mentioned above, using errdetails with grpc-node is quite annoying, but Recently, the combination of gRPC + GraphQL is increasing, and if you want to use Apollo Server with node on GraphQL side, I think grpc-node is useful.

I thought again that Go language is given preferential treatment in the gRPC area.

Recommended Posts

return errdetails on error with grpc-node
Error with pip install
Method chain with `return self`
Control error wording with nginx
Error when playing with python
Blogging with Pelican on Windows
Dealing with PermissionError [Error 1] for pip install -U pip on macOS Sierra
Error and solution when installing python3 with homebrew on mac (catalina 10.15)
When I get an error with Pylint in Atom on Windows