errors: refactor status code (#948)

* refactor status code
pull/952/head
Tony Chen 4 years ago committed by GitHub
parent cc0221b5ce
commit 66412031fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      errors/errors.go
  2. 59
      errors/errors.pb.go
  3. 8
      errors/errors.proto
  4. 12
      errors/errors_test.go
  5. 46
      errors/types.go
  6. 14
      errors/types_test.go
  7. 2
      examples/helloworld/server/main.go
  8. 2
      middleware/recovery/recovery.go
  9. 2
      middleware/validate/validate.go
  10. 7
      transport/http/client.go
  11. 18
      transport/http/handle.go

@ -11,22 +11,24 @@ import (
"google.golang.org/protobuf/proto"
)
// UnknownReason is unknown reason for error info.
const UnknownReason = ""
//go:generate protoc -I. --go_out=paths=source_relative:. errors.proto
func (e *Error) Error() string {
return fmt.Sprintf("error: domain = %s reason = %s metadata = %v", e.Domain, e.Reason, e.Metadata)
return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v", e.Code, e.Reason, e.Message, e.Metadata)
}
// HTTPStatus return an HTTP error code.
func (e *Error) HTTPStatus() int {
return httputil.StatusFromGRPCCode(codes.Code(e.Code))
// StatusCode return an HTTP error code.
func (e *Error) StatusCode() int {
return int(e.Code)
}
// GRPCStatus returns the Status represented by se.
func (e *Error) GRPCStatus() *status.Status {
s, _ := status.New(codes.Code(e.Code), e.Message).
s, _ := status.New(httputil.GRPCCodeFromStatus(e.StatusCode()), e.Message).
WithDetails(&errdetails.ErrorInfo{
Domain: e.Domain,
Reason: e.Reason,
Metadata: e.Metadata,
})
@ -36,7 +38,7 @@ func (e *Error) GRPCStatus() *status.Status {
// Is matches each error in the chain with the target value.
func (e *Error) Is(err error) bool {
if se := new(Error); errors.As(err, &se) {
return se.Domain == e.Domain && se.Reason == e.Reason
return se.Reason == e.Reason
}
return false
}
@ -49,23 +51,22 @@ func (e *Error) WithMetadata(md map[string]string) *Error {
}
// New returns an error object for the code, message.
func New(code codes.Code, domain, reason, message string) *Error {
func New(code int, reason, message string) *Error {
return &Error{
Code: int32(code),
Message: message,
Domain: domain,
Reason: reason,
}
}
// Newf New(code fmt.Sprintf(format, a...))
func Newf(code codes.Code, domain, reason, format string, a ...interface{}) *Error {
return New(code, domain, reason, fmt.Sprintf(format, a...))
func Newf(code int, reason, format string, a ...interface{}) *Error {
return New(code, reason, fmt.Sprintf(format, a...))
}
// Errorf returns an error object for the code, message and error info.
func Errorf(code codes.Code, domain, reason, format string, a ...interface{}) error {
return New(code, domain, reason, fmt.Sprintf(format, a...))
func Errorf(code int, reason, format string, a ...interface{}) error {
return New(code, reason, fmt.Sprintf(format, a...))
}
// Code returns the code for a particular error.
@ -80,22 +81,13 @@ func Code(err error) codes.Code {
return codes.Unknown
}
// Domain returns the domain for a particular error.
// It supports wrapped errors.
func Domain(err error) string {
if se := FromError(err); err != nil {
return se.Domain
}
return ""
}
// Reason returns the reason for a particular error.
// It supports wrapped errors.
func Reason(err error) string {
if se := FromError(err); err != nil {
return se.Reason
}
return ""
return UnknownReason
}
// FromError try to convert an error to *Error.
@ -113,13 +105,12 @@ func FromError(err error) *Error {
switch d := detail.(type) {
case *errdetails.ErrorInfo:
return New(
gs.Code(),
d.Domain,
httputil.StatusFromGRPCCode(gs.Code()),
d.Reason,
gs.Message(),
).WithMetadata(d.Metadata)
}
}
}
return New(gs.Code(), "", "", err.Error())
return New(httputil.StatusFromGRPCCode(gs.Code()), UnknownReason, err.Error())
}

@ -26,11 +26,9 @@ type Error struct {
unknownFields protoimpl.UnknownFields
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
// error details
Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"`
Domain string `protobuf:"bytes,4,opt,name=domain,proto3" json:"domain,omitempty"`
Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"`
Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
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() {
@ -72,13 +70,6 @@ func (x *Error) GetCode() int32 {
return 0
}
func (x *Error) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *Error) GetReason() string {
if x != nil {
return x.Reason
@ -86,9 +77,9 @@ func (x *Error) GetReason() string {
return ""
}
func (x *Error) GetDomain() string {
func (x *Error) GetMessage() string {
if x != nil {
return x.Domain
return x.Message
}
return ""
}
@ -104,28 +95,26 @@ 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, 0x0d,
0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xe2, 0x01,
0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0xca, 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, 0x18, 0x0a, 0x07, 0x6d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a,
0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73,
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, 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,
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, 0x3e, 0x0a,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x22, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 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, 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 (

@ -9,9 +9,7 @@ option objc_class_prefix = "KratosErrors";
message Error {
int32 code = 1;
string message = 2;
// error details
string reason = 3;
string domain = 4;
map<string, string> metadata = 5;
string reason = 2;
string message = 3;
map<string, string> metadata = 4;
};

@ -3,17 +3,16 @@ package errors
import (
"errors"
"fmt"
"net/http"
"testing"
"google.golang.org/grpc/codes"
)
func TestError(t *testing.T) {
var (
base *Error
)
err := Newf(codes.InvalidArgument, "domain", "reason", "message")
err2 := Newf(codes.InvalidArgument, "domain", "reason", "message")
err := Newf(http.StatusBadRequest, "reason", "message")
err2 := Newf(http.StatusBadRequest, "reason", "message")
err3 := err.WithMetadata(map[string]string{
"foo": "bar",
})
@ -36,9 +35,6 @@ func TestError(t *testing.T) {
t.Errorf("should be matchs: %v", err)
}
if domain := Domain(err); domain != err2.Domain {
t.Errorf("got %s want: %s", domain, err)
}
if reason := Reason(err); reason != err3.Reason {
t.Errorf("got %s want: %s", reason, err)
}
@ -49,7 +45,7 @@ func TestError(t *testing.T) {
gs := err.GRPCStatus()
se := FromError(gs.Err())
if se.Domain != err.Domain || se.Reason != se.Reason {
if se.Reason != se.Reason {
t.Errorf("got %+v want %+v", se, err)
}
}

@ -1,82 +1,78 @@
package errors
import (
"google.golang.org/grpc/codes"
)
// BadRequest new BadRequest error that is mapped to a 400 response.
func BadRequest(domain, reason, message string) *Error {
return Newf(codes.InvalidArgument, domain, reason, message)
func BadRequest(reason, message string) *Error {
return Newf(400, reason, message)
}
// IsBadRequest determines if err is an error which indicates a BadRequest error.
// It supports wrapped errors.
func IsBadRequest(err error) bool {
return Code(err) == codes.InvalidArgument
return Code(err) == 400
}
// Unauthorized new Unauthorized error that is mapped to a 401 response.
func Unauthorized(domain, reason, message string) *Error {
return Newf(codes.Unauthenticated, domain, reason, message)
func Unauthorized(reason, message string) *Error {
return Newf(401, reason, message)
}
// IsUnauthorized determines if err is an error which indicates a Unauthorized error.
// It supports wrapped errors.
func IsUnauthorized(err error) bool {
return Code(err) == codes.Unauthenticated
return Code(err) == 401
}
// Forbidden new Forbidden error that is mapped to a 403 response.
func Forbidden(domain, reason, message string) *Error {
return Newf(codes.PermissionDenied, domain, reason, message)
func Forbidden(reason, message string) *Error {
return Newf(403, reason, message)
}
// IsForbidden determines if err is an error which indicates a Forbidden error.
// It supports wrapped errors.
func IsForbidden(err error) bool {
return Code(err) == codes.PermissionDenied
return Code(err) == 403
}
// NotFound new NotFound error that is mapped to a 404 response.
func NotFound(domain, reason, message string) *Error {
return Newf(codes.NotFound, domain, reason, message)
func NotFound(reason, message string) *Error {
return Newf(404, reason, message)
}
// IsNotFound determines if err is an error which indicates an NotFound error.
// It supports wrapped errors.
func IsNotFound(err error) bool {
return Code(err) == codes.NotFound
return Code(err) == 404
}
// Conflict new Conflict error that is mapped to a 409 response.
func Conflict(domain, reason, message string) *Error {
return Newf(codes.Aborted, domain, reason, message)
func Conflict(reason, message string) *Error {
return Newf(409, reason, message)
}
// IsConflict determines if err is an error which indicates a Conflict error.
// It supports wrapped errors.
func IsConflict(err error) bool {
return Code(err) == codes.Aborted
return Code(err) == 409
}
// InternalServer new InternalServer error that is mapped to a 500 response.
func InternalServer(domain, reason, message string) *Error {
return Newf(codes.Internal, domain, reason, message)
func InternalServer(reason, message string) *Error {
return Newf(500, reason, message)
}
// IsInternalServer determines if err is an error which indicates an Internal error.
// It supports wrapped errors.
func IsInternalServer(err error) bool {
return Code(err) == codes.Internal
return Code(err) == 500
}
// ServiceUnavailable new ServiceUnavailable error that is mapped to a HTTP 503 response.
func ServiceUnavailable(domain, reason, message string) *Error {
return Newf(codes.Unavailable, domain, reason, message)
func ServiceUnavailable(reason, message string) *Error {
return Newf(503, reason, message)
}
// IsServiceUnavailable determines if err is an error which indicates a Unavailable error.
// It supports wrapped errors.
func IsServiceUnavailable(err error) bool {
return Code(err) == codes.Unavailable
return Code(err) == 503
}

@ -5,13 +5,13 @@ import "testing"
func TestTypes(t *testing.T) {
var (
input = []error{
BadRequest("domain_400", "reason_400", "message_400"),
Unauthorized("domain_401", "reason_401", "message_401"),
Forbidden("domain_403", "reason_403", "message_403"),
NotFound("domain_404", "reason_404", "message_404"),
Conflict("domain_409", "reason_409", "message_409"),
InternalServer("domain_500", "reason_500", "message_500"),
ServiceUnavailable("domain_503", "reason_503", "message_503"),
BadRequest("reason_400", "message_400"),
Unauthorized("reason_401", "message_401"),
Forbidden("reason_403", "message_403"),
NotFound("reason_404", "message_404"),
Conflict("reason_409", "message_409"),
InternalServer("reason_500", "message_500"),
ServiceUnavailable("reason_503", "message_503"),
}
output = []func(error) bool{
IsBadRequest,

@ -31,7 +31,7 @@ type server struct {
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
if in.Name == "error" {
return nil, errors.BadRequest(Name, "custom_error", fmt.Sprintf("invalid argument %s", in.Name))
return nil, errors.BadRequest("custom_error", fmt.Sprintf("invalid argument %s", in.Name))
}
if in.Name == "panic" {
panic("grpc panic")

@ -40,7 +40,7 @@ func Recovery(opts ...Option) middleware.Middleware {
options := options{
logger: log.DefaultLogger,
handler: func(ctx context.Context, req, err interface{}) error {
return errors.InternalServer("global", "recovery", fmt.Sprintf("panic triggered: %v", err))
return errors.InternalServer("RECOVERY", fmt.Sprintf("panic triggered: %v", err))
},
}
for _, o := range opts {

@ -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("global", "validator", err.Error())
return nil, errors.BadRequest("VALIDATOR", err.Error())
}
}
return handler(ctx, req)

@ -262,13 +262,14 @@ func defaultErrorDecoder(ctx context.Context, res *http.Response) error {
return nil
}
defer res.Body.Close()
if data, err := ioutil.ReadAll(res.Body); err == nil {
data, err := ioutil.ReadAll(res.Body)
if err == nil {
e := new(errors.Error)
if err := codecForResponse(res).Unmarshal(data, e); err == nil {
if err = codecForResponse(res).Unmarshal(data, e); err == nil {
return e
}
}
return errors.Errorf(httputil.GRPCCodeFromStatus(res.StatusCode), "", "", "")
return errors.Errorf(res.StatusCode, errors.UnknownReason, err.Error())
}
func codecForResponse(r *http.Response) encoding.Codec {

@ -157,18 +157,18 @@ func defaultRequestDecoder(req *http.Request, v interface{}) error {
if codec := encoding.GetCodec(subtype); codec != nil {
data, err := ioutil.ReadAll(req.Body)
if err != nil {
return errors.BadRequest("global", "codec", err.Error())
return errors.BadRequest("CODEC", err.Error())
}
if err := codec.Unmarshal(data, v); err != nil {
return errors.BadRequest("global", "codec", err.Error())
return errors.BadRequest("CODEC", err.Error())
}
} else {
if err := binding.BindForm(req, v); err != nil {
return errors.BadRequest("global", "codec", err.Error())
return errors.BadRequest("CODEC", err.Error())
}
}
if err := binding.BindVars(mux.Vars(req), v); err != nil {
return errors.BadRequest("global", "codec", err.Error())
return errors.BadRequest("CODEC", err.Error())
}
return nil
}
@ -182,9 +182,9 @@ func defaultResponseEncoder(w http.ResponseWriter, r *http.Request, v interface{
}
w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
if sc, ok := v.(interface {
HTTPStatus() int
StatusCode() int
}); ok {
w.WriteHeader(sc.HTTPStatus())
w.WriteHeader(sc.StatusCode())
}
_, _ = w.Write(data)
return nil
@ -200,9 +200,11 @@ func defaultErrorEncoder(w http.ResponseWriter, r *http.Request, se error) {
}
w.Header().Set("Content-Type", httputil.ContentType(codec.Name()))
if sc, ok := se.(interface {
HTTPStatus() int
StatusCode() int
}); ok {
w.WriteHeader(sc.HTTPStatus())
w.WriteHeader(sc.StatusCode())
} else {
w.WriteHeader(http.StatusInternalServerError)
}
w.Write(body)
}

Loading…
Cancel
Save