import { ValidationError } from 'class-validator'; import { NormalizedErrorDto } from '../dto/normalized-error.dto'; import { buildNormalizedError } from '../constants/error-messages'; export interface ValidationFieldError { field: string; constraints: string[]; } const FIELD_LABELS_FA: Record = { nationalCode: 'کد ملی', postalCode: 'کد پستی', birthDate: 'تاریخ تولد', mobileNo: 'شماره موبایل', sheba: 'شماره شبا', chassisNo: 'شماره شاسی', plk1: 'بخش اول پلاک', plk2: 'حرف پلاک', plk3: 'بخش سوم پلاک', plksrl: 'سری پلاک', email: 'ایمیل', password: 'رمز عبور', newPassword: 'رمز عبور جدید', role: 'نقش کاربر', clientType: 'نوع کاربر', username: 'نام کاربری', fullName: 'نام کامل', }; const CONSTRAINT_PRIORITY = [ 'isNotEmpty', 'matches', 'isLength', 'length', 'minLength', 'maxLength', 'isEmail', 'isEnum', 'isIn', 'isString', 'isInt', 'isBoolean', 'isArray', ]; function pickConstraint(constraints: Record): string { for (const key of CONSTRAINT_PRIORITY) { if (constraints[key]) { return constraints[key]; } } return Object.values(constraints)[0] ?? 'Invalid value'; } function fieldLabelFa(field: string): string { return FIELD_LABELS_FA[field] ?? field; } function translateValidationConstraint(field: string, constraint: string): string { const label = fieldLabelFa(field); if (constraint.includes('must be exactly 10 digits')) { return `${label} باید دقیقا ۱۰ رقم باشد`; } if (constraint.includes('birthDate must be in YYYY-MM-DD format')) { return 'تاریخ تولد باید با فرمت YYYY-MM-DD باشد'; } if (constraint.includes('birthDate must be in YYYY-MM-DD or YYYYMMDD format')) { return 'تاریخ تولد باید با فرمت YYYY-MM-DD یا YYYYMMDD باشد'; } if (constraint.includes('mobileNo must be an Iranian mobile number')) { return 'شماره موبایل باید معتبر و با 09 شروع شود'; } if (constraint.includes('sheba must start with IR and contain 24 digits')) { return 'شماره شبا باید با IR شروع شود و ۲۴ رقم داشته باشد'; } const exactDigits = constraint.match(/must be exactly (\d+) digits/); if (exactDigits?.[1]) { return `${label} باید دقیقا ${exactDigits[1]} رقم باشد`; } const minLength = constraint.match(/must be longer than or equal to (\d+) characters/); if (minLength?.[1]) { return `${label} باید حداقل ${minLength[1]} کاراکتر باشد`; } if (constraint.includes('must be an email')) { return 'ایمیل باید معتبر باشد'; } if (constraint.includes('must be one of the following values')) { return `${label} مقدار معتبری ندارد`; } if (constraint.includes('should not be empty')) { return `${label} الزامی است`; } if (constraint.includes('must be a string')) { return `${label} باید متن باشد`; } if (constraint.includes('must be an integer number')) { return `${label} باید عدد صحیح باشد`; } if (constraint.includes('must be a boolean value')) { return `${label} باید مقدار درست یا نادرست باشد`; } if (constraint.includes('must be an array')) { return `${label} باید آرایه باشد`; } return `${label} معتبر نیست`; } export function flattenValidationErrors( errors: ValidationError[], parentPath = '', ): ValidationFieldError[] { const result: ValidationFieldError[] = []; for (const error of errors) { const field = parentPath ? `${parentPath}.${error.property}` : error.property; if (error.constraints) { result.push({ field, constraints: [pickConstraint(error.constraints)], }); } if (error.children?.length) { result.push(...flattenValidationErrors(error.children, field)); } } return result; } export function buildValidationErrorResponse(errors: ValidationError[]): { normalizedError: NormalizedErrorDto; details: ValidationFieldError[]; } { const details = flattenValidationErrors(errors).slice(0, 1); const selected = details[0]; const summary = selected?.constraints[0] ?? 'Request validation failed'; const messageFa = selected ? translateValidationConstraint(selected.field, selected.constraints[0] ?? '') : 'اطلاعات ورودی نامعتبر است'; return { normalizedError: buildNormalizedError('VALIDATION_ERROR', { message: summary, messageFa, details, }), details, }; } export function formatHttpExceptionMessage(exception: unknown): string { if (!(exception && typeof exception === 'object' && 'getResponse' in exception)) { return exception instanceof Error ? exception.message : String(exception); } const response = (exception as { getResponse: () => unknown }).getResponse(); if (typeof response === 'string') { return response; } if (typeof response !== 'object' || response === null) { return exception instanceof Error ? exception.message : 'Request failed'; } const body = response as { message?: string | string[]; error?: unknown; }; if (isNormalizedError(body.error)) { return body.error.message; } if (Array.isArray(body.message)) { return body.message.join('; '); } if (typeof body.message === 'string' && body.message.length > 0) { return body.message; } return exception instanceof Error ? exception.message : 'Request failed'; } export function isNormalizedError(value: unknown): value is NormalizedErrorDto { return ( typeof value === 'object' && value !== null && 'code' in value && 'message' in value && typeof (value as NormalizedErrorDto).code === 'string' && typeof (value as NormalizedErrorDto).message === 'string' ); }