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

* add error cause for statck trace
pull/1929/head
Tony Chen 3 years ago committed by GitHub
parent 96cfbe6c22
commit a72fc68ffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      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" httpstatus "github.com/go-kratos/kratos/v2/transport/http/status"
"google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
) )
//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto
const ( const (
// UnknownCode is unknown code for error info. // UnknownCode is unknown code for error info.
UnknownCode = 500 UnknownCode = 500
@ -19,21 +20,18 @@ const (
SupportPackageIsVersion1 = true 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 { 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. // Unwrap provides compatibility for Go 1.13 error chains.
func (e *Error) GRPCStatus() *status.Status { func (e *Error) Unwrap() error { return e.cause }
s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message).
WithDetails(&errdetails.ErrorInfo{
Reason: e.Reason,
Metadata: e.Metadata,
})
return s
}
// Is matches each error in the chain with the target value. // Is matches each error in the chain with the target value.
func (e *Error) Is(err error) bool { func (e *Error) Is(err error) bool {
@ -43,19 +41,38 @@ func (e *Error) Is(err error) bool {
return false 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. // WithMetadata with an MD formed by the mapping of key, value.
func (e *Error) WithMetadata(md map[string]string) *Error { func (e *Error) WithMetadata(md map[string]string) *Error {
err := proto.Clone(e).(*Error) err := Clone(e)
err.Metadata = md err.Metadata = md
return err 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. // New returns an error object for the code, message.
func New(code int, reason, message string) *Error { func New(code int, reason, message string) *Error {
return &Error{ return &Error{
Status: Status{
Code: int32(code), Code: int32(code),
Message: message, Message: message,
Reason: reason, Reason: reason,
},
} }
} }
@ -87,6 +104,23 @@ func Reason(err error) string {
return FromError(err).Reason 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. // FromError try to convert an error to *Error.
// It supports wrapped errors. // It supports wrapped errors.
func FromError(err error) *Error { func FromError(err error) *Error {

@ -1,8 +1,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.27.1 // protoc-gen-go v1.27.1
// protoc v3.17.3 // protoc v3.19.4
// source: errors/errors.proto // source: errors.proto
package errors package errors
@ -21,7 +21,7 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type Error struct { type Status struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields 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"` 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() { func (x *Status) Reset() {
*x = Error{} *x = Status{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_errors_errors_proto_msgTypes[0] mi := &file_errors_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
} }
func (x *Error) String() string { func (x *Status) String() string {
return protoimpl.X.MessageStringOf(x) return protoimpl.X.MessageStringOf(x)
} }
func (*Error) ProtoMessage() {} func (*Status) ProtoMessage() {}
func (x *Error) ProtoReflect() protoreflect.Message { func (x *Status) ProtoReflect() protoreflect.Message {
mi := &file_errors_errors_proto_msgTypes[0] mi := &file_errors_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -59,47 +59,47 @@ func (x *Error) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
// Deprecated: Use Error.ProtoReflect.Descriptor instead. // Deprecated: Use Status.ProtoReflect.Descriptor instead.
func (*Error) Descriptor() ([]byte, []int) { func (*Status) Descriptor() ([]byte, []int) {
return file_errors_errors_proto_rawDescGZIP(), []int{0} return file_errors_proto_rawDescGZIP(), []int{0}
} }
func (x *Error) GetCode() int32 { func (x *Status) GetCode() int32 {
if x != nil { if x != nil {
return x.Code return x.Code
} }
return 0 return 0
} }
func (x *Error) GetReason() string { func (x *Status) GetReason() string {
if x != nil { if x != nil {
return x.Reason return x.Reason
} }
return "" return ""
} }
func (x *Error) GetMessage() string { func (x *Status) GetMessage() string {
if x != nil { if x != nil {
return x.Message return x.Message
} }
return "" return ""
} }
func (x *Error) GetMetadata() map[string]string { func (x *Status) GetMetadata() map[string]string {
if x != nil { if x != nil {
return x.Metadata return x.Metadata
} }
return nil return nil
} }
var file_errors_errors_proto_extTypes = []protoimpl.ExtensionInfo{ var file_errors_proto_extTypes = []protoimpl.ExtensionInfo{
{ {
ExtendedType: (*descriptorpb.EnumOptions)(nil), ExtendedType: (*descriptorpb.EnumOptions)(nil),
ExtensionType: (*int32)(nil), ExtensionType: (*int32)(nil),
Field: 1108, Field: 1108,
Name: "errors.default_code", Name: "errors.default_code",
Tag: "varint,1108,opt,name=default_code", Tag: "varint,1108,opt,name=default_code",
Filename: "errors/errors.proto", Filename: "errors.proto",
}, },
{ {
ExtendedType: (*descriptorpb.EnumValueOptions)(nil), ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
@ -107,79 +107,78 @@ var file_errors_errors_proto_extTypes = []protoimpl.ExtensionInfo{
Field: 1109, Field: 1109,
Name: "errors.code", Name: "errors.code",
Tag: "varint,1109,opt,name=code", Tag: "varint,1109,opt,name=code",
Filename: "errors/errors.proto", Filename: "errors.proto",
}, },
} }
// Extension fields to descriptorpb.EnumOptions. // Extension fields to descriptorpb.EnumOptions.
var ( var (
// optional int32 default_code = 1108; // 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. // Extension fields to descriptorpb.EnumValueOptions.
var ( var (
// optional int32 code = 1109; // 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_proto protoreflect.FileDescriptor
var file_errors_errors_proto_rawDesc = []byte{ var file_errors_proto_rawDesc = []byte{
0x0a, 0x13, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc5, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61,
0xc3, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74,
0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x72,
0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61,
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45,
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x40, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x5f, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4,
0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f,
0x75, 0x6c, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x64, 0x65, 0x3a, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f,
0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75,
0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, 0x08,
0x6e, 0x73, 0x18, 0xd5, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x59, 0x0a, 0x18, 0x63, 0x6f,
0x59, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e,
0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b,
0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x72, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x3b,
0x72, 0x6f, 0x72, 0x73, 0x3b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0xa2, 0x02, 0x0c, 0x4b, 0x72, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0xa2, 0x02, 0x0c, 0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x45,
0x61, 0x74, 0x6f, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x33,
} }
var ( var (
file_errors_errors_proto_rawDescOnce sync.Once file_errors_proto_rawDescOnce sync.Once
file_errors_errors_proto_rawDescData = file_errors_errors_proto_rawDesc file_errors_proto_rawDescData = file_errors_proto_rawDesc
) )
func file_errors_errors_proto_rawDescGZIP() []byte { func file_errors_proto_rawDescGZIP() []byte {
file_errors_errors_proto_rawDescOnce.Do(func() { file_errors_proto_rawDescOnce.Do(func() {
file_errors_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_errors_proto_rawDescData) 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_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_errors_errors_proto_goTypes = []interface{}{ var file_errors_proto_goTypes = []interface{}{
(*Error)(nil), // 0: errors.Error (*Status)(nil), // 0: errors.Status
nil, // 1: errors.Error.MetadataEntry nil, // 1: errors.Status.MetadataEntry
(*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions (*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions
(*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions (*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions
} }
var file_errors_errors_proto_depIdxs = []int32{ var file_errors_proto_depIdxs = []int32{
1, // 0: errors.Error.metadata:type_name -> errors.Error.MetadataEntry 1, // 0: errors.Status.metadata:type_name -> errors.Status.MetadataEntry
2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions 2, // 1: errors.default_code:extendee -> google.protobuf.EnumOptions
3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions 3, // 2: errors.code:extendee -> google.protobuf.EnumValueOptions
3, // [3:3] is the sub-list for method output_type 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 0, // [0:1] is the sub-list for field type_name
} }
func init() { file_errors_errors_proto_init() } func init() { file_errors_proto_init() }
func file_errors_errors_proto_init() { func file_errors_proto_init() {
if File_errors_errors_proto != nil { if File_errors_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled { if !protoimpl.UnsafeEnabled {
file_errors_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { file_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Error); i { switch v := v.(*Status); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@ -212,19 +211,19 @@ func file_errors_errors_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_errors_errors_proto_rawDesc, RawDescriptor: file_errors_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 2, NumMessages: 2,
NumExtensions: 2, NumExtensions: 2,
NumServices: 0, NumServices: 0,
}, },
GoTypes: file_errors_errors_proto_goTypes, GoTypes: file_errors_proto_goTypes,
DependencyIndexes: file_errors_errors_proto_depIdxs, DependencyIndexes: file_errors_proto_depIdxs,
MessageInfos: file_errors_errors_proto_msgTypes, MessageInfos: file_errors_proto_msgTypes,
ExtensionInfos: file_errors_errors_proto_extTypes, ExtensionInfos: file_errors_proto_extTypes,
}.Build() }.Build()
File_errors_errors_proto = out.File File_errors_proto = out.File
file_errors_errors_proto_rawDesc = nil file_errors_proto_rawDesc = nil
file_errors_errors_proto_goTypes = nil file_errors_proto_goTypes = nil
file_errors_errors_proto_depIdxs = nil file_errors_proto_depIdxs = nil
} }

@ -9,7 +9,7 @@ option objc_class_prefix = "KratosErrors";
import "google/protobuf/descriptor.proto"; import "google/protobuf/descriptor.proto";
message Error { message Status {
int32 code = 1; int32 code = 1;
string reason = 2; string reason = 2;
string message = 3; string message = 3;

@ -12,7 +12,11 @@ import (
"google.golang.org/grpc/test/grpc_testing" "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 var base *Error
err := Newf(http.StatusBadRequest, "reason", "message") err := Newf(http.StatusBadRequest, "reason", "message")
err2 := Newf(http.StatusBadRequest, "reason", "message") err2 := Newf(http.StatusBadRequest, "reason", "message")
@ -76,13 +80,13 @@ func TestIs(t *testing.T) {
}{ }{
{ {
name: "true", name: "true",
e: &Error{Code: 404, Reason: "test"}, e: New(404, "test", ""),
err: New(http.StatusNotFound, "test", ""), err: New(http.StatusNotFound, "test", ""),
want: true, want: true,
}, },
{ {
name: "false", name: "false",
e: &Error{Reason: "test"}, e: New(0, "test", ""),
err: errors.New("test"), err: errors.New("test"),
want: false, 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) { func TestOther(t *testing.T) {
if !reflect.DeepEqual(Code(nil), 200) { if !reflect.DeepEqual(Code(nil), 200) {
t.Errorf("Code(nil) = %v, want %v", 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) { return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
if v, ok := req.(validator); ok { if v, ok := req.(validator); ok {
if err := v.Validate(); err != nil { 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) return handler(ctx, req)

@ -31,11 +31,11 @@ func TestDirect(t *testing.T) {
if !reflect.DeepEqual(float64(10), wn.Weight()) { if !reflect.DeepEqual(float64(10), wn.Weight()) {
t.Errorf("expect %v, got %v", float64(10), wn.Weight()) t.Errorf("expect %v, got %v", float64(10), wn.Weight())
} }
if time.Millisecond*15 <= wn.PickElapsed() { if time.Millisecond*20 <= wn.PickElapsed() {
t.Errorf("time.Millisecond*15 <= wn.PickElapsed()(%s)", wn.PickElapsed()) t.Errorf("20ms <= wn.PickElapsed()(%s)", wn.PickElapsed())
} }
if time.Millisecond*5 >= wn.PickElapsed() { if time.Millisecond*10 >= wn.PickElapsed() {
t.Errorf("time.Millisecond*5 >= wn.PickElapsed()(%s)", 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 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 // CodecForResponse get encoding.Codec via http.Response

@ -255,14 +255,14 @@ func TestDefaultErrorDecoder(t *testing.T) {
if err2 == nil { if err2 == nil {
t.Errorf("expected error, got nil") t.Errorf("expected error, got nil")
} }
if !reflect.DeepEqual(int32(500), err2.(*kratosErrors.Error).GetCode()) { if !reflect.DeepEqual(int32(500), err2.(*kratosErrors.Error).Code) {
t.Errorf("expected %v, got %v", 500, err2.(*kratosErrors.Error).GetCode()) t.Errorf("expected %v, got %v", 500, err2.(*kratosErrors.Error).Code)
} }
if !reflect.DeepEqual("hi", err2.(*kratosErrors.Error).GetMessage()) { if !reflect.DeepEqual("hi", err2.(*kratosErrors.Error).Message) {
t.Errorf("expected %v, got %v", "hi", err2.(*kratosErrors.Error).GetMessage()) t.Errorf("expected %v, got %v", "hi", err2.(*kratosErrors.Error).Message)
} }
if !reflect.DeepEqual("FOO", err2.(*kratosErrors.Error).GetReason()) { if !reflect.DeepEqual("FOO", err2.(*kratosErrors.Error).Reason) {
t.Errorf("expected %v, got %v", "FOO", err2.(*kratosErrors.Error).GetReason()) t.Errorf("expected %v, got %v", "FOO", err2.(*kratosErrors.Error).Reason)
} }
} }

@ -87,7 +87,7 @@ func TestDefaultResponseEncoderWithError(t *testing.T) {
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
se := &errors.Error{Code: 511} se := errors.New(511, "", "")
DefaultErrorEncoder(w, req, se) DefaultErrorEncoder(w, req, se)
if !reflect.DeepEqual("application/json", w.Header().Get("Content-Type")) { if !reflect.DeepEqual("application/json", w.Header().Get("Content-Type")) {
t.Errorf("expected %v, got %v", "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) { srv.HandleHeader("content-type", "application/grpc-web+json", func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(testData{Path: r.RequestURI}) _ = 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") { if e, err := srv.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") {
t.Fatal(e, err) t.Fatal(e, err)
@ -49,11 +54,45 @@ func TestServer(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
testHeader(t, srv) testHeader(t, srv)
testClient(t, srv) testClient(t, srv)
testAccept(t, srv)
if srv.Stop(ctx) != nil { if srv.Stop(ctx) != nil {
t.Errorf("expected nil got %v", srv.Stop(ctx)) 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) { func testHeader(t *testing.T, srv *Server) {
e, err := srv.Endpoint() e, err := srv.Endpoint()
if err != nil { if err != nil {
@ -80,20 +119,22 @@ func testClient(t *testing.T, srv *Server) {
tests := []struct { tests := []struct {
method string method string
path string path string
code int
}{ }{
{"GET", "/index"}, {"GET", "/index", 200},
{"PUT", "/index"}, {"PUT", "/index", 200},
{"POST", "/index"}, {"POST", "/index", 200},
{"PATCH", "/index"}, {"PATCH", "/index", 200},
{"DELETE", "/index"}, {"DELETE", "/index", 200},
{"GET", "/index/1"}, {"GET", "/index/1", 200},
{"PUT", "/index/1"}, {"PUT", "/index/1", 200},
{"POST", "/index/1"}, {"POST", "/index/1", 200},
{"PATCH", "/index/1"}, {"PATCH", "/index/1", 200},
{"DELETE", "/index/1"}, {"DELETE", "/index/1", 200},
{"GET", "/index/notfound"}, {"GET", "/index/notfound", 404},
{"GET", "/errors/cause", 400},
} }
e, err := srv.Endpoint() e, err := srv.Endpoint()
if err != nil { if err != nil {
@ -112,13 +153,11 @@ func testClient(t *testing.T, srv *Server) {
t.Fatal(err) t.Fatal(err)
} }
resp, err := client.Do(req) resp, err := client.Do(req)
if test.path == "/index/notfound" && err != nil { if errors.Code(err) != test.code {
if e, ok := err.(*errors.Error); ok && e.Code == http.StatusNotFound { t.Fatalf("want %v, but got %v", test, err)
continue
}
} }
if err != nil { if err != nil {
t.Fatal(err) continue
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
_ = resp.Body.Close() _ = resp.Body.Close()
@ -140,13 +179,11 @@ func testClient(t *testing.T, srv *Server) {
for _, test := range tests { for _, test := range tests {
var res testData var res testData
err := client.Invoke(context.Background(), test.method, test.path, nil, &res) err := client.Invoke(context.Background(), test.method, test.path, nil, &res)
if test.path == "/index/notfound" && err != nil { if errors.Code(err) != test.code {
if e, ok := err.(*errors.Error); ok && e.Code == http.StatusNotFound { t.Fatalf("want %v, but got %v", test, err)
continue
}
} }
if err != nil { if err != nil {
t.Fatalf("invoke error %v", err) continue
} }
if res.Path != test.path { if res.Path != test.path {
t.Errorf("expected %s got %s", test.path, res.Path) t.Errorf("expected %s got %s", test.path, res.Path)

@ -13,6 +13,7 @@ const (
ClientClosed = 499 ClientClosed = 499
) )
// Converter is a status converter.
type Converter interface { type Converter interface {
// ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status.
ToGRPCCode(code int) codes.Code ToGRPCCode(code int) codes.Code
@ -23,6 +24,7 @@ type Converter interface {
type statusConverter struct{} type statusConverter struct{}
// DefaultConverter default converter.
var DefaultConverter Converter = statusConverter{} var DefaultConverter Converter = statusConverter{}
// ToGRPCCode converts a HTTP error code into the corresponding gRPC response status. // ToGRPCCode converts a HTTP error code into the corresponding gRPC response status.

Loading…
Cancel
Save