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,17 +12,21 @@ func microError(err error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// micro error
|
if verr, ok := err.(*errors.Error); ok {
|
||||||
if v, ok := err.(*errors.Error); ok {
|
return verr
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// grpc error
|
// grpc error
|
||||||
if s, ok := status.FromError(err); ok {
|
if s, ok := status.FromError(err); ok {
|
||||||
if e := errors.Parse(s.Message()); e.Code > 0 {
|
details := s.Details()
|
||||||
return e // actually a micro error
|
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
|
// do nothing
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/micro/go-micro/v2/client"
|
"github.com/micro/go-micro/v2/client"
|
||||||
"github.com/micro/go-micro/v2/client/selector"
|
"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"
|
||||||
"github.com/micro/go-micro/v2/registry/memory"
|
"github.com/micro/go-micro/v2/registry/memory"
|
||||||
pgrpc "google.golang.org/grpc"
|
pgrpc "google.golang.org/grpc"
|
||||||
@ -18,6 +19,9 @@ type greeterServer struct{}
|
|||||||
|
|
||||||
// SayHello implements helloworld.GreeterServer
|
// SayHello implements helloworld.GreeterServer
|
||||||
func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
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
|
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)
|
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"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error implements the error interface.
|
//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto
|
||||||
type Error struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Code int32 `json:"code"`
|
|
||||||
Detail string `json:"detail"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
b, _ := json.Marshal(e)
|
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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/micro/go-micro/v2/broker"
|
"github.com/micro/go-micro/v2/broker"
|
||||||
"github.com/micro/go-micro/v2/codec"
|
"github.com/micro/go-micro/v2/codec"
|
||||||
"github.com/micro/go-micro/v2/errors"
|
"github.com/micro/go-micro/v2/errors"
|
||||||
@ -375,20 +376,38 @@ func (g *grpcServer) processRequest(stream grpc.ServerStream, service *service,
|
|||||||
|
|
||||||
statusCode := codes.OK
|
statusCode := codes.OK
|
||||||
statusDesc := ""
|
statusDesc := ""
|
||||||
|
|
||||||
// execute the handler
|
// execute the handler
|
||||||
if appErr := fn(ctx, r, replyv.Interface()); appErr != nil {
|
if appErr := fn(ctx, r, replyv.Interface()); appErr != nil {
|
||||||
if err, ok := appErr.(*rpcError); ok {
|
var errStatus *status.Status
|
||||||
statusCode = err.code
|
switch verr := appErr.(type) {
|
||||||
statusDesc = err.desc
|
case *errors.Error:
|
||||||
} else if err, ok := appErr.(*errors.Error); ok {
|
// micro.Error now proto based and we can attach it to grpc status
|
||||||
statusCode = microError(err)
|
statusCode = microError(verr)
|
||||||
statusDesc = appErr.Error()
|
statusDesc = verr.Error()
|
||||||
} else {
|
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)
|
statusCode = convertCode(appErr)
|
||||||
statusDesc = appErr.Error()
|
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 {
|
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)
|
appErr := fn(ctx, r, ss)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
if err, ok := appErr.(*rpcError); ok {
|
var err error
|
||||||
statusCode = err.code
|
var errStatus *status.Status
|
||||||
statusDesc = err.desc
|
switch verr := appErr.(type) {
|
||||||
} else if err, ok := appErr.(*errors.Error); ok {
|
case *errors.Error:
|
||||||
statusCode = microError(err)
|
// micro.Error now proto based and we can attach it to grpc status
|
||||||
statusDesc = appErr.Error()
|
statusCode = microError(verr)
|
||||||
} else {
|
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)
|
statusCode = convertCode(appErr)
|
||||||
statusDesc = appErr.Error()
|
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()
|
return status.New(statusCode, statusDesc).Err()
|
||||||
|
@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/micro/go-micro/v2/errors"
|
||||||
"github.com/micro/go-micro/v2/registry/memory"
|
"github.com/micro/go-micro/v2/registry/memory"
|
||||||
"github.com/micro/go-micro/v2/server"
|
"github.com/micro/go-micro/v2/server"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
pb "github.com/micro/go-micro/v2/server/grpc/proto"
|
pb "github.com/micro/go-micro/v2/server/grpc/proto"
|
||||||
)
|
)
|
||||||
@ -16,6 +18,10 @@ type testServer struct{}
|
|||||||
|
|
||||||
// TestHello implements helloworld.GreeterServer
|
// TestHello implements helloworld.GreeterServer
|
||||||
func (s *testServer) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
|
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
|
rsp.Msg = "Hello " + req.Name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -63,4 +69,21 @@ func TestGRPCServer(t *testing.T) {
|
|||||||
t.Fatalf("Got unexpected response %v", rsp.Msg)
|
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