package errors import ( "errors" "fmt" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type ErrorType int const ( BusinessError ErrorType = iota InternalErrorType ) type AppError struct { Code string Message string Type ErrorType Err error } func (e *AppError) Error() string { if e.Err != nil { return fmt.Sprintf("%s: %s (%v)", e.Code, e.Message, e.Err) } return fmt.Sprintf("%s: %s", e.Code, e.Message) } func (e *AppError) Unwrap() error { return e.Err } func NewBusinessError(code, message string) *AppError { return &AppError{ Code: code, Message: message, Type: BusinessError, } } func NewInternalError(code, message string, err error) *AppError { return &AppError{ Code: code, Message: message, Type: InternalErrorType, Err: err, } } func IsBusinessError(err error, code string) bool { var appErr *AppError if !errors.As(err, &appErr) { return false } return appErr.Type == BusinessError && appErr.Code == code } func ToGRPCError(err error, zapLogger *zap.Logger, method string) error { if err == nil { return nil } var appErr *AppError ok := errors.As(err, &appErr) if !ok { if zapLogger != nil { zapLogger.Error("gRPC handler error: unknown error type", zap.String("method", method), zap.Error(err), ) } return status.Error(codes.Internal, "internal server error") } if appErr.Type == InternalErrorType { if zapLogger != nil { zapLogger.Error("gRPC handler error: internal error", zap.String("method", method), zap.String("code", appErr.Code), zap.String("message", appErr.Message), zap.Error(appErr.Err), ) } return status.Error(codes.Internal, "internal server error") } switch appErr.Code { case AuthInvalidCredentials, AuthMissing, AuthInvalidToken, RefreshInvalid: return status.Error(codes.Unauthenticated, appErr.Message) case PermissionDenied: return status.Error(codes.PermissionDenied, appErr.Message) case InviteLimitReached: return status.Error(codes.ResourceExhausted, appErr.Message) case InsufficientBalance, InviteInvalidOrExpired: return status.Error(codes.FailedPrecondition, appErr.Message) case EmailAlreadyExists: return status.Error(codes.AlreadyExists, appErr.Message) case UserNotFound, RequestNotFound: return status.Error(codes.NotFound, appErr.Message) default: return status.Error(codes.Unknown, appErr.Message) } }