/
githubmirror
/
nest
Обзор
Документация
Войти
/
githubmirror
/
nest
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
packages/common/pipes/parse-array.pipe.ts
190 строк
6 KB
Mohammed Said
fix(common): Add @publicApi to ParseDatePipe, rename ParseArrayOptions
18 июл 2026, 21:16
18 июл 2026, 21:16
15f30d3
Код
Авторство
О чём код?
import { Injectable } from '../decorators/core/injectable.decorator'; import { Optional } from '../decorators/core/optional.decorator'; import { HttpStatus } from '../enums/http-status.enum'; import { Type } from '../interfaces'; import { ArgumentMetadata, PipeTransform, } from '../interfaces/features/pipe-transform.interface'; import { HttpErrorByCode } from '../utils/http-error-by-code.util'; import { isNil, isString, isUndefined } from '../utils/shared.utils'; import { ValidationPipe, ValidationPipeOptions } from './validation.pipe'; const VALIDATION_ERROR_MESSAGE = 'Validation failed (parsable array expected)'; const DEFAULT_ARRAY_SEPARATOR = ','; /** * @publicApi */ export interface ParseArrayPipeOptions extends Omit< ValidationPipeOptions, 'transform' | 'validateCustomDecorators' | 'exceptionFactory' > { /** * Type for items to be converted into */ items?: Type<unknown>; /** * Items separator to split string by * @default ',' */ separator?: string; /** * If true, the pipe will return null or undefined if the value is not provided * @default false */ optional?: boolean; /** * A factory function that returns an exception object to be thrown * if validation fails. * @param error Error message or object * @returns The exception object */ exceptionFactory?: (error: any) => any; } /** @deprecated Use `ParseArrayPipeOptions` instead. */ export type ParseArrayOptions = ParseArrayPipeOptions; /** * Defines the built-in ParseArray Pipe * * @see [Built-in Pipes](https://docs.nestjs.com/pipes#built-in-pipes) * * @publicApi */ @Injectable() export class ParseArrayPipe implements PipeTransform { protected readonly validationPipe: ValidationPipe; protected exceptionFactory: (error: string) => any; constructor( @Optional() protected readonly options: ParseArrayPipeOptions = {}, ) { this.validationPipe = new ValidationPipe({ transform: true, validateCustomDecorators: true, ...options, }); const { exceptionFactory, errorHttpStatusCode = HttpStatus.BAD_REQUEST } = options; this.exceptionFactory = exceptionFactory || (error => new HttpErrorByCode[errorHttpStatusCode](error)); } /** * Method that accesses and performs optional transformation on argument for * in-flight requests. * * @param value currently processed route argument * @param metadata contains metadata about the currently processed route argument */ async transform(value: any, metadata: ArgumentMetadata): Promise<any> { if (!value && !this.options.optional) { throw this.exceptionFactory(VALIDATION_ERROR_MESSAGE); } else if (isNil(value) && this.options.optional) { return value; } if (!Array.isArray(value)) { if (!isString(value)) { throw this.exceptionFactory(VALIDATION_ERROR_MESSAGE); } else { try { value = value .trim() .split(this.options.separator || DEFAULT_ARRAY_SEPARATOR); } catch { throw this.exceptionFactory(VALIDATION_ERROR_MESSAGE); } } } if (this.options.items) { const validationMetadata: ArgumentMetadata = { metatype: this.options.items, type: 'query', }; const isExpectedTypePrimitive = this.isExpectedTypePrimitive(); const toClassInstance = (item: any, index?: number) => { if (this.options.items !== String) { try { item = JSON.parse(item); } catch { // Do nothing } } if (isExpectedTypePrimitive) { return this.validatePrimitive(item, index); } return this.validationPipe.transform(item, validationMetadata); }; if (this.options.stopAtFirstError === false) { // strict compare to "false" to make sure // that this option is disabled by default let errors: string[] = []; const targetArray = value as Array<unknown>; for (let i = 0; i < targetArray.length; i++) { try { targetArray[i] = await toClassInstance(targetArray[i]); } catch (err) { let message: string[] | string; if (err.getResponse) { const response = err.getResponse(); if (Array.isArray(response.message)) { message = response.message.map( (item: string) => `[${i}] ${item}`, ); } else { message = `[${i}] ${response.message}`; } } else { message = err; } errors = errors.concat(message); } } if (errors.length > 0) { throw this.exceptionFactory(errors as any); } return targetArray; } else { value = await Promise.all(value.map(toClassInstance)); } } return value; } protected isExpectedTypePrimitive(): boolean { return [Boolean, Number, String].includes(this.options.items as any); } protected validatePrimitive(originalValue: any, index?: number) { if (this.options.items === Number) { const value = originalValue !== null && originalValue !== '' ? +originalValue : NaN; if (isNaN(value)) { throw this.exceptionFactory( `${isUndefined(index) ? '' : `[${index}] `}item must be a number`, ); } return value; } else if (this.options.items === String) { if (!isString(originalValue)) { return `${originalValue}`; } } else if (this.options.items === Boolean) { if (typeof originalValue !== 'boolean') { throw this.exceptionFactory( `${ isUndefined(index) ? '' : `[${index}] ` }item must be a boolean value`, ); } } return originalValue; } }