Skip to content

Commit

Permalink
feat: add ExtractProtoMessage to apierror (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
shollyman committed Aug 4, 2022
1 parent 971b9b1 commit a6ce70c
Show file tree
Hide file tree
Showing 4 changed files with 399 additions and 0 deletions.
25 changes: 25 additions & 0 deletions v2/apierror/apierror.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

// ErrDetails holds the google/rpc/error_details.proto messages.
Expand All @@ -60,6 +61,30 @@ type ErrDetails struct {
Unknown []interface{}
}

// ErrMessageNotFound is used to signal ExtractProtoMessage found no matching messages.
var ErrMessageNotFound = errors.New("message not found")

// ExtractProtoMessage provides a mechanism for extracting protobuf messages from the
// Unknown error details. If ExtractProtoMessage finds an unknown message of the same type,
// the content of the message is copied to the provided message.
//
// ExtractProtoMessage will return ErrMessageNotFound if there are no message matching the
// protocol buffer type of the provided message.
func (e ErrDetails) ExtractProtoMessage(v proto.Message) error {
if v == nil {
return ErrMessageNotFound
}
for _, elem := range e.Unknown {
if elemProto, ok := elem.(proto.Message); ok {
if v.ProtoReflect().Type() == elemProto.ProtoReflect().Type() {
proto.Merge(v, elemProto)
return nil
}
}
}
return ErrMessageNotFound
}

func (e ErrDetails) String() string {
var d strings.Builder
if e.ErrorInfo != nil {
Expand Down
68 changes: 68 additions & 0 deletions v2/apierror/apierror_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package apierror

import (
"context"
"errors"
"flag"
"io/ioutil"
"path/filepath"
Expand All @@ -45,6 +46,7 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
Expand All @@ -68,6 +70,72 @@ func TestDetails(t *testing.T) {
t.Errorf("got(-), want(+):\n%s", diff)
}
}

func TestDetails_ExtractProtoMessage(t *testing.T) {

customError := &jsonerror.CustomError{
Code: jsonerror.CustomError_UNIVERSE_WAS_DESTROYED,
Entity: "some entity",
ErrorMessage: "custom error message",
}

testCases := []struct {
description string
src *status.Status
extract proto.Message
want interface{}
wantErr error
}{
{
description: "no details",
src: status.New(codes.Unimplemented, "unimp"),
extract: &jsonerror.CustomError{},
wantErr: ErrMessageNotFound,
},
{
description: "nil argument",
src: func() *status.Status {
s, _ := status.New(codes.Unauthenticated, "who are you").WithDetails(
&descriptorpb.DescriptorProto{},
)
return s
}(),
wantErr: ErrMessageNotFound,
},
{
description: "custom error success",
src: func() *status.Status {
s, _ := status.New(codes.Unknown, "unknown error").WithDetails(
customError,
)
return s
}(),
extract: &jsonerror.CustomError{},
want: customError,
},
}
for _, tc := range testCases {

apiErr, ok := FromError(tc.src.Err())
if !ok {
t.Errorf("%s: FromError failure", tc.description)
}
val := tc.extract
gotErr := apiErr.Details().ExtractProtoMessage(val)
if tc.wantErr != nil {
if !errors.Is(gotErr, tc.wantErr) {
t.Errorf("%s: got error %v, wanted error %v", tc.description, gotErr, tc.wantErr)
}
} else {
if gotErr != nil {
t.Errorf("%s: got error %v", tc.description, gotErr)
}
if diff := cmp.Diff(val, tc.want, protocmp.Transform()); diff != "" {
t.Errorf("%s: got(-), want(+):\n%s", tc.description, diff)
}
}
}
}
func TestUnwrap(t *testing.T) {
pf := &errdetails.PreconditionFailure{
Violations: []*errdetails.PreconditionFailure_Violation{{Type: "Foo", Subject: "Bar", Description: "desc"}},
Expand Down
256 changes: 256 additions & 0 deletions v2/apierror/internal/proto/custom_error.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a6ce70c

Please sign in to comment.