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:
parent
d8110b70a3
commit
7105e4099c
@ -12,18 +12,22 @@ 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 {
|
||||
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 first error from details
|
||||
return details[0].(error)
|
||||
}
|
||||
|
||||
// do nothing
|
||||
return err
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
102
errors/errors.pb.go
Normal 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
10
errors/errors.proto
Normal file
@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package errors;
|
||||
|
||||
message Error {
|
||||
string id = 1;
|
||||
int32 code = 2;
|
||||
string detail = 3;
|
||||
string status = 4;
|
||||
};
|
@ -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
|
||||
}
|
||||
return status.New(statusCode, statusDesc).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()
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user