mirror of
				https://github.com/go-micro/go-micro.git
				synced 2025-10-30 23:27:41 +02:00 
			
		
		
		
	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:
		| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| 				} | ||||
| 			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() | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user