refactor: unify all service error codes
This commit is contained in:
@@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,11 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/errcode"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type authorization struct {
|
type authorization struct {
|
||||||
@@ -25,10 +25,10 @@ type authorization struct {
|
|||||||
func newAuthorization(key string, expireSeconds int64, anonymous []string) (*authorization, error) {
|
func newAuthorization(key string, expireSeconds int64, anonymous []string) (*authorization, error) {
|
||||||
key = strings.TrimSpace(key)
|
key = strings.TrimSpace(key)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, errors.New("authorization key must not be empty")
|
return nil, errcode.ErrTokenSecretKeyNotFound
|
||||||
}
|
}
|
||||||
if expireSeconds <= 0 {
|
if expireSeconds <= 0 {
|
||||||
return nil, errors.New("authorization expiration must be greater than zero")
|
return nil, errcode.ErrTokenAuthExpire
|
||||||
}
|
}
|
||||||
allowed := make(map[string]struct{}, len(anonymous))
|
allowed := make(map[string]struct{}, len(anonymous))
|
||||||
for _, item := range anonymous {
|
for _, item := range anonymous {
|
||||||
@@ -43,10 +43,10 @@ func (a *authorization) unaryInterceptor(ctx context.Context, req any, info *grp
|
|||||||
if !a.isAnonymous(info.FullMethod) {
|
if !a.isAnonymous(info.FullMethod) {
|
||||||
values := metadata.ValueFromIncomingContext(ctx, "authorization")
|
values := metadata.ValueFromIncomingContext(ctx, "authorization")
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
return nil, status.Error(codes.Unauthenticated, "authorization is required")
|
return nil, errcode.ErrHeaderAuthorization
|
||||||
}
|
}
|
||||||
if err := a.validate(values[0]); err != nil {
|
if err := a.validate(values[0]); err != nil {
|
||||||
return nil, status.Error(codes.Unauthenticated, err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handler(ctx, req)
|
return handler(ctx, req)
|
||||||
@@ -68,7 +68,7 @@ func (a *authorization) httpMiddleware(next http.Handler) http.Handler {
|
|||||||
func (a *authorization) validate(raw string) error {
|
func (a *authorization) validate(raw string) error {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
return errors.New("authorization is required")
|
return errcode.ErrHeaderAuthorization
|
||||||
}
|
}
|
||||||
claims := &jwt.RegisteredClaims{}
|
claims := &jwt.RegisteredClaims{}
|
||||||
tokenValue, err := jwt.ParseWithClaims(raw, claims, func(tokenValue *jwt.Token) (any, error) {
|
tokenValue, err := jwt.ParseWithClaims(raw, claims, func(tokenValue *jwt.Token) (any, error) {
|
||||||
@@ -78,14 +78,17 @@ func (a *authorization) validate(raw string) error {
|
|||||||
return a.key, nil
|
return a.key, nil
|
||||||
}, jwt.WithExpirationRequired(), jwt.WithIssuedAt(), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
}, jwt.WithExpirationRequired(), jwt.WithIssuedAt(), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
||||||
if err != nil || !tokenValue.Valid {
|
if err != nil || !tokenValue.Valid {
|
||||||
return errors.New("authorization is invalid or expired")
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return errcode.ErrTokenAuthExpire
|
||||||
|
}
|
||||||
|
return errcode.ErrTokenAuthParseFail
|
||||||
}
|
}
|
||||||
if claims.IssuedAt == nil {
|
if claims.IssuedAt == nil {
|
||||||
return errors.New("authorization issued-at is required")
|
return errcode.ErrTokenDataInvalid
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if claims.IssuedAt.Time.After(now) || now.Sub(claims.IssuedAt.Time) > a.expire {
|
if claims.IssuedAt.Time.After(now) || now.Sub(claims.IssuedAt.Time) > a.expire {
|
||||||
return errors.New("authorization is expired")
|
return errcode.ErrTokenAuthExpire
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,5 +122,5 @@ func normalizePath(value string) string {
|
|||||||
func writeHTTPAuthorizationError(w http.ResponseWriter, err error) {
|
func writeHTTPAuthorizationError(w http.ResponseWriter, err error) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = fmt.Fprintf(w, `{"code":%d,"message":%q}`, codes.Unauthenticated, err.Error())
|
_ = json.NewEncoder(w).Encode(newErrorResponse(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/errcode"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
@@ -30,13 +31,14 @@ func TestHTTPAuthorization(t *testing.T) {
|
|||||||
path string
|
path string
|
||||||
token string
|
token string
|
||||||
want int
|
want int
|
||||||
|
code int32
|
||||||
}{
|
}{
|
||||||
{name: "grpc gateway anonymous", path: "/passport.Login/Pwd", want: http.StatusNoContent},
|
{name: "grpc gateway anonymous", path: "/passport.Login/Pwd", want: http.StatusNoContent},
|
||||||
{name: "dynamic rpc anonymous", path: "/rpc/passport/Login/Pwd", want: http.StatusNoContent},
|
{name: "dynamic rpc anonymous", path: "/rpc/passport/Login/Pwd", want: http.StatusNoContent},
|
||||||
{name: "rest anonymous", path: "/rest/fts/ping", want: http.StatusNoContent},
|
{name: "rest anonymous", path: "/rest/fts/ping", want: http.StatusNoContent},
|
||||||
{name: "missing token", path: "/passport.Account/Get", want: http.StatusOK},
|
{name: "missing token", path: "/passport.Account/Get", want: http.StatusOK, code: int32(status.Code(errcode.ErrHeaderAuthorization))},
|
||||||
{name: "valid raw token", path: "/passport.Account/Get", token: signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusNoContent},
|
{name: "valid raw token", path: "/passport.Account/Get", token: signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusNoContent},
|
||||||
{name: "bearer rejected", path: "/passport.Account/Get", token: "Bearer " + signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusOK},
|
{name: "bearer rejected", path: "/passport.Account/Get", token: "Bearer " + signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusOK, code: int32(status.Code(errcode.ErrTokenAuthParseFail))},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
@@ -49,7 +51,7 @@ func TestHTTPAuthorization(t *testing.T) {
|
|||||||
if response.Code != test.want {
|
if response.Code != test.want {
|
||||||
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
||||||
}
|
}
|
||||||
if test.want == http.StatusOK && !strings.Contains(response.Body.String(), `"code":16`) {
|
if test.want == http.StatusOK && !strings.Contains(response.Body.String(), `"code":`+fmt.Sprint(test.code)) {
|
||||||
t.Fatalf("unexpected authorization error: %s", response.Body.String())
|
t.Fatalf("unexpected authorization error: %s", response.Body.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -76,7 +78,7 @@ func TestGRPCAuthorization(t *testing.T) {
|
|||||||
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Login/Pwd"}, handler); err != nil {
|
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Login/Pwd"}, handler); err != nil {
|
||||||
t.Fatalf("anonymous method failed: %v", err)
|
t.Fatalf("anonymous method failed: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); status.Code(err) != codes.Unauthenticated {
|
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); status.Code(err) != status.Code(errcode.ErrHeaderAuthorization) {
|
||||||
t.Fatalf("expected unauthenticated, got %v", err)
|
t.Fatalf("expected unauthenticated, got %v", err)
|
||||||
}
|
}
|
||||||
ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", signedToken(t, time.Now(), time.Now().Add(time.Hour))))
|
ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", signedToken(t, time.Now(), time.Now().Add(time.Hour))))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/errcode"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -35,10 +36,9 @@ type dynamicGateway struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dynamicRPCResponse struct {
|
type dynamicRPCResponse struct {
|
||||||
Code int32 `json:"code"`
|
Code int32 `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data json.RawMessage `json:"data,omitempty"`
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
Details []json.RawMessage `json:"details,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDynamicGateway(grpcAddr string) (*dynamicGateway, error) {
|
func newDynamicGateway(grpcAddr string) (*dynamicGateway, error) {
|
||||||
@@ -67,7 +67,7 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
|||||||
serviceShortName := strings.TrimSpace(c.Param("service"))
|
serviceShortName := strings.TrimSpace(c.Param("service"))
|
||||||
methodName := strings.TrimSpace(c.Param("method"))
|
methodName := strings.TrimSpace(c.Param("method"))
|
||||||
if moduleName == "" || serviceShortName == "" || methodName == "" {
|
if moduleName == "" || serviceShortName == "" || methodName == "" {
|
||||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "path must be /rpc/{module}/{service}/{method}"))
|
writeDynamicError(c, errcode.String(errcode.ErrInvalidArgument, "path must be /rpc/{module}/{service}/{method}"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serviceName := moduleName + "." + serviceShortName
|
serviceName := moduleName + "." + serviceShortName
|
||||||
@@ -77,23 +77,23 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if descriptor.IsStreamingClient() || descriptor.IsStreamingServer() {
|
if descriptor.IsStreamingClient() || descriptor.IsStreamingServer() {
|
||||||
writeDynamicError(c, status.Error(codes.Unimplemented, "streaming RPC methods are not supported"))
|
writeDynamicError(c, errcode.String(errcode.ErrUnimplemented, "streaming RPC methods are not supported"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxDynamicRPCBody+1))
|
body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxDynamicRPCBody+1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "read request body: "+err.Error()))
|
writeDynamicError(c, errcode.String(errcode.ErrInvalidArgument, "read request body: "+err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(body) > maxDynamicRPCBody {
|
if len(body) > maxDynamicRPCBody {
|
||||||
writeDynamicError(c, status.Error(codes.ResourceExhausted, "request body exceeds 4 MiB"))
|
writeDynamicError(c, errcode.String(errcode.ErrResourceExhausted, "request body exceeds 4 MiB"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
request := dynamicpb.NewMessage(descriptor.Input())
|
request := dynamicpb.NewMessage(descriptor.Input())
|
||||||
if err := (protojson.UnmarshalOptions{DiscardUnknown: false}).Unmarshal(body, request); err != nil {
|
if err := (protojson.UnmarshalOptions{DiscardUnknown: false}).Unmarshal(body, request); err != nil {
|
||||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "invalid protobuf JSON: "+err.Error()))
|
writeDynamicError(c, errcode.String(errcode.ErrJsonUnmarshal, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response := dynamicpb.NewMessage(descriptor.Output())
|
response := dynamicpb.NewMessage(descriptor.Output())
|
||||||
@@ -106,7 +106,7 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
|||||||
|
|
||||||
data, err := (protojson.MarshalOptions{UseProtoNames: false}).Marshal(response)
|
data, err := (protojson.MarshalOptions{UseProtoNames: false}).Marshal(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeDynamicError(c, status.Error(codes.Internal, "marshal protobuf response: "+err.Error()))
|
writeDynamicError(c, errcode.String(errcode.ErrJsonMarshal, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data})
|
c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data})
|
||||||
@@ -123,51 +123,51 @@ func (g *dynamicGateway) resolveMethod(ctx context.Context, serviceName, methodN
|
|||||||
|
|
||||||
stream, err := reflectionv1.NewServerReflectionClient(g.conn).ServerReflectionInfo(ctx)
|
stream, err := reflectionv1.NewServerReflectionClient(g.conn).ServerReflectionInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Unavailable, "open gRPC reflection stream: "+err.Error())
|
return nil, errcode.String(errcode.ErrUnavailable, "open gRPC reflection stream: "+err.Error())
|
||||||
}
|
}
|
||||||
if err := stream.Send(&reflectionv1.ServerReflectionRequest{
|
if err := stream.Send(&reflectionv1.ServerReflectionRequest{
|
||||||
MessageRequest: &reflectionv1.ServerReflectionRequest_FileContainingSymbol{FileContainingSymbol: serviceName},
|
MessageRequest: &reflectionv1.ServerReflectionRequest_FileContainingSymbol{FileContainingSymbol: serviceName},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, status.Error(codes.Unavailable, "query gRPC reflection: "+err.Error())
|
return nil, errcode.String(errcode.ErrUnavailable, "query gRPC reflection: "+err.Error())
|
||||||
}
|
}
|
||||||
reflectionResponse, err := stream.Recv()
|
reflectionResponse, err := stream.Recv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Unavailable, "read gRPC reflection response: "+err.Error())
|
return nil, errcode.String(errcode.ErrUnavailable, "read gRPC reflection response: "+err.Error())
|
||||||
}
|
}
|
||||||
fileResponse := reflectionResponse.GetFileDescriptorResponse()
|
fileResponse := reflectionResponse.GetFileDescriptorResponse()
|
||||||
if fileResponse == nil {
|
if fileResponse == nil {
|
||||||
if reflectionErr := reflectionResponse.GetErrorResponse(); reflectionErr != nil {
|
if reflectionErr := reflectionResponse.GetErrorResponse(); reflectionErr != nil {
|
||||||
return nil, status.Error(codes.Code(reflectionErr.ErrorCode), reflectionErr.ErrorMessage)
|
return nil, sdkError(status.Error(codes.Code(reflectionErr.ErrorCode), reflectionErr.ErrorMessage))
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.NotFound, "service descriptor not found")
|
return nil, errcode.String(errcode.ErrRecordNotFound, "service descriptor not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
set := &descriptorpb.FileDescriptorSet{}
|
set := &descriptorpb.FileDescriptorSet{}
|
||||||
for _, encoded := range fileResponse.FileDescriptorProto {
|
for _, encoded := range fileResponse.FileDescriptorProto {
|
||||||
file := &descriptorpb.FileDescriptorProto{}
|
file := &descriptorpb.FileDescriptorProto{}
|
||||||
if err := proto.Unmarshal(encoded, file); err != nil {
|
if err := proto.Unmarshal(encoded, file); err != nil {
|
||||||
return nil, status.Error(codes.Internal, "decode reflected descriptor: "+err.Error())
|
return nil, errcode.String(errcode.ErrInternal, "decode reflected descriptor: "+err.Error())
|
||||||
}
|
}
|
||||||
set.File = append(set.File, file)
|
set.File = append(set.File, file)
|
||||||
}
|
}
|
||||||
files, err := protodesc.NewFiles(set)
|
files, err := protodesc.NewFiles(set)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Internal, "build reflected descriptors: "+err.Error())
|
return nil, errcode.String(errcode.ErrInternal, "build reflected descriptors: "+err.Error())
|
||||||
}
|
}
|
||||||
descriptor, err := files.FindDescriptorByName(protoreflect.FullName(serviceName))
|
descriptor, err := files.FindDescriptorByName(protoreflect.FullName(serviceName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == protoregistry.NotFound {
|
if err == protoregistry.NotFound {
|
||||||
return nil, status.Error(codes.NotFound, "service not found")
|
return nil, errcode.String(errcode.ErrRecordNotFound, "service not found")
|
||||||
}
|
}
|
||||||
return nil, status.Error(codes.Internal, "resolve service descriptor: "+err.Error())
|
return nil, errcode.String(errcode.ErrInternal, "resolve service descriptor: "+err.Error())
|
||||||
}
|
}
|
||||||
service, ok := descriptor.(protoreflect.ServiceDescriptor)
|
service, ok := descriptor.(protoreflect.ServiceDescriptor)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, status.Error(codes.NotFound, "symbol is not a gRPC service")
|
return nil, errcode.String(errcode.ErrRecordNotFound, "symbol is not a gRPC service")
|
||||||
}
|
}
|
||||||
method = service.Methods().ByName(protoreflect.Name(methodName))
|
method = service.Methods().ByName(protoreflect.Name(methodName))
|
||||||
if method == nil {
|
if method == nil {
|
||||||
return nil, status.Error(codes.NotFound, "method not found")
|
return nil, errcode.String(errcode.ErrRecordNotFound, "method not found")
|
||||||
}
|
}
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
g.cache[cacheKey] = method
|
g.cache[cacheKey] = method
|
||||||
@@ -190,20 +190,5 @@ func outgoingMetadata(request *http.Request) context.Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeDynamicError(c *gin.Context, err error) {
|
func writeDynamicError(c *gin.Context, err error) {
|
||||||
grpcStatus := status.Convert(err)
|
c.JSON(http.StatusOK, newErrorResponse(sdkError(err)))
|
||||||
details := make([]json.RawMessage, 0, len(grpcStatus.Details()))
|
|
||||||
for _, detail := range grpcStatus.Details() {
|
|
||||||
message, ok := detail.(proto.Message)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if encoded, marshalErr := protojson.Marshal(message); marshalErr == nil {
|
|
||||||
details = append(details, encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, dynamicRPCResponse{
|
|
||||||
Code: int32(grpcStatus.Code()),
|
|
||||||
Message: grpcStatus.Message(),
|
|
||||||
Details: details,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
78
all/internal/server/response.go
Normal file
78
all/internal/server/response.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.apinb.com/bsm-sdk/core/errcode"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
Code int32 `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Details any `json:"details"`
|
||||||
|
Timeseq int64 `json:"timeseq"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func sdkError(err error) error {
|
||||||
|
code := status.Code(err)
|
||||||
|
// SDK business errors already use custom codes and must pass through.
|
||||||
|
if code > codes.Unauthenticated {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
message := status.Convert(err).Message()
|
||||||
|
var target error
|
||||||
|
switch code {
|
||||||
|
case codes.Canceled:
|
||||||
|
target = errcode.ErrCanceled
|
||||||
|
case codes.InvalidArgument:
|
||||||
|
target = errcode.ErrInvalidArgument
|
||||||
|
case codes.DeadlineExceeded:
|
||||||
|
target = errcode.ErrDeadlineExceeded
|
||||||
|
case codes.NotFound:
|
||||||
|
target = errcode.ErrRecordNotFound
|
||||||
|
case codes.AlreadyExists:
|
||||||
|
target = errcode.ErrAlreadyExists
|
||||||
|
case codes.PermissionDenied:
|
||||||
|
target = errcode.ErrPermissionDenied
|
||||||
|
case codes.ResourceExhausted:
|
||||||
|
target = errcode.ErrResourceExhausted
|
||||||
|
case codes.FailedPrecondition:
|
||||||
|
target = errcode.ErrFailedPrecondition
|
||||||
|
case codes.Aborted:
|
||||||
|
target = errcode.ErrAborted
|
||||||
|
case codes.OutOfRange:
|
||||||
|
target = errcode.ErrOutOfRange
|
||||||
|
case codes.Unimplemented:
|
||||||
|
target = errcode.ErrUnimplemented
|
||||||
|
case codes.Unavailable:
|
||||||
|
target = errcode.ErrUnavailable
|
||||||
|
case codes.DataLoss:
|
||||||
|
target = errcode.ErrDataLoss
|
||||||
|
case codes.Unauthenticated:
|
||||||
|
target = errcode.ErrUnauthenticated
|
||||||
|
case codes.Internal:
|
||||||
|
target = errcode.ErrInternal
|
||||||
|
default:
|
||||||
|
target = errcode.ErrUnknown
|
||||||
|
}
|
||||||
|
if message == "" || message == status.Convert(target).Message() {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
return errcode.String(target, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrorResponse(err error) errorResponse {
|
||||||
|
response := errorResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: err.Error(),
|
||||||
|
Details: "",
|
||||||
|
Timeseq: time.Now().UnixMilli(),
|
||||||
|
}
|
||||||
|
if grpcStatus, ok := status.FromError(err); ok {
|
||||||
|
response.Code = int32(grpcStatus.Code())
|
||||||
|
response.Message = grpcStatus.Message()
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user