262 lines
6.8 KiB
Go
262 lines
6.8 KiB
Go
package errors
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"service/pkg/logger"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// GRPCErrorInterceptor creates unary interceptor for error handling
|
|
func GRPCUnaryInterceptor() grpc.UnaryServerInterceptor {
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
resp, err := handler(ctx, req)
|
|
if err != nil {
|
|
return nil, HandleGRPCError(err)
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
|
|
// GRPCStreamInterceptor creates stream interceptor for error handling
|
|
func GRPCStreamInterceptor() grpc.StreamServerInterceptor {
|
|
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
err := handler(srv, ss)
|
|
if err != nil {
|
|
return HandleGRPCError(err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// HandleGRPCError converts error to gRPC error
|
|
func HandleGRPCError(err error) error {
|
|
appErr := FromError(err)
|
|
if appErr == nil {
|
|
return status.Error(codes.Internal, "Internal server error")
|
|
}
|
|
|
|
// Log gRPC error
|
|
LogGRPCError(appErr)
|
|
|
|
// Convert to gRPC status
|
|
return status.Error(appErr.GRPCCode(), appErr.GetLocalizedMessage("en"))
|
|
}
|
|
|
|
// LogGRPCError logs gRPC error
|
|
func LogGRPCError(err Error) {
|
|
log := logger.Default()
|
|
|
|
log.Error("gRPC error occurred",
|
|
logger.String("error_code", err.Code()),
|
|
logger.String("category", err.Category()),
|
|
logger.String("grpc_code", err.GRPCCode().String()),
|
|
logger.Any("metadata", err.Metadata()),
|
|
)
|
|
}
|
|
|
|
// FromGRPCError converts gRPC error to AppError
|
|
func FromGRPCError(err error) Error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
st, ok := status.FromError(err)
|
|
if !ok {
|
|
return NewWithCode(ErrCodeInternalError, err.Error())
|
|
}
|
|
|
|
// Map gRPC codes to error codes
|
|
code := mapGRPCToErrorCode(st.Code())
|
|
appErr := NewWithCode(code, st.Message())
|
|
|
|
// Add gRPC metadata
|
|
appErr = appErr.WithMetadata("grpc_code", st.Code().String())
|
|
|
|
return appErr
|
|
}
|
|
|
|
// mapGRPCToErrorCode maps gRPC codes to error codes
|
|
func mapGRPCToErrorCode(grpcCode codes.Code) string {
|
|
switch grpcCode {
|
|
case codes.OK:
|
|
return ""
|
|
case codes.Canceled:
|
|
return ErrCodeTimeout
|
|
case codes.Unknown:
|
|
return ErrCodeInternalError
|
|
case codes.InvalidArgument:
|
|
return ErrCodeValidationFailed
|
|
case codes.DeadlineExceeded:
|
|
return ErrCodeTimeout
|
|
case codes.NotFound:
|
|
return ErrCodeNotFound
|
|
case codes.AlreadyExists:
|
|
return ErrCodeDuplicateEntry
|
|
case codes.PermissionDenied:
|
|
return ErrCodeForbidden
|
|
case codes.ResourceExhausted:
|
|
return ErrCodeRateLimitExceeded
|
|
case codes.FailedPrecondition:
|
|
return ErrCodeBusinessRule
|
|
case codes.Aborted:
|
|
return ErrCodeConflict
|
|
case codes.OutOfRange:
|
|
return ErrCodeInvalidRange
|
|
case codes.Unimplemented:
|
|
return ErrCodeServiceUnavailable
|
|
case codes.Internal:
|
|
return ErrCodeInternalError
|
|
case codes.Unavailable:
|
|
return ErrCodeExternalError
|
|
case codes.DataLoss:
|
|
return ErrCodeInternalError
|
|
case codes.Unauthenticated:
|
|
return ErrCodeUnauthorized
|
|
default:
|
|
return ErrCodeInternalError
|
|
}
|
|
}
|
|
|
|
// GRPCMiddlewareConfig configures gRPC middleware
|
|
type GRPCMiddlewareConfig struct {
|
|
SkipMethods []string
|
|
LogAllErrors bool
|
|
SanitizeErrors bool
|
|
DefaultLanguage string
|
|
EnableMetrics bool
|
|
CustomCodeMapping map[codes.Code]string
|
|
}
|
|
|
|
// DefaultGRPCMiddlewareConfig returns default gRPC configuration
|
|
func DefaultGRPCMiddlewareConfig() *GRPCMiddlewareConfig {
|
|
return &GRPCMiddlewareConfig{
|
|
SkipMethods: []string{"/grpc.health.v1.Health/Check"},
|
|
LogAllErrors: true,
|
|
SanitizeErrors: true,
|
|
DefaultLanguage: "en",
|
|
EnableMetrics: true,
|
|
}
|
|
}
|
|
|
|
// GRPCUnaryInterceptorWithConfig creates interceptor with custom configuration
|
|
func GRPCUnaryInterceptorWithConfig(config *GRPCMiddlewareConfig) grpc.UnaryServerInterceptor {
|
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
// Skip specified methods
|
|
for _, method := range config.SkipMethods {
|
|
if info.FullMethod == method {
|
|
return handler(ctx, req)
|
|
}
|
|
}
|
|
|
|
resp, err := handler(ctx, req)
|
|
if err != nil {
|
|
return nil, handleGRPCErrorWithConfig(err, config)
|
|
}
|
|
return resp, nil
|
|
}
|
|
}
|
|
|
|
// handleGRPCErrorWithConfig handles gRPC error with custom configuration
|
|
func handleGRPCErrorWithConfig(err error, config *GRPCMiddlewareConfig) error {
|
|
appErr := FromError(err)
|
|
if appErr == nil {
|
|
return status.Error(codes.Internal, "Internal server error")
|
|
}
|
|
|
|
// Apply custom code mapping
|
|
if customCode, exists := config.CustomCodeMapping[appErr.GRPCCode()]; exists {
|
|
appErr = NewWithCode(customCode, appErr.Error())
|
|
}
|
|
|
|
// Sanitize error if needed
|
|
if config.SanitizeErrors {
|
|
appErr = sanitizeError(appErr).(Error)
|
|
}
|
|
|
|
// Log error
|
|
if config.LogAllErrors {
|
|
LogGRPCError(appErr)
|
|
}
|
|
|
|
return status.Error(appErr.GRPCCode(), appErr.GetLocalizedMessage(config.DefaultLanguage))
|
|
}
|
|
|
|
// ConvertToHTTPStatus converts gRPC code to HTTP status
|
|
func ConvertToHTTPStatus(grpcCode codes.Code) int {
|
|
switch grpcCode {
|
|
case codes.OK:
|
|
return http.StatusOK
|
|
case codes.Canceled:
|
|
return http.StatusRequestTimeout
|
|
case codes.Unknown:
|
|
return http.StatusInternalServerError
|
|
case codes.InvalidArgument:
|
|
return http.StatusBadRequest
|
|
case codes.DeadlineExceeded:
|
|
return http.StatusRequestTimeout
|
|
case codes.NotFound:
|
|
return http.StatusNotFound
|
|
case codes.AlreadyExists:
|
|
return http.StatusConflict
|
|
case codes.PermissionDenied:
|
|
return http.StatusForbidden
|
|
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
|
|
case codes.Unauthenticated:
|
|
return http.StatusUnauthorized
|
|
default:
|
|
return http.StatusInternalServerError
|
|
}
|
|
}
|
|
|
|
// ConvertToGRPCCode converts HTTP status to gRPC code
|
|
func ConvertToGRPCCode(httpStatus int) codes.Code {
|
|
switch httpStatus {
|
|
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.AlreadyExists
|
|
case http.StatusTooManyRequests:
|
|
return codes.ResourceExhausted
|
|
case http.StatusRequestTimeout:
|
|
return codes.DeadlineExceeded
|
|
case http.StatusNotImplemented:
|
|
return codes.Unimplemented
|
|
case http.StatusInternalServerError:
|
|
return codes.Internal
|
|
case http.StatusServiceUnavailable:
|
|
return codes.Unavailable
|
|
case http.StatusBadGateway:
|
|
return codes.Unavailable
|
|
default:
|
|
return codes.Unknown
|
|
}
|
|
}
|