From 6b7bae615707ecb725e05bf8d1557bf0dcbff60f Mon Sep 17 00:00:00 2001 From: Johan Brandhorst Date: Sun, 18 Mar 2018 14:24:49 +0000 Subject: [PATCH] Allow statuses from compatible errors Embues the status package with the ability to create statuses from generic errors that implement a special interface. This was designed with the github.com/gogo/protobuf project in mind, but is implemented in a fashion that makes it accessible to arbitrary payloads. Fixes #1885. --- status/status.go | 46 ++++++++++++++++++++++++++++-- status/status_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/status/status.go b/status/status.go index 3a42dc6de027..439fded5774b 100644 --- a/status/status.go +++ b/status/status.go @@ -33,6 +33,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" + "github.com/golang/protobuf/ptypes/any" spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" ) @@ -119,9 +120,28 @@ func FromProto(s *spb.Status) *Status { return &Status{s: proto.Clone(s).(*spb.Status)} } -// FromError returns a Status representing err if it was produced from this -// package. Otherwise, ok is false and a Status is returned with codes.Unknown -// and the original error message. +/* +FromError returns a Status representing err. +It converts an error to a status as follows: + + 1. If the error is produced by this package, it will + extract the status from the error. + + 2. If the error implements the following interface, it will + construct a status out of the information exposed: + + type StatusError interface { + GetMessage() string + GetCode() codes.Code + GetDetails() []interface { + GetTypeURL() string + GetValue() []byte + } + } + + 3. Otherwise, ok is false and a Status is returned with codes.Unknown + and the original error message. +*/ func FromError(err error) (s *Status, ok bool) { if err == nil { return &Status{s: &spb.Status{Code: int32(codes.OK)}}, true @@ -129,6 +149,26 @@ func FromError(err error) (s *Status, ok bool) { if se, ok := err.(*statusError); ok { return se.status(), true } + if e, ok := err.(interface { + GetMessage() string + GetCode() codes.Code + GetDetails() []interface { + GetTypeURL() string + GetValue() []byte + } + }); ok { + sp := &spb.Status{ + Code: int32(e.GetCode()), + Message: e.GetMessage(), + } + for _, detail := range e.GetDetails() { + sp.Details = append(sp.Details, &any.Any{ + TypeUrl: detail.GetTypeURL(), + Value: detail.GetValue(), + }) + } + return FromProto(sp), true + } return New(codes.Unknown, err.Error()), false } diff --git a/status/status_test.go b/status/status_test.go index 8b74c27d6e2a..35f9c8ad51a6 100644 --- a/status/status_test.go +++ b/status/status_test.go @@ -19,6 +19,7 @@ package status import ( + "bytes" "errors" "fmt" "reflect" @@ -119,6 +120,70 @@ func TestFromErrorOK(t *testing.T) { } } +type customError struct { + Code codes.Code + Message string + Details []interface { + GetTypeURL() string + GetValue() []byte + } +} + +func (c customError) Error() string { + return fmt.Sprintf("rpc error: code = %s desc = %s", c.GetCode(), c.GetMessage()) +} + +func (c customError) GetCode() codes.Code { + return c.Code +} + +func (c customError) GetMessage() string { + return c.Message +} + +func (c customError) GetDetails() []interface { + GetTypeURL() string + GetValue() []byte +} { + return c.Details +} + +type detail struct { + TypeURL string + Value []byte +} + +func (d detail) GetTypeURL() string { + return d.TypeURL +} + +func (d detail) GetValue() []byte { + return d.Value +} + +func TestFromErrorImplementsInterface(t *testing.T) { + code, message := codes.Internal, "test description" + details := []interface { + GetTypeURL() string + GetValue() []byte + }{ + detail{TypeURL: "testUrl", Value: []byte("testValue")}, + } + err := customError{ + Code: code, + Message: message, + Details: details, + } + s, ok := FromError(err) + if !ok || s.Code() != code || s.Message() != message || s.Err() == nil { + t.Fatalf("FromError(%v) = %v, %v; want , true", err, s, ok, code, message) + } + pd := s.Proto().GetDetails() + if len(pd) != 1 || !bytes.Equal(pd[0].GetValue(), details[0].GetValue()) || pd[0].GetTypeUrl() != details[0].GetTypeURL() { + t.Fatalf("s.Proto.GetDetails() = %v; want ", pd, details) + } +} + func TestFromErrorUnknownError(t *testing.T) { code, message := codes.Unknown, "unknown error" err := errors.New("unknown error")