Logical API errors throw stable codes so users see translated messages instead of a generic bad request. Co-authored-by: Cursor <cursoragent@cursor.com>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { HttpException, HttpStatus } from '@nestjs/common';
|
|
import type { ValidationError } from 'class-validator';
|
|
import { AppException } from './app.exception';
|
|
import { ErrorCode, type ErrorCodeValue, type ValidationErrorDetail } from './error-codes';
|
|
|
|
const KNOWN_VALIDATION_CODES = new Set<string>(Object.values(ErrorCode));
|
|
|
|
function isKnownValidationCode(value: string): value is ErrorCodeValue {
|
|
return KNOWN_VALIDATION_CODES.has(value);
|
|
}
|
|
|
|
function constraintToCode(constraintKey: string, message: string): ErrorCodeValue {
|
|
if (isKnownValidationCode(message)) {
|
|
return message;
|
|
}
|
|
|
|
switch (constraintKey) {
|
|
case 'isEmail':
|
|
return ErrorCode.VALIDATION_EMAIL_INVALID;
|
|
case 'minLength':
|
|
return ErrorCode.VALIDATION_PASSWORD_TOO_SHORT;
|
|
case 'isEnum':
|
|
return ErrorCode.VALIDATION_ORGANIZATION_TYPE_INVALID;
|
|
case 'matches':
|
|
case 'isDateString':
|
|
case 'isUuid':
|
|
case 'isInt':
|
|
case 'isBoolean':
|
|
case 'isArray':
|
|
return ErrorCode.VALIDATION_INVALID_REQUEST;
|
|
case 'isIn':
|
|
return ErrorCode.VALIDATION_LANGUAGE_INVALID;
|
|
case 'whitelistValidation':
|
|
return ErrorCode.VALIDATION_INVALID_REQUEST;
|
|
default:
|
|
return ErrorCode.VALIDATION_FIELD_REQUIRED;
|
|
}
|
|
}
|
|
|
|
function flattenValidationErrors(
|
|
errors: ValidationError[],
|
|
parentPath = '',
|
|
): ValidationErrorDetail[] {
|
|
const details: ValidationErrorDetail[] = [];
|
|
|
|
for (const error of errors) {
|
|
const field = parentPath ? `${parentPath}.${error.property}` : error.property;
|
|
|
|
if (error.constraints) {
|
|
const [constraintKey, message] = Object.entries(error.constraints)[0];
|
|
details.push({
|
|
field,
|
|
code: constraintToCode(constraintKey, message),
|
|
});
|
|
}
|
|
|
|
if (error.children?.length) {
|
|
details.push(...flattenValidationErrors(error.children, field));
|
|
}
|
|
}
|
|
|
|
return details;
|
|
}
|
|
|
|
export function validationExceptionFactory(errors: ValidationError[]): HttpException {
|
|
const details = flattenValidationErrors(errors);
|
|
|
|
if (details.length === 0) {
|
|
return new AppException(ErrorCode.VALIDATION_FAILED, HttpStatus.BAD_REQUEST);
|
|
}
|
|
|
|
return new AppException(ErrorCode.VALIDATION_FAILED, HttpStatus.BAD_REQUEST, details);
|
|
}
|