Проверяйте вложенные объекты, используя валидатор класса и nestjs

Я пытаюсь проверить вложенные объекты, используя class-validator и NestJS. Я уже пытался следовать этой теме, используя @Type декоратор из класса-трансформация и не повезло. Это то, что у меня есть:

DTO:

class PositionDto {
  @IsNumber()
  cost: number;

  @IsNumber()
  quantity: number;
}

export class FreeAgentsCreateEventDto {

  @IsNumber()
  eventId: number;

  @IsEnum(FinderGamesSkillLevel)
  skillLevel: FinderGamesSkillLevel;

  @ValidateNested({ each: true })
  @Type(() => PositionDto)
  positions: PositionDto[];

}

Я также использую встроенный канал проверки nestjs, это мой загрузчик:

async function bootstrap() {
  const app = await NestFactory.create(ServerModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(config.PORT);
}
bootstrap();

Он отлично работает для других свойств, массив объектов - единственный, который не работает.

4 ответа

Решение

Вы ожидаете positions: [1] бросить 400, но вместо этого это принято.

Согласно этой проблеме Github, это, похоже, ошибка в классе-валидаторе. Если вы переходите в примитивный тип (boolean, string, number,...) или array вместо объекта он примет входные данные как допустимые, хотя и не должен.


Я не вижу никакого стандартного обходного пути, кроме создания собственного декоратора проверки:

import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';

export function IsNonPrimitiveArray(validationOptions?: ValidationOptions) {
  return (object: any, propertyName: string) => {
    registerDecorator({
      name: 'IsNonPrimitiveArray',
      target: object.constructor,
      propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          return Array.isArray(value) && value.reduce((a, b) => a && typeof b === 'object' && !Array.isArray(b), true);
        },
      },
    });
  };
}

и затем используйте его в своем классе dto:

@ValidateNested({ each: true })
@IsNonPrimitiveArray()
@Type(() => PositionDto)
positions: PositionDto[];

Для меня я мог бы проверить вложенный объект с помощью 'class-transformer'

import { Type } from 'class-transformer';

полный пример:

import {
  MinLength,
  MaxLength,
  IsNotEmpty,
  ValidateNested,
  IsDefined,
  IsNotEmptyObject,
  IsObject,
  IsString,
} from 'class-validator';
import { Type } from 'class-transformer';

class MultiLanguageDTO {
  @IsString()
  @IsNotEmpty()
  @MinLength(4)
  @MaxLength(40)
  en: string;

  @IsString()
  @IsNotEmpty()
  @MinLength(4)
  @MaxLength(40)
  ar: string;
}

export class VideoDTO {
  @IsDefined()
  @IsNotEmptyObject()
  @IsObject()
  @ValidateNested()
  @Type(() => MultiLanguageDTO)
  name!: MultiLanguageDTO;
}

Я столкнулся с той же проблемой, поэтому я создал свой собственный ValidateNestedдекоратор.

      import {
  ValidationOptions,
  registerDecorator,
  ValidationArguments,
  validateSync,
} from 'class-validator';
import { plainToClass } from 'class-transformer';

/**
 * @decorator
 * @description A custom decorator to validate a validation-schema within a validation schema upload N levels
 * @param schema The validation Class
 */
export function ValidateNested(
  schema: new () => any,
  validationOptions?: ValidationOptions
) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'ValidateNested',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          args.value;
          if (Array.isArray(value)) {
            for (let i = 0; i < (<Array<any>>value).length; i++) {
              if (validateSync(plainToClass(schema, value[i])).length) {
                return false;
              }
            }
            return true;
          } else
            return validateSync(plainToClass(schema, value)).length
              ? false
              : true;
        },
        defaultMessage(args) {
          if (Array.isArray(args.value)) {
            for (let i = 0; i < (<Array<any>>args.value).length; i++) {
              return (
                `${args.property}::index${i} -> ` +
                validateSync(plainToClass(schema, args.value[i]))
                  .map((e) => e.constraints)
                  .reduce((acc, next) => acc.concat(Object.values(next)), [])
              ).toString();
            }
          } else
            return (
              `${args.property}: ` +
              validateSync(plainToClass(schema, args.value))
                .map((e) => e.constraints)
                .reduce((acc, next) => acc.concat(Object.values(next)), [])
            ).toString();
        },
      },
    });
  };
}

Затем вы можете использовать его как -

      class Schema2 {

  @IsNotEmpty()
  @IsString()
  prop1: string;

  @IsNotEmpty()
  @IsString()
  prop2: string;
}


class Schema1 {
  @IsNotEmpty()
  @IsString()
  prop3: string;

  @ValidateNested(Schema2)
  nested_prop: Schema2;
}

Работает как для непримитивных массивов, так и для объектов javascript.

Для меня я также могу использовать вложенный валидатор, но я не могу отобразить сообщение:

      export class OrderDto {
  
  ...

  @ArrayNotEmpty()
  @ValidateNested({ each: true, message:"Not display" })
  @Type(() => FactureDto)
  factures: FactureDto[];
}

export class FactureDto {

  ...

  @IsNumber()
  @Min(1, { message: 'Not display either' })
  amount: number;
}

Сообщение об ошибке всегда "Исключение неверного запроса". Есть идеи об этой проблеме?

Другие вопросы по тегам