feat: Support custom status code conversion from HTTP and gRPC. (#1410)
* feat: Support custom status code conversion from HTTP and gRPC. Co-authored-by: Letian Yi <yiletian@webull.com>pull/1425/head
parent
1ac50be94c
commit
fa54a1dd3a
@ -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) |
||||||
|
} |
@ -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) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue