mirror of https://github.com/grpc/grpc-go.git
				
				
				
			status: Add WithDetails and Details functions (#1358)
This commit is contained in:
		
							parent
							
								
									6495e8dfeb
								
							
						
					
					
						commit
						cdc12d4a3c
					
				|  | @ -28,9 +28,11 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/golang/protobuf/proto" | ||||
| 	"github.com/golang/protobuf/ptypes" | ||||
| 	spb "google.golang.org/genproto/googleapis/rpc/status" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| ) | ||||
|  | @ -128,3 +130,39 @@ func FromError(err error) (s *Status, ok bool) { | |||
| 	} | ||||
| 	return nil, false | ||||
| } | ||||
| 
 | ||||
| // WithDetails returns a new status with the provided details messages appended to the status.
 | ||||
| // If any errors are encountered, it returns nil and the first error encountered.
 | ||||
| func (s *Status) WithDetails(details ...proto.Message) (*Status, error) { | ||||
| 	if s.Code() == codes.OK { | ||||
| 		return nil, errors.New("no error details for status with code OK") | ||||
| 	} | ||||
| 	// s.Code() != OK implies that s.Proto() != nil.
 | ||||
| 	p := s.Proto() | ||||
| 	for _, detail := range details { | ||||
| 		any, err := ptypes.MarshalAny(detail) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p.Details = append(p.Details, any) | ||||
| 	} | ||||
| 	return &Status{s: p}, nil | ||||
| } | ||||
| 
 | ||||
| // Details returns a slice of details messages attached to the status.
 | ||||
| // If a detail cannot be decoded, the error is returned in place of the detail.
 | ||||
| func (s *Status) Details() []interface{} { | ||||
| 	if s == nil || s.s == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	details := make([]interface{}, 0, len(s.s.Details)) | ||||
| 	for _, any := range s.s.Details { | ||||
| 		detail := &ptypes.DynamicAny{} | ||||
| 		if err := ptypes.UnmarshalAny(any, detail); err != nil { | ||||
| 			details = append(details, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		details = append(details, detail.Message) | ||||
| 	} | ||||
| 	return details | ||||
| } | ||||
|  |  | |||
|  | @ -19,11 +19,17 @@ | |||
| package status | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/golang/protobuf/proto" | ||||
| 	"github.com/golang/protobuf/ptypes" | ||||
| 	apb "github.com/golang/protobuf/ptypes/any" | ||||
| 	dpb "github.com/golang/protobuf/ptypes/duration" | ||||
| 	cpb "google.golang.org/genproto/googleapis/rpc/code" | ||||
| 	epb "google.golang.org/genproto/googleapis/rpc/errdetails" | ||||
| 	spb "google.golang.org/genproto/googleapis/rpc/status" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| ) | ||||
|  | @ -112,3 +118,144 @@ func TestFromErrorOK(t *testing.T) { | |||
| 		t.Fatalf("FromError(nil) = %v, %v; want <Code()=%s, Message()=%q, Err=nil>, true", s, ok, code, message) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestStatus_ErrorDetails(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		code    codes.Code | ||||
| 		details []proto.Message | ||||
| 	}{ | ||||
| 		{ | ||||
| 			code:    codes.NotFound, | ||||
| 			details: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			code: codes.NotFound, | ||||
| 			details: []proto.Message{ | ||||
| 				&epb.ResourceInfo{ | ||||
| 					ResourceType: "book", | ||||
| 					ResourceName: "projects/1234/books/5678", | ||||
| 					Owner:        "User", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			code: codes.Internal, | ||||
| 			details: []proto.Message{ | ||||
| 				&epb.DebugInfo{ | ||||
| 					StackEntries: []string{ | ||||
| 						"first stack", | ||||
| 						"second stack", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			code: codes.Unavailable, | ||||
| 			details: []proto.Message{ | ||||
| 				&epb.RetryInfo{ | ||||
| 					RetryDelay: &dpb.Duration{Seconds: 60}, | ||||
| 				}, | ||||
| 				&epb.ResourceInfo{ | ||||
| 					ResourceType: "book", | ||||
| 					ResourceName: "projects/1234/books/5678", | ||||
| 					Owner:        "User", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range tests { | ||||
| 		s, err := New(tc.code, "").WithDetails(tc.details...) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("(%v).WithDetails(%+v) failed: %v", str(s), tc.details, err) | ||||
| 		} | ||||
| 		details := s.Details() | ||||
| 		for i := range details { | ||||
| 			if !proto.Equal(details[i].(proto.Message), tc.details[i]) { | ||||
| 				t.Fatalf("(%v).Details()[%d] = %+v, want %+v", str(s), i, details[i], tc.details[i]) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestStatus_WithDetails_Fail(t *testing.T) { | ||||
| 	tests := []*Status{ | ||||
| 		nil, | ||||
| 		FromProto(nil), | ||||
| 		New(codes.OK, ""), | ||||
| 	} | ||||
| 	for _, s := range tests { | ||||
| 		if s, err := s.WithDetails(); err == nil || s != nil { | ||||
| 			t.Fatalf("(%v).WithDetails(%+v) = %v, %v; want nil, non-nil", str(s), []proto.Message{}, s, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestStatus_ErrorDetails_Fail(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		s *Status | ||||
| 		i []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			nil, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			FromProto(nil), | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			New(codes.OK, ""), | ||||
| 			[]interface{}{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			FromProto(&spb.Status{ | ||||
| 				Code: int32(cpb.Code_CANCELLED), | ||||
| 				Details: []*apb.Any{ | ||||
| 					{ | ||||
| 						TypeUrl: "", | ||||
| 						Value:   []byte{}, | ||||
| 					}, | ||||
| 					mustMarshalAny(&epb.ResourceInfo{ | ||||
| 						ResourceType: "book", | ||||
| 						ResourceName: "projects/1234/books/5678", | ||||
| 						Owner:        "User", | ||||
| 					}), | ||||
| 				}, | ||||
| 			}), | ||||
| 			[]interface{}{ | ||||
| 				errors.New(`message type url "" is invalid`), | ||||
| 				&epb.ResourceInfo{ | ||||
| 					ResourceType: "book", | ||||
| 					ResourceName: "projects/1234/books/5678", | ||||
| 					Owner:        "User", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range tests { | ||||
| 		got := tc.s.Details() | ||||
| 		if !reflect.DeepEqual(got, tc.i) { | ||||
| 			t.Errorf("(%v).Details() = %+v, want %+v", str(tc.s), got, tc.i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func str(s *Status) string { | ||||
| 	if s == nil { | ||||
| 		return "nil" | ||||
| 	} | ||||
| 	if s.s == nil { | ||||
| 		return "<Code=OK>" | ||||
| 	} | ||||
| 	return fmt.Sprintf("<Code=%v, Message=%q, Details=%+v>", codes.Code(s.s.GetCode()), s.s.GetMessage(), s.s.GetDetails()) | ||||
| } | ||||
| 
 | ||||
| // mustMarshalAny converts a protobuf message to an any.
 | ||||
| func mustMarshalAny(msg proto.Message) *apb.Any { | ||||
| 	any, err := ptypes.MarshalAny(msg) | ||||
| 	if err != nil { | ||||
| 		panic(fmt.Sprintf("ptypes.MarshalAny(%+v) failed: %v", msg, err)) | ||||
| 	} | ||||
| 	return any | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue