diff --git a/src/infrastructure/decorators/fields/BaseField.ts b/src/infrastructure/decorators/fields/BaseField.ts index 5f10fbb..6edfc81 100644 --- a/src/infrastructure/decorators/fields/BaseField.ts +++ b/src/infrastructure/decorators/fields/BaseField.ts @@ -1,7 +1,7 @@ import {applyDecorators} from '@nestjs/common'; import {ApiProperty} from '@nestjs/swagger'; import {ColumnType} from '@steroidsjs/typeorm/driver/types/ColumnTypes'; -import {IsNotEmpty, isString} from 'class-validator'; +import {IsArray, IsDefined, IsOptional, isString, NotEquals, ValidateIf} from 'class-validator'; import {IAllFieldOptions} from './index'; import {ITransformCallback, Transform} from '../Transform'; @@ -155,6 +155,17 @@ export const getFieldDecorator = (targetClass, fieldName: string): (...args: any return decorator; }; +const getRequiredNullableValidators = ({required, nullable}: IBaseFieldOptions) => [ + // Отключаем валидацию для null, не пропускаем undefined + required && nullable && [ValidateIf((object, value) => value !== null), NotEquals(undefined)], + // Не пропускаем null и undefined + required && !nullable && IsDefined(), + // Отключаем валидацию для null и undefined + !required && nullable && IsOptional(), + // Отключаем валидацию для undefined, не пропускаем null + !required && !nullable && [ValidateIf((object, value) => value !== undefined), NotEquals(null)], +].flat().filter(Boolean); + const ColumnMetaDecorator = (options: IBaseFieldOptions, internalOptions: IInternalFieldOptions) => (object, propertyName) => { //проверить getOwnMetadata Reflect.defineMetadata(STEROIDS_META_FIELD, options, object, propertyName); @@ -166,28 +177,30 @@ const ColumnMetaDecorator = (options: IBaseFieldOptions, internalOptions: IInter }; export function BaseField(options: IBaseFieldOptions = null, internalOptions: IInternalFieldOptions = {}) { + const isArray = typeof options.isArray === 'boolean' + ? options.isArray + : (internalOptions.isArray || null); + return applyDecorators( ...[ ColumnMetaDecorator({ label: null, hint: null, ...options, - isArray: typeof options.isArray === 'boolean' - ? options.isArray - : (internalOptions.isArray || null), + isArray, appType: internalOptions.appType || null, }, internalOptions), ApiProperty({ type: options.jsType || internalOptions.swaggerType || internalOptions.jsType, description: options.label || undefined, example: options.example || undefined, - required: options.nullable === false, + required: options.required, + nullable: options.nullable, isArray: options.isArray || internalOptions.isArray, }), options.transform && Transform(options.transform), - options.required && IsNotEmpty({ - message: 'Обязательно для заполнения', - }), + ...getRequiredNullableValidators(options), + isArray && IsArray(), ].filter(Boolean), ); } diff --git a/src/infrastructure/decorators/fields/BooleanField.ts b/src/infrastructure/decorators/fields/BooleanField.ts index 23d2030..d90eae8 100644 --- a/src/infrastructure/decorators/fields/BooleanField.ts +++ b/src/infrastructure/decorators/fields/BooleanField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsBoolean, IsOptional} from 'class-validator'; +import {IsBoolean} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; import {Transform} from '../Transform'; @@ -23,6 +23,5 @@ export function BooleanField(options: IBaseFieldOptions = {}) { IsBoolean({ message: 'Должен быть булевом', }), - IsOptional(), ); } diff --git a/src/infrastructure/decorators/fields/CoordinateField.ts b/src/infrastructure/decorators/fields/CoordinateField.ts index f5e1f65..6892e3e 100644 --- a/src/infrastructure/decorators/fields/CoordinateField.ts +++ b/src/infrastructure/decorators/fields/CoordinateField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsString, ValidateIf} from 'class-validator'; +import {IsString} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface ICoordinateFieldOptions extends IBaseFieldOptions { @@ -15,7 +15,6 @@ export function CoordinateField(options: ICoordinateFieldOptions = {}) { appType: 'decimal', jsType: 'number', }), - options.nullable && ValidateIf((object, value) => value !== null), IsString(), ].filter(Boolean) ); diff --git a/src/infrastructure/decorators/fields/DateField.ts b/src/infrastructure/decorators/fields/DateField.ts index 0b57240..c34020f 100644 --- a/src/infrastructure/decorators/fields/DateField.ts +++ b/src/infrastructure/decorators/fields/DateField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsISO8601, ValidateIf, ValidationArguments} from 'class-validator'; +import {IsISO8601, ValidationArguments} from 'class-validator'; import {formatISO9075, parseISO} from 'date-fns'; import {BaseField, IBaseFieldOptions} from './BaseField'; import {Transform, TRANSFORM_TYPE_FROM_DB, TRANSFORM_TYPE_TO_DB} from '../Transform'; @@ -46,7 +46,6 @@ export function DateField(options: IDateFieldOptions = {}) { }), Transform(({value}) => normalizeDate(value), TRANSFORM_TYPE_FROM_DB), Transform(({value}) => normalizeDate(value), TRANSFORM_TYPE_TO_DB), - options.nullable && ValidateIf((object, value) => value), options.minDate && MinDate(options.minDate, { each: options.isArray, message: (args) => `Выбрана дата раньше минимально допустимой (${normalizeFunctionDate(options.minDate, args)})`, diff --git a/src/infrastructure/decorators/fields/DecimalField.ts b/src/infrastructure/decorators/fields/DecimalField.ts index 9fdb1b0..53592f5 100644 --- a/src/infrastructure/decorators/fields/DecimalField.ts +++ b/src/infrastructure/decorators/fields/DecimalField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsDecimal, ValidateBy, ValidateIf, ValidationOptions} from 'class-validator'; +import {IsDecimal, ValidateBy, ValidationOptions} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface IDecimalFieldOptions extends IBaseFieldOptions { @@ -54,7 +54,6 @@ export function DecimalField(options: IDecimalFieldOptions = {}) { appType: 'decimal', jsType: 'number', }), - options.nullable && ValidateIf((object, value) => value !== null && typeof value !== 'undefined'), IsDecimal({ decimal_digits: String(options.scale || 2), }, { diff --git a/src/infrastructure/decorators/fields/DecimalNumberField.ts b/src/infrastructure/decorators/fields/DecimalNumberField.ts index 15c0ce4..07f3404 100644 --- a/src/infrastructure/decorators/fields/DecimalNumberField.ts +++ b/src/infrastructure/decorators/fields/DecimalNumberField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {Max, Min, ValidateIf, ValidateBy, ValidationOptions, buildMessage, isDecimal} from 'class-validator'; +import {Max, Min, ValidateBy, ValidationOptions, buildMessage, isDecimal} from 'class-validator'; import {IDecimalFieldOptions} from './DecimalField'; import {BaseField} from './BaseField'; @@ -43,7 +43,6 @@ export function DecimalNumberField(options: IDecimalFieldOptions = {}) { jsType: 'number', }), Transform(({value}) => value ? Number(value) : value, TRANSFORM_TYPE_FROM_DB), - options.nullable && ValidateIf((object, value) => value !== null && typeof value !== 'undefined'), IsDecimalNumber(options, { message: options.isDecimalConstraintMessage || 'Должно быть числом', }), diff --git a/src/infrastructure/decorators/fields/EmailField.ts b/src/infrastructure/decorators/fields/EmailField.ts index 1538896..4372ed6 100644 --- a/src/infrastructure/decorators/fields/EmailField.ts +++ b/src/infrastructure/decorators/fields/EmailField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsEmail, ValidateIf} from 'class-validator'; +import {IsEmail} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface IEmailFieldOptions extends IBaseFieldOptions { @@ -17,7 +17,6 @@ export function EmailField(options: IEmailFieldOptions = {}) { appType: 'email', jsType: 'string', }), - options.nullable && ValidateIf((object, value) => value !== null && typeof value !== 'undefined'), IsEmail({ allow_display_name: true, }, { diff --git a/src/infrastructure/decorators/fields/EnumField.ts b/src/infrastructure/decorators/fields/EnumField.ts index bde8b9a..c93ebab 100644 --- a/src/infrastructure/decorators/fields/EnumField.ts +++ b/src/infrastructure/decorators/fields/EnumField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsEnum, ValidateIf} from 'class-validator'; +import {IsEnum} from 'class-validator'; import {ApiProperty} from '@nestjs/swagger'; import {BaseField, IBaseFieldOptions} from './BaseField'; import BaseEnum from '../../../domain/base/BaseEnum'; @@ -35,7 +35,6 @@ export function EnumField(options: IEnumFieldOptions = {}) { ApiProperty({ enum: options.enum, }), - options.nullable && ValidateIf((object, value) => value !== null && typeof value !== 'undefined'), IsEnum(options.enum, { each: options.isArray, message: options.isEnumConstraintMessage || 'Выберите одно из значений', diff --git a/src/infrastructure/decorators/fields/FileField.ts b/src/infrastructure/decorators/fields/FileField.ts index 83fce56..4bddb73 100644 --- a/src/infrastructure/decorators/fields/FileField.ts +++ b/src/infrastructure/decorators/fields/FileField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsArray, IsInt, ValidateIf} from 'class-validator'; +import {IsArray, IsInt} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface IFileField extends IBaseFieldOptions { @@ -14,7 +14,6 @@ export function getFileFieldDecorators(options: IFileField) { appType: 'file', jsType: 'number', }), - options.nullable && ValidateIf((object, value) => value), options.multiple ? IsArray({ message: options.isImage ? 'Необходимо загрузить изображения' : 'Необходимо загрузить файлы', diff --git a/src/infrastructure/decorators/fields/IntegerField.ts b/src/infrastructure/decorators/fields/IntegerField.ts index d4a6032..b0db936 100644 --- a/src/infrastructure/decorators/fields/IntegerField.ts +++ b/src/infrastructure/decorators/fields/IntegerField.ts @@ -1,6 +1,6 @@ import {applyDecorators} from '@nestjs/common'; import {toInteger as _toInteger} from 'lodash'; -import {IsInt, Max, Min, ValidateIf} from 'class-validator'; +import {IsInt, Max, Min} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; import {Transform} from '../Transform'; @@ -12,7 +12,6 @@ export interface IIntegerFieldOptions extends IBaseFieldOptions { } const isEmpty = value => !value && value !== 0 && value !== '0'; -const isArrayEmpty = value => !value || (Array.isArray(value) && value?.length === 0); export function IntegerField(options: IIntegerFieldOptions = {}) { return applyDecorators(...[ @@ -21,7 +20,6 @@ export function IntegerField(options: IIntegerFieldOptions = {}) { appType: 'integer', jsType: 'number', }), - options.nullable && ValidateIf((object, value) => options.isArray ? !isArrayEmpty(value) : !isEmpty(value)), Transform(({value}) => { if (Array.isArray(value)) { return value.map(valueItem => !isEmpty(valueItem) ? _toInteger(valueItem) : null); diff --git a/src/infrastructure/decorators/fields/JSONBField.ts b/src/infrastructure/decorators/fields/JSONBField.ts index f07cb48..cdba843 100644 --- a/src/infrastructure/decorators/fields/JSONBField.ts +++ b/src/infrastructure/decorators/fields/JSONBField.ts @@ -1,5 +1,4 @@ import {applyDecorators} from '@nestjs/common'; -import {IsOptional} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export type IJSONBFieldOptions = IBaseFieldOptions @@ -11,6 +10,5 @@ export function JSONBField(options: IJSONBFieldOptions = {}) { appType: 'object', jsType: 'jsonb', }), - !options.required && IsOptional(), ].filter(Boolean)); } diff --git a/src/infrastructure/decorators/fields/PhoneField.ts b/src/infrastructure/decorators/fields/PhoneField.ts index 2de4dbc..3d984de 100644 --- a/src/infrastructure/decorators/fields/PhoneField.ts +++ b/src/infrastructure/decorators/fields/PhoneField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsPhoneNumber, ValidateIf} from 'class-validator'; +import {IsPhoneNumber} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; import {Transform} from '../Transform'; @@ -28,7 +28,6 @@ export function PhoneField(options: IPhoneFieldOptions = {}) { appType: 'phone', jsType: 'string', }), - options.nullable && ValidateIf((object, value) => value), Transform(({value}) => normalizePhone(value)), IsPhoneNumber(null, { message: options.constraintMessage || 'Некорректный номер телефона', diff --git a/src/infrastructure/decorators/fields/RelationField.ts b/src/infrastructure/decorators/fields/RelationField.ts index 76381e1..035dece 100644 --- a/src/infrastructure/decorators/fields/RelationField.ts +++ b/src/infrastructure/decorators/fields/RelationField.ts @@ -99,7 +99,6 @@ export function RelationField(options: IRelationFieldOptions) { isArray: ['ManyToMany', 'OneToMany'].includes(options.type), }), //options.type === 'ManyToOne' && JoinColumn(), - ValidateIf((object, value) => !!value), ValidateNested({each: true}), Type(options.relationClass), Transform(relationTransformFromDb, TRANSFORM_TYPE_FROM_DB), diff --git a/src/infrastructure/decorators/fields/RelationIdField.ts b/src/infrastructure/decorators/fields/RelationIdField.ts index 5f8c6fd..1b3a513 100644 --- a/src/infrastructure/decorators/fields/RelationIdField.ts +++ b/src/infrastructure/decorators/fields/RelationIdField.ts @@ -1,6 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {ArrayNotEmpty, ValidateIf} from 'class-validator'; -import {isEmpty as _isEmpty, isBoolean as _isBoolean} from 'lodash'; +import {isBoolean as _isBoolean} from 'lodash'; import {BaseField, getFieldOptions, getMetaPrimaryKey, IBaseFieldOptions} from './BaseField'; import {Transform, TRANSFORM_TYPE_FROM_DB, TRANSFORM_TYPE_TO_DB} from '../Transform'; import {getTableFromModel} from '../../base/ModelTableStorage'; @@ -54,8 +53,6 @@ export function RelationIdField(options: IRelationIdFieldOptions = {}) { options.nullable = true; } - const arrayNotEmptyMessage = options.isFieldValidConstraintMessage || 'Не должно быть пустым'; - return applyDecorators( ...[ BaseField(options, { @@ -63,8 +60,6 @@ export function RelationIdField(options: IRelationIdFieldOptions = {}) { appType: 'relationId', jsType: 'number', }), - options.nullable && ValidateIf((object, value) => !_isEmpty(value)), - options.isArray && !options.nullable && ArrayNotEmpty({message: arrayNotEmptyMessage}), Transform(relationTransformFromDb, TRANSFORM_TYPE_FROM_DB), Transform(relationTransformToDb, TRANSFORM_TYPE_TO_DB), ].filter(Boolean), diff --git a/src/infrastructure/decorators/fields/StringField.ts b/src/infrastructure/decorators/fields/StringField.ts index 5707a7e..770fbab 100644 --- a/src/infrastructure/decorators/fields/StringField.ts +++ b/src/infrastructure/decorators/fields/StringField.ts @@ -1,6 +1,6 @@ import {applyDecorators} from '@nestjs/common'; import {toInteger as _toInteger} from 'lodash'; -import {IsOptional, IsString, MaxLength, MinLength} from 'class-validator'; +import {IsString, MaxLength, MinLength} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface IStringFieldOptions extends IBaseFieldOptions { @@ -23,7 +23,6 @@ export function StringField(options: IStringFieldOptions = {}) { each: options.isArray, message: options.isStringConstraintMessage || 'Должна быть строка', }), - !options.required && IsOptional(), // TODO check nullable and required typeof options.min === 'number' && MinLength(options.min, { message: options.minConstraintMessage, each: options.isArray, diff --git a/src/infrastructure/decorators/fields/TextField.ts b/src/infrastructure/decorators/fields/TextField.ts index 4a556be..0fa8580 100644 --- a/src/infrastructure/decorators/fields/TextField.ts +++ b/src/infrastructure/decorators/fields/TextField.ts @@ -1,6 +1,6 @@ import {applyDecorators} from '@nestjs/common'; import {toInteger as _toInteger} from 'lodash'; -import {IsOptional, IsString, MaxLength, MinLength} from 'class-validator'; +import {IsString, MaxLength, MinLength} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export interface ITextFieldOptions extends IBaseFieldOptions { @@ -20,7 +20,6 @@ export function TextField(options: ITextFieldOptions = {}) { each: options.isArray, message: options.isStringConstraintMessage || 'Должна быть строка', }), - !options.required && IsOptional(), typeof options.min === 'number' && MinLength(options.min, { message: `Длина строка должна быть не менее ${options.min}` || options.minConstraintMessage, each: options.isArray, diff --git a/src/infrastructure/decorators/fields/TimeField.ts b/src/infrastructure/decorators/fields/TimeField.ts index 90d592d..b883ef0 100644 --- a/src/infrastructure/decorators/fields/TimeField.ts +++ b/src/infrastructure/decorators/fields/TimeField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsMilitaryTime, ValidateIf} from 'class-validator'; +import {IsMilitaryTime} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; export function TimeField(options: IBaseFieldOptions = {}) { @@ -10,7 +10,6 @@ export function TimeField(options: IBaseFieldOptions = {}) { appType: 'time', jsType: 'string', }), - options?.nullable && ValidateIf((object, value) => value !== null), IsMilitaryTime({ message: 'Время необходимо ввести в формате часы:минуты, например 07:32', }), diff --git a/src/infrastructure/decorators/fields/UpdateTimeField.ts b/src/infrastructure/decorators/fields/UpdateTimeField.ts index f4b2818..db334ac 100644 --- a/src/infrastructure/decorators/fields/UpdateTimeField.ts +++ b/src/infrastructure/decorators/fields/UpdateTimeField.ts @@ -1,5 +1,5 @@ import {applyDecorators} from '@nestjs/common'; -import {IsOptional, IsString} from 'class-validator'; +import {IsString} from 'class-validator'; import {BaseField, IBaseFieldOptions} from './BaseField'; import {normalizeDateTime} from './DateTimeField'; import {Transform, TRANSFORM_TYPE_FROM_DB, TRANSFORM_TYPE_TO_DB} from '../Transform'; @@ -21,7 +21,6 @@ export function UpdateTimeField(options: IUpdateTimeFieldOptions = {}) { }), Transform(({value}) => normalizeDateTime(value, false), TRANSFORM_TYPE_FROM_DB), Transform(() => normalizeDateTime(new Date(), false), TRANSFORM_TYPE_TO_DB), - IsOptional(), IsString(), ); }