Skip to content

Commit

Permalink
Allow statuses from compatible errors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
johanbrandhorst committed Mar 18, 2018
1 parent fa28bef commit 6b7bae6
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
46 changes: 43 additions & 3 deletions status/status.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -119,16 +120,55 @@ 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
}
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
}

Expand Down
65 changes: 65 additions & 0 deletions status/status_test.go
Expand Up @@ -19,6 +19,7 @@
package status

import (
"bytes"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -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 <Code()=%s, Message()=%q, Err()!=nil>, 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 <Details()=%s>", pd, details)
}
}

func TestFromErrorUnknownError(t *testing.T) {
code, message := codes.Unknown, "unknown error"
err := errors.New("unknown error")
Expand Down

0 comments on commit 6b7bae6

Please sign in to comment.