import { HttpStatus } from '@nestjs/common'; import { NormalizedErrorDto } from '../dto/normalized-error.dto'; export interface ErrorCatalogEntry { status: HttpStatus; message: string; messageFa: string; } export const ERROR_CATALOG = { PROVIDER_TIMEOUT: { status: HttpStatus.BAD_GATEWAY, message: 'Provider request timed out', messageFa: 'درخواست به سرویس‌دهنده زمان‌بر شد', }, PROVIDER_ERROR: { status: HttpStatus.BAD_GATEWAY, message: 'Provider request failed', messageFa: 'خطا در سرویس‌دهنده', }, PROVIDER_NETWORK_ERROR: { status: HttpStatus.BAD_GATEWAY, message: 'Provider network request failed', messageFa: 'ارتباط با سرویس‌دهنده برقرار نشد', }, PROVIDER_BAD_RESPONSE: { status: HttpStatus.BAD_GATEWAY, message: 'Provider returned an invalid response', messageFa: 'پاسخ سرویس‌دهنده معتبر نیست', }, PROVIDER_AUTH_FAILED: { status: HttpStatus.BAD_GATEWAY, message: 'Provider authentication failed', messageFa: 'احراز هویت سرویس‌دهنده ناموفق بود', }, PROVIDER_REJECTED: { status: HttpStatus.BAD_GATEWAY, message: 'Provider rejected the request', messageFa: 'سرویس‌دهنده درخواست را رد کرد', }, NO_PROVIDERS: { status: HttpStatus.UNPROCESSABLE_ENTITY, message: 'No enabled providers configured for this inquiry', messageFa: 'سرویس‌دهنده‌ای برای این استعلام فعال نیست', }, ALL_PROVIDERS_FAILED: { status: HttpStatus.UNPROCESSABLE_ENTITY, message: 'All providers failed', messageFa: 'تمامی سرویس‌دهنده‌ها با خطا مواجه شدند', }, UNKNOWN_ERROR: { status: HttpStatus.INTERNAL_SERVER_ERROR, message: 'Unknown provider error', messageFa: 'خطای ناشناخته در سرویس‌دهنده', }, UNSUPPORTED_INQUIRY: { status: HttpStatus.UNPROCESSABLE_ENTITY, message: 'Provider does not support this inquiry type', messageFa: 'سرویس‌دهنده از این نوع استعلام پشتیبانی نمی‌کند', }, API_ERROR: { status: HttpStatus.BAD_GATEWAY, message: 'Provider API request failed', messageFa: 'خطا در فراخوانی API سرویس‌دهنده', }, HTTP_ERROR: { status: HttpStatus.INTERNAL_SERVER_ERROR, message: 'HTTP request failed', messageFa: 'درخواست ناموفق بود', }, INTERNAL_ERROR: { status: HttpStatus.INTERNAL_SERVER_ERROR, message: 'Internal server error', messageFa: 'خطای داخلی سرور', }, NETWORK_ERROR: { status: HttpStatus.BAD_GATEWAY, message: 'Network request failed', messageFa: 'خطا در اتصال به شبکه', }, INQUIRY_FAILED: { status: HttpStatus.UNPROCESSABLE_ENTITY, message: 'Inquiry failed', messageFa: 'استعلام با خطا مواجه شد', }, INQUIRY_NO_MATCH: { status: HttpStatus.OK, message: 'Inquiry returned no matching result', messageFa: 'نتیجه‌ای مطابق با اطلاعات وارد شده یافت نشد', }, RECORD_NOT_FOUND: { status: HttpStatus.OK, message: 'Record not found', messageFa: 'رکوردی یافت نشد', }, SAYAH_ERROR: { status: HttpStatus.BAD_GATEWAY, message: 'Sayah inquiry failed', messageFa: 'خطا در سرویس سیاح', }, SHEBA_MISMATCH: { status: HttpStatus.OK, message: 'Submitted sheba does not match inquiry result', messageFa: 'اطلاعات شبای وارد شده با نتیجه استعلام مطابقت ندارد', }, VALIDATION_ERROR: { status: HttpStatus.BAD_REQUEST, message: 'Request validation failed', messageFa: 'اطلاعات وارد شده معتبر نیست', }, INVALID_CREDENTIALS: { status: HttpStatus.UNAUTHORIZED, message: 'Invalid credentials', messageFa: 'نام کاربری یا رمز عبور صحیح نیست', }, ACCOUNT_INACTIVE: { status: HttpStatus.FORBIDDEN, message: 'Account is inactive', messageFa: 'حساب کاربری غیرفعال است', }, ACCOUNT_BLOCKED: { status: HttpStatus.FORBIDDEN, message: 'Account is blocked', messageFa: 'حساب کاربری مسدود شده است', }, INVALID_REFRESH_TOKEN: { status: HttpStatus.UNAUTHORIZED, message: 'Invalid refresh token', messageFa: 'توکن نوسازی نامعتبر است', }, API_KEY_NOT_CONFIGURED: { status: HttpStatus.UNAUTHORIZED, message: 'API key authentication is not configured', messageFa: 'احراز هویت با کلید API پیکربندی نشده است', }, INVALID_API_KEY: { status: HttpStatus.UNAUTHORIZED, message: 'Invalid or missing API key', messageFa: 'کلید API نامعتبر است یا ارسال نشده است', }, UNAUTHORIZED: { status: HttpStatus.UNAUTHORIZED, message: 'Unauthorized', messageFa: 'احراز هویت انجام نشده است', }, AUTHENTICATION_REQUIRED: { status: HttpStatus.FORBIDDEN, message: 'Authentication required', messageFa: 'احراز هویت الزامی است', }, INSUFFICIENT_ROLE: { status: HttpStatus.FORBIDDEN, message: 'Insufficient role permissions', messageFa: 'سطح دسترسی کافی نیست', }, INSUFFICIENT_USER_MANAGEMENT_PERMISSION: { status: HttpStatus.FORBIDDEN, message: 'Insufficient permissions to manage users', messageFa: 'دسترسی کافی برای مدیریت کاربران وجود ندارد', }, SUPER_ADMIN_ROLE_REQUIRED: { status: HttpStatus.FORBIDDEN, message: 'Only SUPER_ADMIN can assign SUPER_ADMIN role', messageFa: 'فقط مدیر ارشد می‌تواند نقش مدیر ارشد را اختصاص دهد', }, USER_NOT_FOUND: { status: HttpStatus.NOT_FOUND, message: 'User not found', messageFa: 'کاربر یافت نشد', }, USERNAME_OR_EMAIL_EXISTS: { status: HttpStatus.CONFLICT, message: 'Username or email already exists', messageFa: 'نام کاربری یا ایمیل قبلا ثبت شده است', }, EMAIL_ALREADY_IN_USE: { status: HttpStatus.CONFLICT, message: 'Email already in use', messageFa: 'این ایمیل قبلا استفاده شده است', }, PASSWORD_TOO_SHORT: { status: HttpStatus.CONFLICT, message: 'Password must be at least 8 characters', messageFa: 'رمز عبور باید حداقل ۸ کاراکتر باشد', }, CANNOT_BLOCK_SELF: { status: HttpStatus.BAD_REQUEST, message: 'Cannot block your own account', messageFa: 'امکان مسدود کردن حساب کاربری خودتان وجود ندارد', }, RATE_LIMIT_EXCEEDED: { status: HttpStatus.TOO_MANY_REQUESTS, message: 'User rate limit exceeded', messageFa: 'تعداد درخواست‌های مجاز کاربر به پایان رسیده است', }, SHAHKAR_NO_MATCH: { status: HttpStatus.OK, message: 'No match found between the provided national code and mobile number', messageFa: 'تطبیقی بین کد ملی و شماره موبایل وارد شده یافت نشد', }, CAR_POLICY_NOT_FOUND: { status: HttpStatus.OK, message: 'No policy record found', messageFa: 'سوابق بیمه‌نامه خودرو یافت نشد', }, REAL_ESTATE_NOT_FOUND: { status: HttpStatus.OK, message: 'No property ownership found for the provided national code and postal code', messageFa: 'ملکی برای کد ملی و کد پستی وارد شده یافت نشد', }, REQUEST_FAILED: { status: HttpStatus.INTERNAL_SERVER_ERROR, message: 'Request failed', messageFa: 'درخواست ناموفق بود', }, } as const satisfies Record; export type AppErrorCode = keyof typeof ERROR_CATALOG; export function isAppErrorCode(code: string): code is AppErrorCode { return code in ERROR_CATALOG; } export function getErrorStatus(code: AppErrorCode): HttpStatus { return ERROR_CATALOG[code].status; } export function buildNormalizedError( code: AppErrorCode, overrides: Partial> = {}, ): NormalizedErrorDto { const entry = ERROR_CATALOG[code]; return { code, message: overrides.message ?? entry.message, messageFa: overrides.messageFa ?? entry.messageFa, providerMessage: overrides.providerMessage, providerCode: overrides.providerCode, providerTrackingCode: overrides.providerTrackingCode, details: overrides.details, conflict: overrides.conflict, }; } /** * Resolve a Persian error message for a given code. * * @param code The NormalizedErrorDto.code value * @param params Optional interpolation params (e.g. { provider, inquiryType }) * @returns The Persian message or undefined when no translation exists */ export function resolveErrorMessageFa( code: string, params?: Record, ): string | undefined { const entry = ERROR_CATALOG[code as AppErrorCode]; if (!entry) { return undefined; } void params; return entry.messageFa; } /** * Map well-known provider error messages / codes that originate * from upstream providers (returned as providerMessage) to their * Persian equivalents when the provider itself does not return a * Persian message. */ export const PROVIDER_MESSAGE_TRANSLATIONS: Record = { 'No match found between the provided national code and mobile number': 'تطبیقی بین کد ملی و شماره موبایل وارد شده یافت نشد', 'Shahkar inquiry failed': 'خطا در استعلام شاهکار', 'No policy record found': 'سوابق بیمه‌نامه خودرو یافت نشد', 'Record not found': 'رکوردی یافت نشد', 'Request failed': 'درخواست ناموفق بود', 'No property ownership found for the provided national code and postal code': 'ملکی برای کد ملی و کد پستی وارد شده یافت نشد', 'Inquiry returned no matching result': 'نتیجه‌ای مطابق با اطلاعات وارد شده یافت نشد', 'Insufficient role permissions': 'سطح دسترسی کافی نیست', 'Invalid credentials': 'نام کاربری یا رمز عبور صحیح نیست', 'Account is inactive': 'حساب کاربری غیرفعال است', 'Account is blocked': 'حساب کاربری مسدود شده است', 'Invalid refresh token': 'توکن نوسازی نامعتبر است', }; export const PROVIDER_PUBLIC_MESSAGE_TRANSLATIONS: Record< string, { message: string; messageFa: string } > = { 'کد ملی اشتباه است': { message: 'Invalid national code', messageFa: 'کد ملی اشتباه است', }, 'کدملی اشتباه است': { message: 'Invalid national code', messageFa: 'کد ملی اشتباه است', }, }; export const MESSAGE_CODE_MAP: Record = { 'Invalid credentials': 'INVALID_CREDENTIALS', 'Account is inactive': 'ACCOUNT_INACTIVE', 'Account is blocked': 'ACCOUNT_BLOCKED', 'Invalid refresh token': 'INVALID_REFRESH_TOKEN', 'API key authentication is not configured': 'API_KEY_NOT_CONFIGURED', 'Invalid or missing API key': 'INVALID_API_KEY', Unauthorized: 'UNAUTHORIZED', 'Authentication required': 'AUTHENTICATION_REQUIRED', 'Insufficient role permissions': 'INSUFFICIENT_ROLE', 'Insufficient permissions to manage users': 'INSUFFICIENT_USER_MANAGEMENT_PERMISSION', 'Only SUPER_ADMIN can assign SUPER_ADMIN role': 'SUPER_ADMIN_ROLE_REQUIRED', 'User not found': 'USER_NOT_FOUND', 'Username or email already exists': 'USERNAME_OR_EMAIL_EXISTS', 'Email already in use': 'EMAIL_ALREADY_IN_USE', 'Password must be at least 8 characters': 'PASSWORD_TOO_SHORT', 'Cannot block your own account': 'CANNOT_BLOCK_SELF', 'User rate limit exceeded': 'RATE_LIMIT_EXCEEDED', }; export function resolveErrorCodeFromMessage(message: string): AppErrorCode | undefined { return MESSAGE_CODE_MAP[message.trim()]; } /** * Try to translate an upstream provider message to Persian. * * @param providerMessage The English message returned by the provider * @returns The Persian equivalent or undefined when no mapping exists */ export function translateProviderMessage(providerMessage: string): string | undefined { return PROVIDER_MESSAGE_TRANSLATIONS[providerMessage.trim()]; } export function resolveProviderPublicMessage( providerMessage: string | undefined, fallbackMessage = 'Provider request failed', ): { message: string; messageFa: string } { const trimmed = providerMessage?.trim(); if (!trimmed) { return { message: fallbackMessage, messageFa: ERROR_CATALOG.PROVIDER_ERROR.messageFa, }; } const direct = PROVIDER_PUBLIC_MESSAGE_TRANSLATIONS[trimmed]; if (direct) { return direct; } const messageFa = translateProviderMessage(trimmed); if (messageFa) { return { message: trimmed, messageFa, }; } if (/[آ-ی]/.test(trimmed)) { return { message: fallbackMessage, messageFa: trimmed, }; } return { message: trimmed, messageFa: ERROR_CATALOG.PROVIDER_ERROR.messageFa, }; }