I am working with the DTO validation via IntersectionType, and trying to add in an optional field from another DTO, using PickType composition. For instance, I create an AdminCodeDTO, which is basically all my rules for working with the AdminCode in the application, so the number range is consistent, and any other definitions.
export class AdminCodeDTO {
@IsDefined() @IsNumber() @Min(1) @Max(999) public AdminCode: number;
constructor(AdminCode?: number) {
this.AdminCode = AdminCode;
}
}
The test works correctly, and the validation does indeed run.
it('should generate the DTO errors', async () => {
const DTOValidCheck: AdminCodeDTO = new AdminCodeDTO();
const Errors: Array<ValidationError> = await validate(DTOValidCheck);
expect(Errors.length).toBe(1);
expect(Errors[0].constraints['isDefined']).toBe('AdminCode should not be null or undefined');
expect(Errors[0].constraints['isNumber']).toBe('AdminCode must be a number conforming to the specified constraints');
expect(Errors[0].constraints['max']).toBe('AdminCode must not be greater than 999');
expect(Errors[0].constraints['min']).toBe('AdminCode must not be less than 1');
});
I now have another DTO that has other data, plus the AdminCode, so I use the IntersectionType to include it.
export class TimeEntryDTO extends IntersectionType (
AdminCodeDTO,
OtherDTO
) {}
In the above case, my AdminCodeDTO is now part of OtherDTO, and all the validation works properly. This works as expected, my AdminCode is required, including all the validation rules.
Now I have a case where I want the AdminCodeDTO validation to be present, but only if the field is populated. The field is no longer required, but optional. I believe the PickType was for this. As such, I thought I could do:
export class TimeEntryDTO extends IntersectionType (
PickType(AdminCodeDTO, ['AdminCode'] as const),
TimeDetailDTO,
) {}
I would have expected this to now have my TimeDetailDTO, and then the AdminCode field, as optional. This does appear to be the case, as the field is included, however, the validation causes an issue as @isDefined() fails when I do not provide the AdminCode.
it('should not generate the AdminCode errors when AdminCode is not set', async () => {
TestDTO.AdminCode = undefined;
const Errors: Array<ValidationError> = await validate(TestDTO);
expect(Errors.length).toBe(0); // This should be the case, but it is returning all the validation
// The following errors are all returned, which I thought the PickType would have made the field optional, so the IsDefined() should not be processed.
expect(Errors[0].constraints['isDefined']).toBe('AdminCode should not be null or undefined');
expect(Errors[0].constraints['isNumber']).toBe('AdminCode must be a number conforming to the specified constraints');
expect(Errors[0].constraints['max']).toBe('AdminCode must not be greater than 999');
expect(Errors[0].constraints['min']).toBe('AdminCode must not be less than 1');
});
However, I do believe the InterestionType is combining all of these, and while the AdminCode field may now be optional in the class, the validation decorators are still used as they were loaded.
As such, is there a way to remove a decorator in this scenario? Is there a way to add one, so that I could remove it from the AdminCodeDTO, and then add it when I want the AdminCode rule enforced (such as defining the AdminCode)?
CodePudding user response:
PickType
only picks the fields that are specified in the array, it doesn't modify if the field is optional or not. You can wrap the PickType
in a PartialType
to make that field picked and optional. PartialType(PickType(AdminCodeDTO, ['AdminCode'] as const))