Skip to content

Commit

Permalink
Merge pull request #1274 from tf3/partial-type-helper-add-skip-null-o…
Browse files Browse the repository at this point in the history
…ption

feat(mapped-types): add skip null properties option to partial type
  • Loading branch information
kamilmysliwiec authored Feb 7, 2024
2 parents 4ee8720 + 981c4ca commit 8b26440
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 2 deletions.
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './partial-type.helper';
export * from './pick-type.helper';
export {
applyIsOptionalDecorator,
applyValidateIfDefinedDecorator,
inheritPropertyInitializers,
inheritTransformationMetadata,
inheritValidationMetadata,
Expand Down
20 changes: 18 additions & 2 deletions lib/partial-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@ import { Type } from '@nestjs/common';
import { MappedType } from './mapped-type.interface';
import {
applyIsOptionalDecorator,
applyValidateIfDefinedDecorator,
inheritPropertyInitializers,
inheritTransformationMetadata,
inheritValidationMetadata,
} from './type-helpers.utils';
import { RemoveFieldsWithType } from './types/remove-fields-with-type.type';

export function PartialType<T>(classRef: Type<T>) {
export function PartialType<T>(
classRef: Type<T>,
/**
* Configuration options.
*/
options: {
/**
* If true, validations will be ignored on a property if it is either null or undefined. If
* false, validations will be ignored only if the property is undefined.
* @default true
*/
skipNullProperties?: boolean;
} = {},
) {
abstract class PartialClassType {
constructor() {
inheritPropertyInitializers(this, classRef);
Expand All @@ -20,7 +34,9 @@ export function PartialType<T>(classRef: Type<T>) {

if (propertyKeys) {
propertyKeys.forEach((key) => {
applyIsOptionalDecorator(PartialClassType, key);
options.skipNullProperties === false
? applyValidateIfDefinedDecorator(PartialClassType, key)
: applyIsOptionalDecorator(PartialClassType, key);
});
}

Expand Down
14 changes: 14 additions & 0 deletions lib/type-helpers.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ export function applyIsOptionalDecorator(
decoratorFactory(targetClass.prototype, propertyKey);
}

export function applyValidateIfDefinedDecorator(
targetClass: Function,
propertyKey: string,
) {
if (!isClassValidatorAvailable()) {
return;
}
const classValidator: typeof import('class-validator') = require('class-validator');
const decoratorFactory = classValidator.ValidateIf(
(_, value) => value !== undefined,
);
decoratorFactory(targetClass.prototype, propertyKey);
}

export function inheritValidationMetadata(
parentClass: Type<any>,
targetClass: Function,
Expand Down
40 changes: 40 additions & 0 deletions tests/partial-type.helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,44 @@ describe('PartialType', () => {
expect(updateUserDto.login).toEqual('defaultLogin');
});
});

describe('Configuration options', () => {
it('should not ignore validations for null properties when `skipNullProperties` is false', async () => {
class UpdateUserDtoDisallowNull extends PartialType(CreateUserDto, {
skipNullProperties: false,
}) {}

const updateDto = new UpdateUserDtoDisallowNull();
updateDto.password = null as any;

const validationErrors = await validate(updateDto);

expect(validationErrors.length).toBe(1);
expect(validationErrors[0].constraints).toEqual({
isString: 'password must be a string',
});
});

it('should ignore validations on null properties when `skipNullProperties` is undefined', async () => {
const updateDto = new UpdateUserDto();
updateDto.password = null as any;

const validationErrors = await validate(updateDto);

expect(validationErrors.length).toBe(0);
});

it('should ignore validations on null properties when `skipNullProperties` is true', async () => {
class UpdateUserDtoAllowNull extends PartialType(CreateUserDto, {
skipNullProperties: true,
}) {}

const updateDto = new UpdateUserDtoAllowNull();
updateDto.password = null as any;

const validationErrors = await validate(updateDto);

expect(validationErrors.length).toBe(0);
});
});
});

0 comments on commit 8b26440

Please sign in to comment.