feat: add error cause for statck trace (#1910)

* add error cause for statck trace
status-code-override
Tony Chen 3 years ago committed by chenzhihui
parent 4133bc497c
commit a9ebd0822c
  1. 66
      errors/errors.go
  2. 163
      errors/errors.pb.go
  3. 2
      errors/errors.proto
  4. 23
      errors/errors_test.go
  5. 2
      middleware/validate/validate.go
  6. 8
      selector/node/direct/direct_test.go
  7. 2
      transport/http/client.go
  8. 12
      transport/http/client_test.go
  9. 2
      transport/http/codec_test.go
  10. 79
      transport/http/server_test.go
  11. 2
      transport/http/status/status.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 {

@ -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
}

@ -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;

@ -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)

@ -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)

@ -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())
}
}

@ -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

@ -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)
}
}

@ -97,7 +97,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"))

@ -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)

@ -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.

Loading…
Cancel
Save