Files
2026-04-14 01:21:54 +00:00

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
}
}