Skip to content

Commit

Permalink
runtime/errors: handle error details
Browse files Browse the repository at this point in the history
Fixes grpc-ecosystem#405.

If we go with this, that is.

In a separate commit, I'll update the generated files.

Signed-off-by: Stephan Renatus <[email protected]>
  • Loading branch information
srenatus committed Jan 3, 2018
1 parent 61c34cc commit 107513b
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 7 deletions.
5 changes: 5 additions & 0 deletions examples/examplepb/a_bit_of_everything.proto
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ service ABitOfEverythingService {
get: "/v2/example/timeout",
};
}
rpc ErrorWithDetails(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
get: "/v2/example/errorwithdetails",
};
}
rpc GetMessageWithBody(MessageWithBody) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/v2/example/withbody/{id}",
Expand Down
60 changes: 57 additions & 3 deletions examples/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import (
)

type errorBody struct {
Error string `json:"error"`
Code int `json:"code"`
Error string `json:"error"`
Code int `json:"code"`
Details []interface{} `json:"details"`
}

func TestEcho(t *testing.T) {
Expand Down Expand Up @@ -460,7 +461,7 @@ func testABELookupNotFound(t *testing.T, port int) {

var msg errorBody
if err := json.Unmarshal(buf, &msg); err != nil {
t.Errorf("jsonpb.UnmarshalString(%s, &msg) failed with %v; want success", buf, err)
t.Errorf("json.Unmarshal(%s, &msg) failed with %v; want success", buf, err)
return
}

Expand Down Expand Up @@ -723,6 +724,59 @@ func TestTimeout(t *testing.T) {
}
}

func TestErrorWithDetails(t *testing.T) {
url := "http://localhost:8080/v2/example/errorwithdetails"
resp, err := http.Get(url)
if err != nil {
t.Errorf("http.Get(%q) failed with %v; want success", url, err)
return
}
defer resp.Body.Close()

buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("iotuil.ReadAll(resp.Body) failed with %v; want success", err)
}

if got, want := resp.StatusCode, http.StatusInternalServerError; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
}

var msg errorBody
if err := json.Unmarshal(buf, &msg); err != nil {
t.Fatalf("json.Unmarshal(%s, &msg) failed with %v; want success", buf, err)
}

if got, want := msg.Code, int(codes.Unknown); got != want {
t.Errorf("msg.Code = %d; want %d", got, want)
}
if got, want := msg.Error, "with details"; got != want {
t.Errorf("msg.Error = %s; want %s", got, want)
}
if got, want := len(msg.Details), 1; got != want {
t.Fatalf("len(msg.Details) = %q; want %q", got, want)
}

details, ok := msg.Details[0].(map[string]interface{})
if got, want := ok, true; got != want {
t.Fatalf("msg.Details[0] got type: %T, want %T", msg.Details[0], map[string]interface{}{})
}
if got, want := details["detail"], "error debug details"; got != want {
t.Errorf("msg.Details[\"detail\"] = %q; want %q", got, want)
}
entries, ok := details["stack_entries"].([]interface{})
if got, want := ok, true; got != want {
t.Fatalf("msg.Details[0][\"stack_entries\"] got type: %T, want %T", entries, []string{})
}
entry, ok := entries[0].(string)
if got, want := ok, true; got != want {
t.Fatalf("msg.Details[0][\"stack_entries\"][0] got type: %T, want %T", entry, "")
}
if got, want := entries[0], "foo:1"; got != want {
t.Errorf("msg.Details[\"stack_entries\"][0] = %q; want %q", got, want)
}
}

func TestUnknownPath(t *testing.T) {
url := "http://localhost:8080"
resp, err := http.Post(url, "application/json", strings.NewReader("{}"))
Expand Down
18 changes: 17 additions & 1 deletion examples/server/a_bit_of_everything.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import (
"sync"

"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/duration"
"github.com/golang/protobuf/ptypes/empty"
examples "github.com/grpc-ecosystem/grpc-gateway/examples/examplepb"
sub "github.com/grpc-ecosystem/grpc-gateway/examples/sub"
sub2 "github.com/grpc-ecosystem/grpc-gateway/examples/sub2"
"github.com/rogpeppe/fastuuid"
"golang.org/x/net/context"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"

)

// Implements of ABitOfEverythingServiceServer
Expand Down Expand Up @@ -248,6 +249,21 @@ func (s *_ABitOfEverythingServer) Timeout(ctx context.Context, msg *empty.Empty)
}
}

func (s *_ABitOfEverythingServer) ErrorWithDetails(ctx context.Context, msg *empty.Empty) (*empty.Empty, error) {
stat := status.New(codes.Unknown, "with details")
details := []proto.Message{
&errdetails.DebugInfo{
StackEntries: []string{"foo:1"},
Detail: "error debug details",
},
}
stat, err := stat.WithDetails(details...)
if err != nil {
return nil, status.Errorf(codes.Internal, "unexpected error adding details: %s", err)
}
return nil, stat.Err()
}

func (s *_ABitOfEverythingServer) GetMessageWithBody(ctx context.Context, msg *examples.MessageWithBody) (*empty.Empty, error) {
return &empty.Empty{}, nil
}
13 changes: 10 additions & 3 deletions runtime/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ var (
)

type errorBody struct {
Error string `protobuf:"bytes,1,name=error" json:"error"`
Code int32 `protobuf:"varint,2,name=code" json:"code"`
Error string `protobuf:"bytes,1,name=error" json:"error"`
Code int32 `protobuf:"varint,2,name=code" json:"code"`
Details []proto.Message `protobuf:"bytes,3,name=details" json:"details"`
}

//Make this also conform to proto.Message for builtin JSONPb Marshaler
// Make this also conform to proto.Message for builtin JSONPb Marshaler
func (e *errorBody) Reset() { *e = errorBody{} }
func (e *errorBody) String() string { return proto.CompactTextString(e) }
func (*errorBody) ProtoMessage() {}
Expand All @@ -94,6 +95,12 @@ func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w
Code: int32(s.Code()),
}

for _, detail := range s.Details() {
if det, ok := detail.(proto.Message); ok {
body.Details = append(body.Details, det)
}
}

buf, merr := marshaler.Marshal(body)
if merr != nil {
grpclog.Printf("Failed to marshal error message %q: %v", body, merr)
Expand Down

0 comments on commit 107513b

Please sign in to comment.