0

I would like to use the google.golang.org/grpc/status error model for my REST APIs as it says you can:

The Status type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs.

But I am running into a problem with the details part of the struct. I understand it is of type []*anypb.Any, however, I am unclear how to get it into an "unpacked" form so I can see the Field and Description attributes rather than a base64 encoded value field.

What I am getting:

{
    "code": 3,
    "message": "One or more fields are invalid",
    "details": [
        {
            "type_url": "type.googleapis.com/google.rpc.BadRequest.FieldViolation",
            "value": "CgVFbWFpbBIUSW52YWxpZCBlbWFpbCBmb3JtYXQ="
        },
        {
            "type_url": "type.googleapis.com/google.rpc.BadRequest.FieldViolation",
            "value": "CghQYXNzd29yZBIeTXVzdCBiZSBhdCBsZWFzdCAxMCBjaGFyYWN0ZXJz"
        }
    ]
}

What I should be getting:

{
    "code": 3,
    "message": "One or more fields are invalid",
    "details": [
        {
            "type_url": "type.googleapis.com/google.rpc.BadRequest.FieldViolation",
            "field": "Email",
            "description": "Invalid email format"
        },
        {
            "type_url": "type.googleapis.com/google.rpc.BadRequest.FieldViolation",
            "field": "Password",
            "description": "Must be at least 10 characters"
        }
    ]
}

Example code:

package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/go-chi/chi"
    "github.com/go-chi/chi/middleware"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)


func main ()  {

    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {

        err := getError()
        st, _ := status.FromError(err)

        p:= st.Proto()

        w.Header().Set("content-type","application/json")
        err = json.NewEncoder(w).Encode(p)

        if err !=nil {
            fmt.Println("Error encoding", err)
        }

    })

    http.ListenAndServe(":3000", r)
}


func getError() error {

    st := status.New(codes.InvalidArgument, "One or more fields are invalid")

    f1 := &errdetails.BadRequest_FieldViolation{
        Field:                "Email",
        Description:          "Invalid email format",
    }

    f2 := &errdetails.BadRequest_FieldViolation{
        Field:                "Password",
        Description:          "Must be at least 10 characters",
    }

    st, _ = st.WithDetails(f1)
    st, _ = st.WithDetails(f2)

    return st.Err()
}


user3137124
  • 515
  • 1
  • 7
  • 13

1 Answers1

4

The json Encoder ist not 100% compatible with protobuf.

Use protojson.Marshal from "google.golang.org/protobuf/encoding/protojson" instead.

See this Playground.

It is not as fast though.

EDIT To answer the request for a faster alternative:

One could utilise a custom error struct that holds all required data & unwrap the grpc status & its details manually. See this playground. On my machine this saved about 15% time.

thlcodes
  • 106
  • 3