/
githubmirror
/
novu
Обзор
Документация
Войти
/
githubmirror
/
novu
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
next
libs/application-generic/src/commands/base.command.ts
111 строк
4 KB
Dima Grossman
fix(application-generic): avoid ClientSession plainToInstance crash fixes NV-8457 (#12153)
30 июл 2026, 14:20
Не верифицирован
30 июл 2026, 14:20
477f7da
Код
Авторство
О чём код?
import { BadRequestException } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; import { plainToInstance } from 'class-transformer'; import { ValidationError, validateSync } from 'class-validator'; // biome-ignore lint/complexity/noStaticOnlyClass: Base class pattern for command validation export abstract class BaseCommand { /** * @param data - Plain command fields validated via class-transformer / class-validator. * @param extras - Runtime objects that must not go through `plainToInstance` * (e.g. Mongo `ClientSession`, `AbortSignal`). Assigned onto the instance after transform. * Passing them in `data` throws (e.g. `ClientSession requires a MongoClient`). */ static create<T extends BaseCommand>(this: new (...args: unknown[]) => T, data: T, extras?: Partial<T>): T { // biome-ignore lint/complexity/noThisInStatic: Biome linter is configured to newer JS/TS version than the compiler const convertedObject = plainToInstance<T, unknown>(this, { ...data, }); const errors = validateSync(convertedObject, { forbidUnknownValues: false }); const flattenedErrors = flattenErrors(errors); if (Object.keys(flattenedErrors).length > 0) { // biome-ignore lint/complexity/noThisInStatic: Biome linter is configured to newer JS/TS version than the compiler throw new CommandValidationException(this.name, flattenedErrors); } if (extras) { Object.assign(convertedObject, extras); } return convertedObject; } } export class ConstraintValidation { @ApiProperty({ type: 'array', items: { type: 'string' }, description: 'List of validation error messages', example: ['Field is required', 'Invalid format'], }) messages: string[]; @ApiProperty({ required: false, description: 'Value that failed validation', oneOf: [ { type: 'string', nullable: true }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array', items: { anyOf: [ { type: 'string', nullable: true }, { type: 'number' }, { type: 'boolean' }, { type: 'object', additionalProperties: true }, ], }, }, ], example: 'xx xx xx ', }) value?: string | number | boolean | object | object[] | null; } function flattenErrors(errors: ValidationError[], prefix: string = ''): Record<string, ConstraintValidation> { const result: Record<string, ConstraintValidation> = {}; for (const error of errors) { const currentKey = prefix ? `${prefix}.${error.property}` : error.property; if (error.constraints) { result[currentKey] = { messages: Object.values(error.constraints), value: error.value, }; } if (error.children && error.children.length > 0) { const childErrors = flattenErrors(error.children, currentKey); for (const [key, value] of Object.entries(childErrors)) { if (result[key]) { result[key].messages = result[key].messages.concat(value.messages); } else { result[key] = value; } } } } return result; } export class CommandValidationException extends BadRequestException { constructor( public className: string, public constraintsViolated: Record<string, ConstraintValidation> ) { const message = formatValidationMessage(className, constraintsViolated); super({ message, className, constraintsViolated }); } } function formatValidationMessage(className: string, constraints: Record<string, ConstraintValidation>): string { const details = Object.entries(constraints) .map(([field, constraint]) => `${field}: ${constraint.messages.join(', ')}`) .join('; '); return `Validation failed for ${className}: ${details}`; }