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