gogrpcgrpc-go

How to use grpc status code and build response accordingly to the client?


I am wondering what is the best way to deal with grpc status code and response when we sent back to the user?

func (us *customerService) FetchResponse(ctx context.Context, request *custPbV1.CustomerRequest) (*custPbV1.CustomerResponse, error) {
    meta := service.MetadataFromContext(ctx)
    clientId := meta.ClientId
    if clientId <= 0 {
        msg := fmt.Sprintf("could not retrieve client info for clientId:%d", clientId)
        // is this right way to return it?
        return nil, status.Error(codes.NotFound, msg)
    }

    resources := request.GetResources()
    if len(resources) == 0 {
        // is this right way to return it?
        err := status.Error(codes.InvalidArgument, "value cannot be null. (Parameter 'resources')")
        return nil, err
    }

    return us.GenerateCustomerInfo(clientId, resources)
}

My proto is very simple -

service CustomerService {
   rpc FetchResponse(CustomerRequest) returns (CustomerResponse) {};
}

message CustomerRequest {
   string resources = 1;
}

message CustomerResponse {
   string proc = 1;
   string data = 2;
}

GenerateCustomerInfo method will return CustomerResponse and error both. But if there is a error then what status code it will be? I am trying to figure out what is the standard basically people follow while returning grpc status code and response back to user. Is it mandatory to return status code back to client or no?

Is it good idea to have error property in response object as well? Any example that demonstrates best practice on how to return grpc status and response back will be useful for me.


Solution

  • This is totally fine to do it like this:

    err := status.Error(codes.InvalidArgument, "value cannot be null. (Parameter 'resources')")
    return nil, err
    

    This will tell your client that there is an error and with that knowledge the client can check the status code and the status message as following:

    resp, err = server.FetchResponse(context.Background(), req)
    s, ok := status.FromError(err)
    
    if ok { // there is an error
       fmt.Printf("Code: %d, Message: %s\n", s.Code(), s.Message())
    }
    

    GenerateCustomerInfo method will return CustomerResponse and error both. But if there is a error then what status code it will be?

    For the GenerateCustomerInfo we don't have much info to reproduce but in my opinion you should just return something like:

    return &custPbV1.CustomerResponse{
        Proc: "YOUR_PROC",
        Data: "YOUR_DATA",
    }, nil
    
    

    by returning nil in the error, you will show the client that there is no error and in the previous code give the ok will be false.

    Is it good idea to have error property in response object as well?

    for that, you definitely could add an error property, but is there any need for that? you can already have a StatusCode and a Message. It would just increase the size of your payload unnecessarily.

    But if GenerateCustomerInfo return any error then what status code should I return back? Do I need to define that too?

    Given an potential implementation of GenerateCustomerInfo like:

    func GenerateCustomerInfo() (*pb.CustomerResponse, error) {
        // some code
        if err {
            st := status.Error(codes.Internal /*or other*/, "a message")
            return nil, err
        }
    
        return &pb.CustomerResponse{
            Proc: "",
            Data: "",
        }, nil
    }
    

    it will basically return either a CustomerResponse or nil which is totally what the parent function need.