From a72fc68ffd1736f956234cf7bd123625c10f8d74 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Sun, 10 Apr 2022 16:15:34 +0800 Subject: [PATCH] feat: add error cause for statck trace (#1910) * add error cause for statck trace --- errors/errors.go | 66 ++++++++--- errors/errors.pb.go | 163 ++++++++++++++-------------- errors/errors.proto | 2 +- errors/errors_test.go | 23 +++- middleware/validate/validate.go | 2 +- selector/node/direct/direct_test.go | 8 +- transport/http/client.go | 2 +- transport/http/client_test.go | 12 +- transport/http/codec_test.go | 2 +- transport/http/server_test.go | 79 ++++++++++---- transport/http/status/status.go | 2 + 11 files changed, 225 insertions(+), 136 deletions(-) diff --git a/errors/errors.go b/errors/errors.go index c9f6ef00d..f3031616e 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -7,9 +7,10 @@ import ( httpstatus "github.com/go-kratos/kratos/v2/transport/http/status" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" ) +//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto + const ( // UnknownCode is unknown code for error info. UnknownCode = 500 @@ -19,21 +20,18 @@ const ( SupportPackageIsVersion1 = true ) -//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto +// Error is a status error. +type Error struct { + Status + cause error +} func (e *Error) Error() string { - return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v", e.Code, e.Reason, e.Message, e.Metadata) + return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v cause = %v", e.Code, e.Reason, e.Message, e.Metadata, e.cause) } -// GRPCStatus returns the Status represented by se. -func (e *Error) GRPCStatus() *status.Status { - s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). - WithDetails(&errdetails.ErrorInfo{ - Reason: e.Reason, - Metadata: e.Metadata, - }) - return s -} +// Unwrap provides compatibility for Go 1.13 error chains. +func (e *Error) Unwrap() error { return e.cause } // Is matches each error in the chain with the target value. func (e *Error) Is(err error) bool { @@ -43,19 +41,38 @@ func (e *Error) Is(err error) bool { return false } +// WithCause with the underlying cause of the error. +func (e *Error) WithCause(cause error) *Error { + err := Clone(e) + err.cause = cause + return err +} + // WithMetadata with an MD formed by the mapping of key, value. func (e *Error) WithMetadata(md map[string]string) *Error { - err := proto.Clone(e).(*Error) + err := Clone(e) err.Metadata = md return err } +// GRPCStatus returns the Status represented by se. +func (e *Error) GRPCStatus() *status.Status { + s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). + WithDetails(&errdetails.ErrorInfo{ + Reason: e.Reason, + Metadata: e.Metadata, + }) + return s +} + // New returns an error object for the code, message. func New(code int, reason, message string) *Error { return &Error{ - Code: int32(code), - Message: message, - Reason: reason, + Status: Status{ + Code: int32(code), + Message: message, + Reason: reason, + }, } } @@ -87,6 +104,23 @@ func Reason(err error) string { return FromError(err).Reason } +// Clone deep clone error to a new error. +func Clone(err *Error) *Error { + metadata := make(map[string]string, len(err.Metadata)) + for k, v := range err.Metadata { + metadata[k] = v + } + return &Error{ + cause: err.cause, + Status: Status{ + Code: err.Code, + Reason: err.Reason, + Message: err.Message, + Metadata: metadata, + }, + } +} + // FromError try to convert an error to *Error. // It supports wrapped errors. func FromError(err error) *Error { diff --git a/errors/errors.pb.go b/errors/errors.pb.go index d09b70fad..0f4e28f23 100644 --- a/errors/errors.pb.go +++ b/errors/errors.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.17.3 -// source: errors/errors.proto +// protoc v3.19.4 +// source: errors.proto package errors @@ -21,7 +21,7 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type Error struct { +type Status struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -32,23 +32,23 @@ type Error struct { Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } -func (x *Error) Reset() { - *x = Error{} +func (x *Status) Reset() { + *x = Status{} if protoimpl.UnsafeEnabled { - mi := &file_errors_errors_proto_msgTypes[0] + mi := &file_errors_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *Error) String() string { +func (x *Status) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Error) ProtoMessage() {} +func (*Status) ProtoMessage() {} -func (x *Error) ProtoReflect() protoreflect.Message { - mi := &file_errors_errors_proto_msgTypes[0] +func (x *Status) ProtoReflect() protoreflect.Message { + mi := &file_errors_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -59,47 +59,47 @@ func (x *Error) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Error.ProtoReflect.Descriptor instead. -func (*Error) Descriptor() ([]byte, []int) { - return file_errors_errors_proto_rawDescGZIP(), []int{0} +// Deprecated: Use Status.ProtoReflect.Descriptor instead. +func (*Status) Descriptor() ([]byte, []int) { + return file_errors_proto_rawDescGZIP(), []int{0} } -func (x *Error) GetCode() int32 { +func (x *Status) GetCode() int32 { if x != nil { return x.Code } return 0 } -func (x *Error) GetReason() string { +func (x *Status) GetReason() string { if x != nil { return x.Reason } return "" } -func (x *Error) GetMessage() string { +func (x *Status) GetMessage() string { if x != nil { return x.Message } return "" } -func (x *Error) GetMetadata() map[string]string { +func (x *Status) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } -var file_errors_errors_proto_extTypes = []protoimpl.ExtensionInfo{ +var file_errors_proto_extTypes = []protoimpl.ExtensionInfo{ { ExtendedType: (*descriptorpb.EnumOptions)(nil), ExtensionType: (*int32)(nil), Field: 1108, Name: "errors.default_code", Tag: "varint,1108,opt,name=default_code", - Filename: "errors/errors.proto", + Filename: "errors.proto", }, { ExtendedType: (*descriptorpb.EnumValueOptions)(nil), @@ -107,79 +107,78 @@ var file_errors_errors_proto_extTypes = []protoimpl.ExtensionInfo{ Field: 1109, Name: "errors.code", Tag: "varint,1109,opt,name=code", - Filename: "errors/errors.proto", + Filename: "errors.proto", }, } // Extension fields to descriptorpb.EnumOptions. var ( // optional int32 default_code = 1108; - E_DefaultCode = &file_errors_errors_proto_extTypes[0] + E_DefaultCode = &file_errors_proto_extTypes[0] ) // Extension fields to descriptorpb.EnumValueOptions. var ( // optional int32 code = 1109; - E_Code = &file_errors_errors_proto_extTypes[1] + E_Code = &file_errors_proto_extTypes[1] ) -var File_errors_errors_proto protoreflect.FileDescriptor - -var file_errors_errors_proto_rawDesc = []byte{ - 0x0a, 0x13, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0xc3, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, - 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0xd5, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, - 0x59, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, - 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x2c, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, - 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0xa2, 0x02, 0x0c, 0x4b, 0x72, - 0x61, 0x74, 0x6f, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, +var File_errors_proto protoreflect.FileDescriptor + +var file_errors_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc5, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4, + 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, + 0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, + 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x08, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x59, 0x0a, 0x18, 0x63, 0x6f, + 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, + 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x3b, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0xa2, 0x02, 0x0c, 0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_errors_errors_proto_rawDescOnce sync.Once - file_errors_errors_proto_rawDescData = file_errors_errors_proto_rawDesc + file_errors_proto_rawDescOnce sync.Once + file_errors_proto_rawDescData = file_errors_proto_rawDesc ) -func file_errors_errors_proto_rawDescGZIP() []byte { - file_errors_errors_proto_rawDescOnce.Do(func() { - file_errors_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_errors_proto_rawDescData) +func file_errors_proto_rawDescGZIP() []byte { + file_errors_proto_rawDescOnce.Do(func() { + file_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_proto_rawDescData) }) - return file_errors_errors_proto_rawDescData + return file_errors_proto_rawDescData } -var file_errors_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_errors_errors_proto_goTypes = []interface{}{ - (*Error)(nil), // 0: errors.Error - nil, // 1: errors.Error.MetadataEntry +var file_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_errors_proto_goTypes = []interface{}{ + (*Status)(nil), // 0: errors.Status + nil, // 1: errors.Status.MetadataEntry (*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions (*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions } -var file_errors_errors_proto_depIdxs = []int32{ - 1, // 0: errors.Error.metadata:type_name -> errors.Error.MetadataEntry +var file_errors_proto_depIdxs = []int32{ + 1, // 0: errors.Status.metadata:type_name -> errors.Status.MetadataEntry 2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions 3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions 3, // [3:3] is the sub-list for method output_type @@ -189,14 +188,14 @@ var file_errors_errors_proto_depIdxs = []int32{ 0, // [0:1] is the sub-list for field type_name } -func init() { file_errors_errors_proto_init() } -func file_errors_errors_proto_init() { - if File_errors_errors_proto != nil { +func init() { file_errors_proto_init() } +func file_errors_proto_init() { + if File_errors_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_errors_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Error); i { + file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Status); i { case 0: return &v.state case 1: @@ -212,19 +211,19 @@ func file_errors_errors_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_errors_errors_proto_rawDesc, + RawDescriptor: file_errors_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 2, NumServices: 0, }, - GoTypes: file_errors_errors_proto_goTypes, - DependencyIndexes: file_errors_errors_proto_depIdxs, - MessageInfos: file_errors_errors_proto_msgTypes, - ExtensionInfos: file_errors_errors_proto_extTypes, + GoTypes: file_errors_proto_goTypes, + DependencyIndexes: file_errors_proto_depIdxs, + MessageInfos: file_errors_proto_msgTypes, + ExtensionInfos: file_errors_proto_extTypes, }.Build() - File_errors_errors_proto = out.File - file_errors_errors_proto_rawDesc = nil - file_errors_errors_proto_goTypes = nil - file_errors_errors_proto_depIdxs = nil + File_errors_proto = out.File + file_errors_proto_rawDesc = nil + file_errors_proto_goTypes = nil + file_errors_proto_depIdxs = nil } diff --git a/errors/errors.proto b/errors/errors.proto index ff6766b49..ab14f20b3 100644 --- a/errors/errors.proto +++ b/errors/errors.proto @@ -9,7 +9,7 @@ option objc_class_prefix = "KratosErrors"; import "google/protobuf/descriptor.proto"; -message Error { +message Status { int32 code = 1; string reason = 2; string message = 3; diff --git a/errors/errors_test.go b/errors/errors_test.go index 01217bdc1..f76d51511 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -12,7 +12,11 @@ import ( "google.golang.org/grpc/test/grpc_testing" ) -func TestError(t *testing.T) { +type TestError struct{ message string } + +func (e *TestError) Error() string { return e.message } + +func TestErrors(t *testing.T) { var base *Error err := Newf(http.StatusBadRequest, "reason", "message") err2 := Newf(http.StatusBadRequest, "reason", "message") @@ -76,13 +80,13 @@ func TestIs(t *testing.T) { }{ { name: "true", - e: &Error{Code: 404, Reason: "test"}, + e: New(404, "test", ""), err: New(http.StatusNotFound, "test", ""), want: true, }, { name: "false", - e: &Error{Reason: "test"}, + e: New(0, "test", ""), err: errors.New("test"), want: false, }, @@ -96,6 +100,19 @@ func TestIs(t *testing.T) { } } +func TestCause(t *testing.T) { + testError := &TestError{message: "test"} + err := BadRequest("foo", "bar").WithCause(testError) + if !errors.Is(err, testError) { + t.Fatalf("want %v but got %v", testError, err) + } + if te := new(TestError); errors.As(err, &te) { + if te.message != testError.message { + t.Fatalf("want %s but got %s", testError.message, te.message) + } + } +} + func TestOther(t *testing.T) { if !reflect.DeepEqual(Code(nil), 200) { t.Errorf("Code(nil) = %v, want %v", Code(nil), 200) diff --git a/middleware/validate/validate.go b/middleware/validate/validate.go index a6eee1f0d..f33d76e44 100644 --- a/middleware/validate/validate.go +++ b/middleware/validate/validate.go @@ -17,7 +17,7 @@ func Validator() middleware.Middleware { return func(ctx context.Context, req interface{}) (reply interface{}, err error) { if v, ok := req.(validator); ok { if err := v.Validate(); err != nil { - return nil, errors.BadRequest("VALIDATOR", err.Error()) + return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) } } return handler(ctx, req) diff --git a/selector/node/direct/direct_test.go b/selector/node/direct/direct_test.go index 063307da4..d498c55c9 100644 --- a/selector/node/direct/direct_test.go +++ b/selector/node/direct/direct_test.go @@ -31,11 +31,11 @@ func TestDirect(t *testing.T) { if !reflect.DeepEqual(float64(10), wn.Weight()) { t.Errorf("expect %v, got %v", float64(10), wn.Weight()) } - if time.Millisecond*15 <= wn.PickElapsed() { - t.Errorf("time.Millisecond*15 <= wn.PickElapsed()(%s)", wn.PickElapsed()) + if time.Millisecond*20 <= wn.PickElapsed() { + t.Errorf("20ms <= wn.PickElapsed()(%s)", wn.PickElapsed()) } - if time.Millisecond*5 >= wn.PickElapsed() { - t.Errorf("time.Millisecond*5 >= wn.PickElapsed()(%s)", wn.PickElapsed()) + if time.Millisecond*10 >= wn.PickElapsed() { + t.Errorf("10ms >= wn.PickElapsed()(%s)", wn.PickElapsed()) } } diff --git a/transport/http/client.go b/transport/http/client.go index 17cd34e3d..55719eb9e 100644 --- a/transport/http/client.go +++ b/transport/http/client.go @@ -341,7 +341,7 @@ func DefaultErrorDecoder(ctx context.Context, res *http.Response) error { return e } } - return errors.Errorf(res.StatusCode, errors.UnknownReason, err.Error()) + return errors.Newf(res.StatusCode, errors.UnknownReason, "").WithCause(err) } // CodecForResponse get encoding.Codec via http.Response diff --git a/transport/http/client_test.go b/transport/http/client_test.go index 61365223a..731d111dc 100644 --- a/transport/http/client_test.go +++ b/transport/http/client_test.go @@ -255,14 +255,14 @@ func TestDefaultErrorDecoder(t *testing.T) { if err2 == nil { t.Errorf("expected error, got nil") } - if !reflect.DeepEqual(int32(500), err2.(*kratosErrors.Error).GetCode()) { - t.Errorf("expected %v, got %v", 500, err2.(*kratosErrors.Error).GetCode()) + if !reflect.DeepEqual(int32(500), err2.(*kratosErrors.Error).Code) { + t.Errorf("expected %v, got %v", 500, err2.(*kratosErrors.Error).Code) } - if !reflect.DeepEqual("hi", err2.(*kratosErrors.Error).GetMessage()) { - t.Errorf("expected %v, got %v", "hi", err2.(*kratosErrors.Error).GetMessage()) + if !reflect.DeepEqual("hi", err2.(*kratosErrors.Error).Message) { + t.Errorf("expected %v, got %v", "hi", err2.(*kratosErrors.Error).Message) } - if !reflect.DeepEqual("FOO", err2.(*kratosErrors.Error).GetReason()) { - t.Errorf("expected %v, got %v", "FOO", err2.(*kratosErrors.Error).GetReason()) + if !reflect.DeepEqual("FOO", err2.(*kratosErrors.Error).Reason) { + t.Errorf("expected %v, got %v", "FOO", err2.(*kratosErrors.Error).Reason) } } diff --git a/transport/http/codec_test.go b/transport/http/codec_test.go index 880890722..ad77eeb62 100644 --- a/transport/http/codec_test.go +++ b/transport/http/codec_test.go @@ -87,7 +87,7 @@ func TestDefaultResponseEncoderWithError(t *testing.T) { } req.Header.Set("Content-Type", "application/json") - se := &errors.Error{Code: 511} + se := errors.New(511, "", "") DefaultErrorEncoder(w, req, se) if !reflect.DeepEqual("application/json", w.Header().Get("Content-Type")) { t.Errorf("expected %v, got %v", "application/json", w.Header().Get("Content-Type")) diff --git a/transport/http/server_test.go b/transport/http/server_test.go index 8fdaf3cc8..34f2b8553 100644 --- a/transport/http/server_test.go +++ b/transport/http/server_test.go @@ -36,6 +36,11 @@ func TestServer(t *testing.T) { srv.HandleHeader("content-type", "application/grpc-web+json", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(testData{Path: r.RequestURI}) }) + srv.Route("/errors").GET("/cause", func(ctx Context) error { + return errors.BadRequest("xxx", "zzz"). + WithMetadata(map[string]string{"foo": "bar"}). + WithCause(fmt.Errorf("error cause")) + }) if e, err := srv.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") { t.Fatal(e, err) @@ -49,11 +54,45 @@ func TestServer(t *testing.T) { time.Sleep(time.Second) testHeader(t, srv) testClient(t, srv) + testAccept(t, srv) if srv.Stop(ctx) != nil { t.Errorf("expected nil got %v", srv.Stop(ctx)) } } +func testAccept(t *testing.T, srv *Server) { + tests := []struct { + method string + path string + contentType string + }{ + {"GET", "/errors/cause", "application/json"}, + {"GET", "/errors/cause", "application/proto"}, + } + e, err := srv.Endpoint() + if err != nil { + t.Errorf("expected nil got %v", err) + } + client, err := NewClient(context.Background(), WithEndpoint(e.Host)) + if err != nil { + t.Errorf("expected nil got %v", err) + } + for _, test := range tests { + req, err := http.NewRequest(test.method, e.String()+test.path, nil) + if err != nil { + t.Errorf("expected nil got %v", err) + } + req.Header.Set("Content-Type", test.contentType) + resp, err := client.Do(req) + if errors.Code(err) != 400 { + t.Errorf("expected 400 got %v", err) + } + if err == nil { + resp.Body.Close() + } + } +} + func testHeader(t *testing.T, srv *Server) { e, err := srv.Endpoint() if err != nil { @@ -80,20 +119,22 @@ func testClient(t *testing.T, srv *Server) { tests := []struct { method string path string + code int }{ - {"GET", "/index"}, - {"PUT", "/index"}, - {"POST", "/index"}, - {"PATCH", "/index"}, - {"DELETE", "/index"}, + {"GET", "/index", 200}, + {"PUT", "/index", 200}, + {"POST", "/index", 200}, + {"PATCH", "/index", 200}, + {"DELETE", "/index", 200}, - {"GET", "/index/1"}, - {"PUT", "/index/1"}, - {"POST", "/index/1"}, - {"PATCH", "/index/1"}, - {"DELETE", "/index/1"}, + {"GET", "/index/1", 200}, + {"PUT", "/index/1", 200}, + {"POST", "/index/1", 200}, + {"PATCH", "/index/1", 200}, + {"DELETE", "/index/1", 200}, - {"GET", "/index/notfound"}, + {"GET", "/index/notfound", 404}, + {"GET", "/errors/cause", 400}, } e, err := srv.Endpoint() if err != nil { @@ -112,13 +153,11 @@ func testClient(t *testing.T, srv *Server) { t.Fatal(err) } resp, err := client.Do(req) - if test.path == "/index/notfound" && err != nil { - if e, ok := err.(*errors.Error); ok && e.Code == http.StatusNotFound { - continue - } + if errors.Code(err) != test.code { + t.Fatalf("want %v, but got %v", test, err) } if err != nil { - t.Fatal(err) + continue } if resp.StatusCode != 200 { _ = resp.Body.Close() @@ -140,13 +179,11 @@ func testClient(t *testing.T, srv *Server) { for _, test := range tests { var res testData err := client.Invoke(context.Background(), test.method, test.path, nil, &res) - if test.path == "/index/notfound" && err != nil { - if e, ok := err.(*errors.Error); ok && e.Code == http.StatusNotFound { - continue - } + if errors.Code(err) != test.code { + t.Fatalf("want %v, but got %v", test, err) } if err != nil { - t.Fatalf("invoke error %v", err) + continue } if res.Path != test.path { t.Errorf("expected %s got %s", test.path, res.Path) diff --git a/transport/http/status/status.go b/transport/http/status/status.go index d2240ec74..2f6a717f8 100644 --- a/transport/http/status/status.go +++ b/transport/http/status/status.go @@ -13,6 +13,7 @@ const ( ClientClosed = 499 ) +// Converter is a status converter. type Converter interface { // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. ToGRPCCode(code int) codes.Code @@ -23,6 +24,7 @@ type Converter interface { type statusConverter struct{} +// DefaultConverter default converter. var DefaultConverter Converter = statusConverter{} // ToGRPCCode converts a HTTP error code into the corresponding gRPC response status.