first commit
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user