diff --git a/errors/errors.go b/errors/errors.go index 351830cb4..8b2e7f55d 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -4,7 +4,8 @@ import ( "errors" "fmt" - "github.com/go-kratos/kratos/v2/internal/httputil" + 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" @@ -27,7 +28,7 @@ func (e *Error) Error() string { // GRPCStatus returns the Status represented by se. func (e *Error) GRPCStatus() *status.Status { - s, _ := status.New(httputil.GRPCCodeFromStatus(int(e.Code)), e.Message). + s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). WithDetails(&errdetails.ErrorInfo{ Reason: e.Reason, Metadata: e.Metadata, @@ -105,7 +106,7 @@ func FromError(err error) *Error { switch d := detail.(type) { case *errdetails.ErrorInfo: return New( - httputil.StatusFromGRPCCode(gs.Code()), + httpstatus.FromGRPCCode(gs.Code()), d.Reason, gs.Message(), ).WithMetadata(d.Metadata) diff --git a/internal/httputil/http.go b/internal/httputil/http.go index 1d63c363e..81adf6baa 100644 --- a/internal/httputil/http.go +++ b/internal/httputil/http.go @@ -1,19 +1,11 @@ package httputil import ( - "net/http" "strings" - - "google.golang.org/grpc/codes" ) const ( baseContentType = "application" - - // StatusClientClosed is non-standard http status code, - // which defined by nginx. - // https://httpstatus.in/499/ - StatusClientClosed = 499 ) // ContentType returns the content-type with base prefix. @@ -40,77 +32,3 @@ func ContentSubtype(contentType string) string { } return contentType[left+1 : right] } - -// GRPCCodeFromStatus converts a HTTP error code into the corresponding gRPC response status. -// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto -func GRPCCodeFromStatus(code int) codes.Code { - switch code { - case http.StatusOK: - return codes.OK - case http.StatusBadRequest: - return codes.InvalidArgument - case http.StatusUnauthorized: - return codes.Unauthenticated - case http.StatusForbidden: - return codes.PermissionDenied - case http.StatusNotFound: - return codes.NotFound - case http.StatusConflict: - return codes.Aborted - case http.StatusTooManyRequests: - return codes.ResourceExhausted - case http.StatusInternalServerError: - return codes.Internal - case http.StatusNotImplemented: - return codes.Unimplemented - case http.StatusServiceUnavailable: - return codes.Unavailable - case http.StatusGatewayTimeout: - return codes.DeadlineExceeded - case StatusClientClosed: - return codes.Canceled - } - return codes.Unknown -} - -// StatusFromGRPCCode converts a gRPC error code into the corresponding HTTP response status. -// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto -func StatusFromGRPCCode(code codes.Code) int { - switch code { - case codes.OK: - return http.StatusOK - case codes.Canceled: - return StatusClientClosed - case codes.Unknown: - return http.StatusInternalServerError - case codes.InvalidArgument: - return http.StatusBadRequest - case codes.DeadlineExceeded: - return http.StatusGatewayTimeout - case codes.NotFound: - return http.StatusNotFound - case codes.AlreadyExists: - return http.StatusConflict - case codes.PermissionDenied: - return http.StatusForbidden - case codes.Unauthenticated: - return http.StatusUnauthorized - case codes.ResourceExhausted: - return http.StatusTooManyRequests - case codes.FailedPrecondition: - return http.StatusBadRequest - case codes.Aborted: - return http.StatusConflict - case codes.OutOfRange: - return http.StatusBadRequest - case codes.Unimplemented: - return http.StatusNotImplemented - case codes.Internal: - return http.StatusInternalServerError - case codes.Unavailable: - return http.StatusServiceUnavailable - case codes.DataLoss: - return http.StatusInternalServerError - } - return http.StatusInternalServerError -} diff --git a/internal/httputil/http_test.go b/internal/httputil/http_test.go index fcf9a0203..da252a96c 100644 --- a/internal/httputil/http_test.go +++ b/internal/httputil/http_test.go @@ -1,10 +1,7 @@ package httputil import ( - "net/http" "testing" - - "google.golang.org/grpc/codes" ) func TestContentSubtype(t *testing.T) { @@ -31,69 +28,6 @@ func TestContentSubtype(t *testing.T) { } } -func TestGRPCCodeFromStatus(t *testing.T) { - tests := []struct { - name string - code int - want codes.Code - }{ - {"http.StatusOK", http.StatusOK, codes.OK}, - {"http.StatusBadRequest", http.StatusBadRequest, codes.InvalidArgument}, - {"http.StatusUnauthorized", http.StatusUnauthorized, codes.Unauthenticated}, - {"http.StatusForbidden", http.StatusForbidden, codes.PermissionDenied}, - {"http.StatusNotFound", http.StatusNotFound, codes.NotFound}, - {"http.StatusConflict", http.StatusConflict, codes.Aborted}, - {"http.StatusTooManyRequests", http.StatusTooManyRequests, codes.ResourceExhausted}, - {"http.StatusInternalServerError", http.StatusInternalServerError, codes.Internal}, - {"http.StatusNotImplemented", http.StatusNotImplemented, codes.Unimplemented}, - {"http.StatusServiceUnavailable", http.StatusServiceUnavailable, codes.Unavailable}, - {"http.StatusGatewayTimeout", http.StatusGatewayTimeout, codes.DeadlineExceeded}, - {"StatusClientClosed", StatusClientClosed, codes.Canceled}, - {"else", 100000, codes.Unknown}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GRPCCodeFromStatus(tt.code); got != tt.want { - t.Errorf("GRPCCodeFromStatus() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestStatusFromGRPCCode(t *testing.T) { - tests := []struct { - name string - code codes.Code - want int - }{ - {"codes.OK", codes.OK, http.StatusOK}, - {"codes.Canceled", codes.Canceled, StatusClientClosed}, - {"codes.Unknown", codes.Unknown, http.StatusInternalServerError}, - {"codes.InvalidArgument", codes.InvalidArgument, http.StatusBadRequest}, - {"codes.DeadlineExceeded", codes.DeadlineExceeded, http.StatusGatewayTimeout}, - {"codes.NotFound", codes.NotFound, http.StatusNotFound}, - {"codes.AlreadyExists", codes.AlreadyExists, http.StatusConflict}, - {"codes.PermissionDenied", codes.PermissionDenied, http.StatusForbidden}, - {"codes.Unauthenticated", codes.Unauthenticated, http.StatusUnauthorized}, - {"codes.ResourceExhausted", codes.ResourceExhausted, http.StatusTooManyRequests}, - {"codes.FailedPrecondition", codes.FailedPrecondition, http.StatusBadRequest}, - {"codes.Aborted", codes.Aborted, http.StatusConflict}, - {"codes.OutOfRange", codes.OutOfRange, http.StatusBadRequest}, - {"codes.Unimplemented", codes.Unimplemented, http.StatusNotImplemented}, - {"codes.Internal", codes.Internal, http.StatusInternalServerError}, - {"codes.Unavailable", codes.Unavailable, http.StatusServiceUnavailable}, - {"codes.DataLoss", codes.DataLoss, http.StatusInternalServerError}, - {"else", codes.Code(10000), http.StatusInternalServerError}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := StatusFromGRPCCode(tt.code); got != tt.want { - t.Errorf("StatusFromGRPCCode() = %v, want %v", got, tt.want) - } - }) - } -} - func TestContentType(t *testing.T) { tests := []struct { name string diff --git a/transport/http/status/status.go b/transport/http/status/status.go new file mode 100644 index 000000000..d2240ec74 --- /dev/null +++ b/transport/http/status/status.go @@ -0,0 +1,110 @@ +package status + +import ( + "net/http" + + "google.golang.org/grpc/codes" +) + +const ( + // ClientClosed is non-standard http status code, + // which defined by nginx. + // https://httpstatus.in/499/ + ClientClosed = 499 +) + +type Converter interface { + // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. + ToGRPCCode(code int) codes.Code + + // FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. + FromGRPCCode(code codes.Code) int +} + +type statusConverter struct{} + +var DefaultConverter Converter = statusConverter{} + +// ToGRPCCode converts a HTTP error code into the corresponding gRPC response status. +// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto +func (c statusConverter) ToGRPCCode(code int) codes.Code { + switch code { + case http.StatusOK: + return codes.OK + case http.StatusBadRequest: + return codes.InvalidArgument + case http.StatusUnauthorized: + return codes.Unauthenticated + case http.StatusForbidden: + return codes.PermissionDenied + case http.StatusNotFound: + return codes.NotFound + case http.StatusConflict: + return codes.Aborted + case http.StatusTooManyRequests: + return codes.ResourceExhausted + case http.StatusInternalServerError: + return codes.Internal + case http.StatusNotImplemented: + return codes.Unimplemented + case http.StatusServiceUnavailable: + return codes.Unavailable + case http.StatusGatewayTimeout: + return codes.DeadlineExceeded + case ClientClosed: + return codes.Canceled + } + return codes.Unknown +} + +// FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. +// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto +func (c statusConverter) FromGRPCCode(code codes.Code) int { + switch code { + case codes.OK: + return http.StatusOK + case codes.Canceled: + return ClientClosed + case codes.Unknown: + return http.StatusInternalServerError + case codes.InvalidArgument: + return http.StatusBadRequest + case codes.DeadlineExceeded: + return http.StatusGatewayTimeout + case codes.NotFound: + return http.StatusNotFound + case codes.AlreadyExists: + return http.StatusConflict + case codes.PermissionDenied: + return http.StatusForbidden + case codes.Unauthenticated: + return http.StatusUnauthorized + case codes.ResourceExhausted: + return http.StatusTooManyRequests + case codes.FailedPrecondition: + return http.StatusBadRequest + case codes.Aborted: + return http.StatusConflict + case codes.OutOfRange: + return http.StatusBadRequest + case codes.Unimplemented: + return http.StatusNotImplemented + case codes.Internal: + return http.StatusInternalServerError + case codes.Unavailable: + return http.StatusServiceUnavailable + case codes.DataLoss: + return http.StatusInternalServerError + } + return http.StatusInternalServerError +} + +// ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. +func ToGRPCCode(code int) codes.Code { + return DefaultConverter.ToGRPCCode(code) +} + +// FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. +func FromGRPCCode(code codes.Code) int { + return DefaultConverter.FromGRPCCode(code) +} diff --git a/transport/http/status/status_test.go b/transport/http/status/status_test.go new file mode 100644 index 000000000..2e2c706b6 --- /dev/null +++ b/transport/http/status/status_test.go @@ -0,0 +1,71 @@ +package status + +import ( + "net/http" + "testing" + + "google.golang.org/grpc/codes" +) + +func TestToGRPCCode(t *testing.T) { + tests := []struct { + name string + code int + want codes.Code + }{ + {"http.StatusOK", http.StatusOK, codes.OK}, + {"http.StatusBadRequest", http.StatusBadRequest, codes.InvalidArgument}, + {"http.StatusUnauthorized", http.StatusUnauthorized, codes.Unauthenticated}, + {"http.StatusForbidden", http.StatusForbidden, codes.PermissionDenied}, + {"http.StatusNotFound", http.StatusNotFound, codes.NotFound}, + {"http.StatusConflict", http.StatusConflict, codes.Aborted}, + {"http.StatusTooManyRequests", http.StatusTooManyRequests, codes.ResourceExhausted}, + {"http.StatusInternalServerError", http.StatusInternalServerError, codes.Internal}, + {"http.StatusNotImplemented", http.StatusNotImplemented, codes.Unimplemented}, + {"http.StatusServiceUnavailable", http.StatusServiceUnavailable, codes.Unavailable}, + {"http.StatusGatewayTimeout", http.StatusGatewayTimeout, codes.DeadlineExceeded}, + {"StatusClientClosed", ClientClosed, codes.Canceled}, + {"else", 100000, codes.Unknown}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToGRPCCode(tt.code); got != tt.want { + t.Errorf("GRPCCodeFromStatus() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFromGRPCCode(t *testing.T) { + tests := []struct { + name string + code codes.Code + want int + }{ + {"codes.OK", codes.OK, http.StatusOK}, + {"codes.Canceled", codes.Canceled, ClientClosed}, + {"codes.Unknown", codes.Unknown, http.StatusInternalServerError}, + {"codes.InvalidArgument", codes.InvalidArgument, http.StatusBadRequest}, + {"codes.DeadlineExceeded", codes.DeadlineExceeded, http.StatusGatewayTimeout}, + {"codes.NotFound", codes.NotFound, http.StatusNotFound}, + {"codes.AlreadyExists", codes.AlreadyExists, http.StatusConflict}, + {"codes.PermissionDenied", codes.PermissionDenied, http.StatusForbidden}, + {"codes.Unauthenticated", codes.Unauthenticated, http.StatusUnauthorized}, + {"codes.ResourceExhausted", codes.ResourceExhausted, http.StatusTooManyRequests}, + {"codes.FailedPrecondition", codes.FailedPrecondition, http.StatusBadRequest}, + {"codes.Aborted", codes.Aborted, http.StatusConflict}, + {"codes.OutOfRange", codes.OutOfRange, http.StatusBadRequest}, + {"codes.Unimplemented", codes.Unimplemented, http.StatusNotImplemented}, + {"codes.Internal", codes.Internal, http.StatusInternalServerError}, + {"codes.Unavailable", codes.Unavailable, http.StatusServiceUnavailable}, + {"codes.DataLoss", codes.DataLoss, http.StatusInternalServerError}, + {"else", codes.Code(10000), http.StatusInternalServerError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FromGRPCCode(tt.code); got != tt.want { + t.Errorf("StatusFromGRPCCode() = %v, want %v", got, tt.want) + } + }) + } +}