pass micro errors from grpc server to grpc client (#1167)

* pass micro errors from grpc server to grpc client

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>

* wrap micro errors.Error to grpc status

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2020-02-06 13:18:33 +03:00 committed by GitHub
parent d8110b70a3
commit 7105e4099c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 29 deletions

View File

@ -12,17 +12,21 @@ func microError(err error) error {
return nil
}
// micro error
if v, ok := err.(*errors.Error); ok {
return v
if verr, ok := err.(*errors.Error); ok {
return verr
}
// grpc error
if s, ok := status.FromError(err); ok {
if e := errors.Parse(s.Message()); e.Code > 0 {
return e // actually a micro error
details := s.Details()
if len(details) == 0 {
if e := errors.Parse(s.Message()); e.Code > 0 {
return e // actually a micro error
}
return errors.InternalServerError("go.micro.client", s.Message())
}
return errors.InternalServerError("go.micro.client", s.Message())
// return first error from details
return details[0].(error)
}
// do nothing

View File

@ -7,6 +7,7 @@ import (
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/client/selector"
"github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v2/registry/memory"
pgrpc "google.golang.org/grpc"
@ -18,6 +19,9 @@ type greeterServer struct{}
// SayHello implements helloworld.GreeterServer
func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
if in.Name == "Error" {
return nil, &errors.Error{Id: "1", Code: 99, Detail: "detail"}
}
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
@ -84,4 +88,25 @@ func TestGRPCClient(t *testing.T) {
t.Fatalf("Got unexpected response %v", rsp.Message)
}
}
req := c.NewRequest("helloworld", "/helloworld.Greeter/SayHello", &pb.HelloRequest{
Name: "Error",
})
rsp := pb.HelloReply{}
err = c.Call(context.TODO(), req, &rsp)
if err == nil {
t.Fatal("nil error received")
}
verr, ok := err.(*errors.Error)
if !ok {
t.Fatalf("invalid error received %#+v\n", err)
}
if verr.Code != 99 && verr.Id != "1" && verr.Detail != "detail" {
t.Fatalf("invalid error received %#+v\n", verr)
}
}

View File

@ -8,13 +8,7 @@ import (
"net/http"
)
// Error implements the error interface.
type Error struct {
Id string `json:"id"`
Code int32 `json:"code"`
Detail string `json:"detail"`
Status string `json:"status"`
}
//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto
func (e *Error) Error() string {
b, _ := json.Marshal(e)

102
errors/errors.pb.go Normal file
View File

@ -0,0 +1,102 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: errors.proto
package errors
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Error struct {
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Code int32 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"`
Detail string `protobuf:"bytes,3,opt,name=detail,proto3" json:"detail,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Error) Reset() { *m = Error{} }
func (m *Error) String() string { return proto.CompactTextString(m) }
func (*Error) ProtoMessage() {}
func (*Error) Descriptor() ([]byte, []int) {
return fileDescriptor_24fe73c7f0ddb19c, []int{0}
}
func (m *Error) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Error.Unmarshal(m, b)
}
func (m *Error) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Error.Marshal(b, m, deterministic)
}
func (m *Error) XXX_Merge(src proto.Message) {
xxx_messageInfo_Error.Merge(m, src)
}
func (m *Error) XXX_Size() int {
return xxx_messageInfo_Error.Size(m)
}
func (m *Error) XXX_DiscardUnknown() {
xxx_messageInfo_Error.DiscardUnknown(m)
}
var xxx_messageInfo_Error proto.InternalMessageInfo
func (m *Error) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func (m *Error) GetCode() int32 {
if m != nil {
return m.Code
}
return 0
}
func (m *Error) GetDetail() string {
if m != nil {
return m.Detail
}
return ""
}
func (m *Error) GetStatus() string {
if m != nil {
return m.Status
}
return ""
}
func init() {
proto.RegisterType((*Error)(nil), "errors.Error")
}
func init() { proto.RegisterFile("errors.proto", fileDescriptor_24fe73c7f0ddb19c) }
var fileDescriptor_24fe73c7f0ddb19c = []byte{
// 116 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x83, 0xf0, 0x94, 0xa2, 0xb9, 0x58,
0x5d, 0x41, 0x2c, 0x21, 0x3e, 0x2e, 0xa6, 0xcc, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20,
0xa6, 0xcc, 0x14, 0x21, 0x21, 0x2e, 0x96, 0xe4, 0xfc, 0x94, 0x54, 0x09, 0x26, 0x05, 0x46, 0x0d,
0xd6, 0x20, 0x30, 0x5b, 0x48, 0x8c, 0x8b, 0x2d, 0x25, 0xb5, 0x24, 0x31, 0x33, 0x47, 0x82, 0x19,
0xac, 0x0e, 0xca, 0x03, 0x89, 0x17, 0x97, 0x24, 0x96, 0x94, 0x16, 0x4b, 0xb0, 0x40, 0xc4, 0x21,
0xbc, 0x24, 0x36, 0xb0, 0x5d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xef, 0xe7, 0x5d, 0xd3,
0x7b, 0x00, 0x00, 0x00,
}

10
errors/errors.proto Normal file
View File

@ -0,0 +1,10 @@
syntax = "proto3";
package errors;
message Error {
string id = 1;
int32 code = 2;
string detail = 3;
string status = 4;
};

View File

@ -14,6 +14,7 @@ import (
"sync"
"time"
"github.com/golang/protobuf/proto"
"github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/codec"
"github.com/micro/go-micro/v2/errors"
@ -375,20 +376,38 @@ func (g *grpcServer) processRequest(stream grpc.ServerStream, service *service,
statusCode := codes.OK
statusDesc := ""
// execute the handler
if appErr := fn(ctx, r, replyv.Interface()); appErr != nil {
if err, ok := appErr.(*rpcError); ok {
statusCode = err.code
statusDesc = err.desc
} else if err, ok := appErr.(*errors.Error); ok {
statusCode = microError(err)
statusDesc = appErr.Error()
} else {
var errStatus *status.Status
switch verr := appErr.(type) {
case *errors.Error:
// micro.Error now proto based and we can attach it to grpc status
statusCode = microError(verr)
statusDesc = verr.Error()
errStatus, err = status.New(statusCode, statusDesc).WithDetails(verr)
if err != nil {
return err
}
case proto.Message:
// user defined error that proto based we can attach it to grpc status
statusCode = convertCode(appErr)
statusDesc = appErr.Error()
errStatus, err = status.New(statusCode, statusDesc).WithDetails(verr)
if err != nil {
return err
}
case *rpcError:
// rpcError handling may be we have ability to attach it to details?
statusCode = verr.code
statusDesc = verr.desc
errStatus = status.New(statusCode, statusDesc)
default:
// default case user pass own error type that not proto based
statusCode = convertCode(verr)
statusDesc = verr.Error()
errStatus = status.New(statusCode, statusDesc)
}
return status.New(statusCode, statusDesc).Err()
return errStatus.Err()
}
if err := stream.SendMsg(replyv.Interface()); err != nil {
@ -436,16 +455,37 @@ func (g *grpcServer) processStream(stream grpc.ServerStream, service *service, m
appErr := fn(ctx, r, ss)
if appErr != nil {
if err, ok := appErr.(*rpcError); ok {
statusCode = err.code
statusDesc = err.desc
} else if err, ok := appErr.(*errors.Error); ok {
statusCode = microError(err)
statusDesc = appErr.Error()
} else {
var err error
var errStatus *status.Status
switch verr := appErr.(type) {
case *errors.Error:
// micro.Error now proto based and we can attach it to grpc status
statusCode = microError(verr)
statusDesc = verr.Error()
errStatus, err = status.New(statusCode, statusDesc).WithDetails(verr)
if err != nil {
return err
}
case proto.Message:
// user defined error that proto based we can attach it to grpc status
statusCode = convertCode(appErr)
statusDesc = appErr.Error()
errStatus, err = status.New(statusCode, statusDesc).WithDetails(verr)
if err != nil {
return err
}
case *rpcError:
// rpcError handling may be we have ability to attach it to details?
statusCode = verr.code
statusDesc = verr.desc
errStatus = status.New(statusCode, statusDesc)
default:
// default case user pass own error type that not proto based
statusCode = convertCode(verr)
statusDesc = verr.Error()
errStatus = status.New(statusCode, statusDesc)
}
return errStatus.Err()
}
return status.New(statusCode, statusDesc).Err()

View File

@ -4,9 +4,11 @@ import (
"context"
"testing"
"github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/registry/memory"
"github.com/micro/go-micro/v2/server"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
pb "github.com/micro/go-micro/v2/server/grpc/proto"
)
@ -16,6 +18,10 @@ type testServer struct{}
// TestHello implements helloworld.GreeterServer
func (s *testServer) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
if req.Name == "Error" {
return &errors.Error{Id: "1", Code: 99, Detail: "detail"}
}
rsp.Msg = "Hello " + req.Name
return nil
}
@ -63,4 +69,21 @@ func TestGRPCServer(t *testing.T) {
t.Fatalf("Got unexpected response %v", rsp.Msg)
}
}
// Test grpc error
rsp := pb.Response{}
if err := cc.Invoke(context.Background(), "/test.Test/Call", &pb.Request{Name: "Error"}, &rsp); err != nil {
st, ok := status.FromError(err)
if !ok {
t.Fatalf("invalid error received %#+v\n", err)
}
verr, ok := st.Details()[0].(*errors.Error)
if !ok {
t.Fatalf("invalid error received %#+v\n", st.Details()[0])
}
if verr.Code != 99 && verr.Id != "1" && verr.Detail != "detail" {
t.Fatalf("invalid error received %#+v\n", verr)
}
}
}