Skip to content

Commit d3c8844

Browse files
authoredMar 29, 2024··
Merge pull request #2863 from NovikovEvgeny/partial-type-helper-add-skip-null-option
feat: add skip null properties option to partial type
2 parents 8a51339 + 1b4084e commit d3c8844

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed
 

Diff for: ‎lib/type-helpers/partial-type.helper.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Type } from '@nestjs/common';
22
import {
33
applyIsOptionalDecorator,
4+
applyValidateIfDefinedDecorator,
45
inheritPropertyInitializers,
56
inheritTransformationMetadata,
67
inheritValidationMetadata
@@ -15,7 +16,25 @@ import { clonePluginMetadataFactory } from './mapped-types.utils';
1516

1617
const modelPropertiesAccessor = new ModelPropertiesAccessor();
1718

18-
export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
19+
export function PartialType<T>(
20+
classRef: Type<T>,
21+
/**
22+
* Configuration options.
23+
*/
24+
options: {
25+
/**
26+
* If true, validations will be ignored on a property if it is either null or undefined. If
27+
* false, validations will be ignored only if the property is undefined.
28+
* @default true
29+
*/
30+
skipNullProperties?: boolean;
31+
} = {}
32+
): Type<Partial<T>> {
33+
const applyPartialDecoratorFn =
34+
options.skipNullProperties === false
35+
? applyValidateIfDefinedDecorator
36+
: applyIsOptionalDecorator;
37+
1938
const fields = modelPropertiesAccessor.getModelProperties(classRef.prototype);
2039

2140
abstract class PartialTypeClass {
@@ -30,7 +49,7 @@ export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
3049
if (keysWithValidationConstraints) {
3150
keysWithValidationConstraints
3251
.filter((key) => !fields.includes(key))
33-
.forEach((key) => applyIsOptionalDecorator(PartialTypeClass, key));
52+
.forEach((key) => applyPartialDecoratorFn(PartialTypeClass, key));
3453
}
3554

3655
inheritTransformationMetadata(classRef, PartialTypeClass);
@@ -48,7 +67,7 @@ export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
4867
PartialTypeClass[METADATA_FACTORY_NAME]()
4968
);
5069
pluginFields.forEach((key) =>
51-
applyIsOptionalDecorator(PartialTypeClass, key)
70+
applyPartialDecoratorFn(PartialTypeClass, key)
5271
);
5372
}
5473

@@ -65,7 +84,7 @@ export function PartialType<T>(classRef: Type<T>): Type<Partial<T>> {
6584
required: false
6685
});
6786
decoratorFactory(PartialTypeClass.prototype, key);
68-
applyIsOptionalDecorator(PartialTypeClass, key);
87+
applyPartialDecoratorFn(PartialTypeClass, key);
6988
});
7089
}
7190
applyFields(fields);

Diff for: ‎test/type-helpers/partial-type.helper.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,44 @@ describe('PartialType', () => {
2121
describe('Validation metadata', () => {
2222
it('should apply @IsOptional to properties reflected by the plugin', async () => {
2323
const updateDto = new UpdateUserDto();
24+
updateDto.firstName = null;
2425
const validationErrors = await validate(updateDto);
2526
expect(validationErrors).toHaveLength(0);
2627
});
28+
29+
it('should apply @IsOptional to properties reflected by the plugin if option `skipNullProperties` is true', async () => {
30+
class UpdateUserWithNullableDto extends PartialType(CreateUserDto, {
31+
skipNullProperties: true
32+
}) {}
33+
const updateDto = new UpdateUserWithNullableDto();
34+
updateDto.firstName = null;
35+
const validationErrors = await validate(updateDto);
36+
expect(validationErrors).toHaveLength(0);
37+
});
38+
39+
it('should apply @IsOptional to properties reflected by the plugin if option `skipNullProperties` is undefined', async () => {
40+
class UpdateUserWithoutNullableDto extends PartialType(
41+
CreateUserDto,
42+
{}
43+
) {}
44+
const updateDto = new UpdateUserWithoutNullableDto();
45+
updateDto.firstName = null;
46+
const validationErrors = await validate(updateDto);
47+
expect(validationErrors).toHaveLength(0);
48+
});
49+
50+
it('should apply @ValidateIf to properties reflected by the plugin if option `skipNullProperties` is false', async () => {
51+
class UpdateUserWithoutNullableDto extends PartialType(CreateUserDto, {
52+
skipNullProperties: false
53+
}) {}
54+
const updateDto = new UpdateUserWithoutNullableDto();
55+
updateDto.firstName = null;
56+
const validationErrors = await validate(updateDto);
57+
expect(validationErrors).toHaveLength(1);
58+
expect(validationErrors[0].constraints).toEqual({
59+
isString: 'firstName must be a string'
60+
});
61+
});
2762
});
2863
describe('OpenAPI metadata', () => {
2964
it('should return partial class', async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.