diff --git a/lib/services/model-properties-accessor.ts b/lib/services/model-properties-accessor.ts
index 3a7bd31af..013e0f545 100644
--- a/lib/services/model-properties-accessor.ts
+++ b/lib/services/model-properties-accessor.ts
@@ -30,7 +30,7 @@ export class ModelPropertiesAccessor {
}
const metadata = prototype.constructor[METADATA_FACTORY_NAME]();
const properties = Object.keys(metadata);
- properties.forEach(key => {
+ properties.forEach((key) => {
createApiPropertyDecorator(metadata[key], false)(classPrototype, key);
});
} while (
diff --git a/lib/type-helpers/index.ts b/lib/type-helpers/index.ts
index fba5b5df9..dbd86ed6b 100644
--- a/lib/type-helpers/index.ts
+++ b/lib/type-helpers/index.ts
@@ -1,3 +1,4 @@
+export * from './intersection-type.helper';
export * from './omit-type.helper';
export * from './partial-type.helper';
export * from './pick-type.helper';
diff --git a/lib/type-helpers/intersection-type.helper.ts b/lib/type-helpers/intersection-type.helper.ts
new file mode 100644
index 000000000..8b6640ff1
--- /dev/null
+++ b/lib/type-helpers/intersection-type.helper.ts
@@ -0,0 +1,63 @@
+import { Type } from '@nestjs/common';
+import {
+ inheritTransformationMetadata,
+ inheritValidationMetadata
+} from '@nestjs/mapped-types';
+import { DECORATORS } from '../constants';
+import { ApiProperty } from '../decorators';
+import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
+import { clonePluginMetadataFactory } from './mapped-types.utils';
+
+const modelPropertiesAccessor = new ModelPropertiesAccessor();
+
+export function IntersectionType(
+ classARef: Type,
+ classBRef: Type
+): Type {
+ const fieldsOfA = modelPropertiesAccessor.getModelProperties(
+ classARef.prototype
+ );
+ const fieldsOfB = modelPropertiesAccessor.getModelProperties(
+ classBRef.prototype
+ );
+
+ abstract class IntersectionTypeClass {}
+ inheritValidationMetadata(classARef, IntersectionTypeClass);
+ inheritTransformationMetadata(classARef, IntersectionTypeClass);
+ inheritValidationMetadata(classBRef, IntersectionTypeClass);
+ inheritTransformationMetadata(classBRef, IntersectionTypeClass);
+
+ clonePluginMetadataFactory(
+ IntersectionTypeClass as Type,
+ classARef.prototype
+ );
+ clonePluginMetadataFactory(
+ IntersectionTypeClass as Type,
+ classBRef.prototype
+ );
+
+ fieldsOfA.forEach((propertyKey) => {
+ const metadata = Reflect.getMetadata(
+ DECORATORS.API_MODEL_PROPERTIES,
+ classARef.prototype,
+ propertyKey
+ );
+ const decoratorFactory = ApiProperty(metadata);
+ decoratorFactory(IntersectionTypeClass.prototype, propertyKey);
+ });
+
+ fieldsOfB.forEach((propertyKey) => {
+ const metadata = Reflect.getMetadata(
+ DECORATORS.API_MODEL_PROPERTIES,
+ classBRef.prototype,
+ propertyKey
+ );
+ const decoratorFactory = ApiProperty(metadata);
+ decoratorFactory(IntersectionTypeClass.prototype, propertyKey);
+ });
+
+ Object.defineProperty(IntersectionTypeClass, 'name', {
+ value: `Intersection${classARef.name}${classBRef.name}`
+ });
+ return IntersectionTypeClass as Type;
+}
diff --git a/lib/type-helpers/mapped-types.utils.ts b/lib/type-helpers/mapped-types.utils.ts
new file mode 100644
index 000000000..60589e2d4
--- /dev/null
+++ b/lib/type-helpers/mapped-types.utils.ts
@@ -0,0 +1,43 @@
+import { Type } from '@nestjs/common';
+import { identity } from 'lodash';
+import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants';
+
+export function clonePluginMetadataFactory(
+ target: Type,
+ parent: Type,
+ transformFn: (metadata: Record) => Record = identity
+) {
+ let targetMetadata = {};
+
+ do {
+ if (!parent.constructor) {
+ return;
+ }
+ if (!parent.constructor[METADATA_FACTORY_NAME]) {
+ continue;
+ }
+ const parentMetadata = parent.constructor[METADATA_FACTORY_NAME]();
+ targetMetadata = {
+ ...parentMetadata,
+ ...targetMetadata
+ };
+ } while (
+ (parent = Reflect.getPrototypeOf(parent) as Type) &&
+ parent !== Object.prototype &&
+ parent
+ );
+ targetMetadata = transformFn(targetMetadata);
+
+ if (target[METADATA_FACTORY_NAME]) {
+ const originalFactory = target[METADATA_FACTORY_NAME];
+ target[METADATA_FACTORY_NAME] = () => {
+ const originalMetadata = originalFactory();
+ return {
+ ...originalMetadata,
+ ...targetMetadata
+ };
+ };
+ } else {
+ target[METADATA_FACTORY_NAME] = () => targetMetadata;
+ }
+}
diff --git a/lib/type-helpers/omit-type.helper.ts b/lib/type-helpers/omit-type.helper.ts
index 8de59d2aa..c82ad039b 100644
--- a/lib/type-helpers/omit-type.helper.ts
+++ b/lib/type-helpers/omit-type.helper.ts
@@ -1,11 +1,13 @@
import { Type } from '@nestjs/common';
-import { DECORATORS } from '../constants';
-import { ApiProperty } from '../decorators';
-import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import {
inheritTransformationMetadata,
inheritValidationMetadata
-} from './type-helpers.utils';
+} from '@nestjs/mapped-types';
+import { omit } from 'lodash';
+import { DECORATORS } from '../constants';
+import { ApiProperty } from '../decorators';
+import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
+import { clonePluginMetadataFactory } from './mapped-types.utils';
const modelPropertiesAccessor = new ModelPropertiesAccessor();
@@ -24,6 +26,12 @@ export function OmitType(
inheritValidationMetadata(classRef, OmitTypeClass, isInheritedPredicate);
inheritTransformationMetadata(classRef, OmitTypeClass, isInheritedPredicate);
+ clonePluginMetadataFactory(
+ OmitTypeClass as Type,
+ classRef.prototype,
+ (metadata: Record) => omit(metadata, keys)
+ );
+
fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
diff --git a/lib/type-helpers/partial-type.helper.ts b/lib/type-helpers/partial-type.helper.ts
index a71f31e1e..425b163c7 100644
--- a/lib/type-helpers/partial-type.helper.ts
+++ b/lib/type-helpers/partial-type.helper.ts
@@ -1,12 +1,14 @@
import { Type } from '@nestjs/common';
-import { DECORATORS } from '../constants';
-import { ApiProperty } from '../decorators';
-import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import {
applyIsOptionalDecorator,
inheritTransformationMetadata,
inheritValidationMetadata
-} from './type-helpers.utils';
+} from '@nestjs/mapped-types';
+import { mapValues } from 'lodash';
+import { DECORATORS } from '../constants';
+import { ApiProperty } from '../decorators';
+import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
+import { clonePluginMetadataFactory } from './mapped-types.utils';
const modelPropertiesAccessor = new ModelPropertiesAccessor();
@@ -17,6 +19,13 @@ export function PartialType(classRef: Type): Type> {
inheritValidationMetadata(classRef, PartialTypeClass);
inheritTransformationMetadata(classRef, PartialTypeClass);
+ clonePluginMetadataFactory(
+ PartialTypeClass as Type,
+ classRef.prototype,
+ (metadata: Record) =>
+ mapValues(metadata, (item) => ({ ...item, required: false }))
+ );
+
fields.forEach((key) => {
const metadata =
Reflect.getMetadata(
diff --git a/lib/type-helpers/pick-type.helper.ts b/lib/type-helpers/pick-type.helper.ts
index 546937ad5..34145207e 100644
--- a/lib/type-helpers/pick-type.helper.ts
+++ b/lib/type-helpers/pick-type.helper.ts
@@ -1,11 +1,13 @@
import { Type } from '@nestjs/common';
-import { DECORATORS } from '../constants';
-import { ApiProperty } from '../decorators';
-import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
import {
inheritTransformationMetadata,
inheritValidationMetadata
-} from './type-helpers.utils';
+} from '@nestjs/mapped-types';
+import { pick } from 'lodash';
+import { DECORATORS } from '../constants';
+import { ApiProperty } from '../decorators';
+import { ModelPropertiesAccessor } from '../services/model-properties-accessor';
+import { clonePluginMetadataFactory } from './mapped-types.utils';
const modelPropertiesAccessor = new ModelPropertiesAccessor();
@@ -24,6 +26,12 @@ export function PickType(
inheritValidationMetadata(classRef, PickTypeClass, isInheritedPredicate);
inheritTransformationMetadata(classRef, PickTypeClass, isInheritedPredicate);
+ clonePluginMetadataFactory(
+ PickTypeClass as Type,
+ classRef.prototype,
+ (metadata: Record) => pick(metadata, keys)
+ );
+
fields.forEach((propertyKey) => {
const metadata = Reflect.getMetadata(
DECORATORS.API_MODEL_PROPERTIES,
diff --git a/lib/type-helpers/type-helpers.utils.ts b/lib/type-helpers/type-helpers.utils.ts
deleted file mode 100644
index fba9426eb..000000000
--- a/lib/type-helpers/type-helpers.utils.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import { Logger, Type } from '@nestjs/common';
-
-/* eslint-disable @typescript-eslint/no-var-requires */
-const logger = new Logger('TypeHelpers');
-
-export function applyIsOptionalDecorator(
- targetClass: Function,
- propertyKey: string
-) {
- if (!isClassValidatorAvailable()) {
- return;
- }
- const classValidator: typeof import('class-validator') = require('class-validator');
- const decoratorFactory = classValidator.IsOptional();
- decoratorFactory(targetClass.prototype, propertyKey);
-}
-
-export function inheritValidationMetadata(
- parentClass: Type,
- targetClass: Function,
- isPropertyInherited?: (key: string) => boolean
-) {
- if (!isClassValidatorAvailable()) {
- return;
- }
- try {
- const classValidator: typeof import('class-validator') = require('class-validator');
- const metadataStorage = classValidator.getFromContainer(
- classValidator.MetadataStorage
- );
- const targetMetadata = metadataStorage.getTargetValidationMetadatas(
- parentClass,
- null
- );
- targetMetadata
- .filter(
- ({ propertyName }) =>
- !isPropertyInherited || isPropertyInherited(propertyName)
- )
- .forEach((value) =>
- metadataStorage.addValidationMetadata({
- ...value,
- target: targetClass
- })
- );
- } catch (err) {
- logger.error(
- `Validation ("class-validator") metadata cannot be inherited for "${parentClass.name}" class.`
- );
- logger.error(err);
- }
-}
-
-type TransformMetadataKey =
- | '_excludeMetadatas'
- | '_exposeMetadatas'
- | '_typeMetadatas'
- | '_transformMetadatas';
-
-export function inheritTransformationMetadata(
- parentClass: Type,
- targetClass: Function,
- isPropertyInherited?: (key: string) => boolean
-) {
- if (!isClassTransformerAvailable()) {
- return;
- }
- try {
- const transformMetadataKeys: TransformMetadataKey[] = [
- '_excludeMetadatas',
- '_exposeMetadatas',
- '_transformMetadatas',
- '_typeMetadatas'
- ];
- transformMetadataKeys.forEach((key) =>
- inheritTransformerMetadata(
- key,
- parentClass,
- targetClass,
- isPropertyInherited
- )
- );
- } catch (err) {
- logger.error(
- `Transformer ("class-transformer") metadata cannot be inherited for "${parentClass.name}" class.`
- );
- logger.error(err);
- }
-}
-
-function inheritTransformerMetadata(
- key: TransformMetadataKey,
- parentClass: Type,
- targetClass: Function,
- isPropertyInherited?: (key: string) => boolean
-) {
- const classTransformer: typeof import('class-transformer/storage') = require('class-transformer/storage');
- const metadataStorage = classTransformer.defaultMetadataStorage;
-
- if (metadataStorage[key].has(parentClass)) {
- const metadataMap = metadataStorage[key] as Map>;
- const parentMetadata = metadataMap.get(parentClass);
-
- const targetMetadataEntries: Iterable<[string, any]> = Array.from(
- parentMetadata.entries()
- )
- .filter(([key]) => !isPropertyInherited || isPropertyInherited(key))
- .map(([key, metadata]) => {
- if (Array.isArray(metadata)) {
- // "_transformMetadatas" is an array of elements
- const targetMetadata = metadata.map((item) => ({
- ...item,
- target: targetClass
- }));
- return [key, targetMetadata];
- }
- return [key, { ...metadata, target: targetClass }];
- });
-
- metadataMap.set(targetClass, new Map(targetMetadataEntries));
- }
-}
-
-function isClassValidatorAvailable() {
- try {
- require('class-validator');
- return true;
- } catch {
- return false;
- }
-}
-
-function isClassTransformerAvailable() {
- try {
- require('class-transformer');
- return true;
- } catch {
- return false;
- }
-}
diff --git a/package-lock.json b/package-lock.json
index 1cf0ccf60..9f7ff7209 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1473,6 +1473,11 @@
}
}
},
+ "@nestjs/mapped-types": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.0.1.tgz",
+ "integrity": "sha512-4G4Ui7Sj0UqXiZsUFk/6cPD3K7uZEFSElzkOftaJ3/lXW+HUi1/vfWXabF53qrzO1enTRQDxt1plDbP6SsqXEg=="
+ },
"@nestjs/platform-express": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-7.0.8.tgz",
diff --git a/package.json b/package.json
index 563a870cf..6b9e28840 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"release": "release-it"
},
"dependencies": {
+ "@nestjs/mapped-types": "0.0.1",
"lodash": "4.17.15",
"path-to-regexp": "3.2.0"
},
diff --git a/test/type-helpers/intersection-type.helper.spec.ts b/test/type-helpers/intersection-type.helper.spec.ts
new file mode 100644
index 000000000..19911f8b7
--- /dev/null
+++ b/test/type-helpers/intersection-type.helper.spec.ts
@@ -0,0 +1,57 @@
+import { Type } from '@nestjs/common';
+import { Expose, Transform } from 'class-transformer';
+import { IsString } from 'class-validator';
+import { ApiProperty } from '../../lib/decorators';
+import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants';
+import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
+import { IntersectionType } from '../../lib/type-helpers';
+
+describe('IntersectionType', () => {
+ class CreateUserDto {
+ @ApiProperty({ required: true })
+ login: string;
+
+ @Expose()
+ @Transform((str) => str + '_transformed')
+ @IsString()
+ @ApiProperty({ minLength: 10 })
+ password: string;
+
+ static [METADATA_FACTORY_NAME]() {
+ return { dateOfBirth: { required: true, type: () => String } };
+ }
+ }
+
+ class UserDto {
+ @IsString()
+ @ApiProperty({ required: false })
+ firstName: string;
+
+ static [METADATA_FACTORY_NAME]() {
+ return { dateOfBirth2: { required: true, type: () => String } };
+ }
+ }
+
+ class UpdateUserDto extends IntersectionType(UserDto, CreateUserDto) {}
+
+ let modelPropertiesAccessor: ModelPropertiesAccessor;
+
+ beforeEach(() => {
+ modelPropertiesAccessor = new ModelPropertiesAccessor();
+ });
+
+ describe('OpenAPI metadata', () => {
+ it('should return combined class', () => {
+ const prototype = (UpdateUserDto.prototype as any) as Type;
+
+ modelPropertiesAccessor.applyMetadataFactory(prototype);
+ expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([
+ 'firstName',
+ 'login',
+ 'password',
+ 'dateOfBirth2',
+ 'dateOfBirth'
+ ]);
+ });
+ });
+});
diff --git a/test/type-helpers/omit-type.helper.spec.ts b/test/type-helpers/omit-type.helper.spec.ts
index f48c8684b..e98b8eb3d 100644
--- a/test/type-helpers/omit-type.helper.spec.ts
+++ b/test/type-helpers/omit-type.helper.spec.ts
@@ -1,10 +1,10 @@
import { Type } from '@nestjs/common';
-import { classToClass, Transform } from 'class-transformer';
-import { MinLength, validate } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { MinLength } from 'class-validator';
import { ApiProperty } from '../../lib/decorators';
+import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { OmitType } from '../../lib/type-helpers';
-import { getValidationMetadataByTarget } from './type-helpers.test-utils';
describe('OmitType', () => {
class CreateUserDto {
@@ -16,9 +16,18 @@ describe('OmitType', () => {
@MinLength(10)
@ApiProperty({ minLength: 10 })
password: string;
+
+ lastName: string;
+
+ static [METADATA_FACTORY_NAME]() {
+ return {
+ firstName: { required: true, type: () => String },
+ lastName: { required: true, type: () => String }
+ };
+ }
}
- class UpdateUserDto extends OmitType(CreateUserDto, ['login']) {}
+ class UpdateUserDto extends OmitType(CreateUserDto, ['login', 'lastName']) {}
let modelPropertiesAccessor: ModelPropertiesAccessor;
@@ -28,53 +37,13 @@ describe('OmitType', () => {
describe('OpenAPI metadata', () => {
it('should omit "login" property', () => {
- expect(
- modelPropertiesAccessor.getModelProperties(
- (UpdateUserDto.prototype as any) as Type
- )
- ).toEqual(['password']);
- });
- });
-
- describe('Validation metadata', () => {
- it('should inherit metadata with "login" property excluded', () => {
- const validationKeys = getValidationMetadataByTarget(UpdateUserDto).map(
- (item) => item.propertyName
- );
- expect(validationKeys).toEqual(['password']);
- });
- describe('when object does not fulfil validation rules', () => {
- it('"validate" should return validation errors', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.password = '1234567';
-
- const validationErrors = await validate(updateDto);
-
- expect(validationErrors.length).toEqual(1);
- expect(validationErrors[0].constraints).toEqual({
- minLength: 'password must be longer than or equal to 10 characters'
- });
- });
- });
- describe('otherwise', () => {
- it('"validate" should return an empty array', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.password = '1234567891011';
-
- const validationErrors = await validate(updateDto);
- expect(validationErrors.length).toEqual(0);
- });
- });
- });
-
- describe('Transformer metadata', () => {
- it('should inherit transformer metadata', () => {
- const password = '1234567891011';
- const updateDto = new UpdateUserDto();
- updateDto.password = password;
+ const prototype = (UpdateUserDto.prototype as any) as Type;
- const transformedDto = classToClass(updateDto);
- expect(transformedDto.password).toEqual(password + '_transformed');
+ modelPropertiesAccessor.applyMetadataFactory(prototype);
+ expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([
+ 'password',
+ 'firstName'
+ ]);
});
});
});
diff --git a/test/type-helpers/partial-type.helper.spec.ts b/test/type-helpers/partial-type.helper.spec.ts
index 62d13471e..2d77a6b39 100644
--- a/test/type-helpers/partial-type.helper.spec.ts
+++ b/test/type-helpers/partial-type.helper.spec.ts
@@ -1,11 +1,11 @@
import { Type } from '@nestjs/common';
-import { classToClass, Expose, Transform } from 'class-transformer';
-import { IsString, validate } from 'class-validator';
+import { Expose, Transform } from 'class-transformer';
+import { IsString } from 'class-validator';
import { DECORATORS } from '../../lib/constants';
import { ApiProperty } from '../../lib/decorators';
+import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { PartialType } from '../../lib/type-helpers';
-import { getValidationMetadataByTarget } from './type-helpers.test-utils';
describe('PartialType', () => {
class CreateUserDto {
@@ -17,6 +17,13 @@ describe('PartialType', () => {
@IsString()
@ApiProperty({ minLength: 10 })
password: string;
+
+ static [METADATA_FACTORY_NAME]() {
+ return {
+ firstName: { required: true, type: String },
+ lastName: { required: true, type: String }
+ };
+ }
}
class UpdateUserDto extends PartialType(CreateUserDto) {}
@@ -29,11 +36,15 @@ describe('PartialType', () => {
describe('OpenAPI metadata', () => {
it('should return partial class', () => {
- expect(
- modelPropertiesAccessor.getModelProperties(
- (UpdateUserDto.prototype as any) as Type
- )
- ).toEqual(['login', 'password']);
+ const prototype = (UpdateUserDto.prototype as any) as Type;
+
+ modelPropertiesAccessor.applyMetadataFactory(prototype);
+ expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([
+ 'login',
+ 'password',
+ 'firstName',
+ 'lastName'
+ ]);
});
it('should set "required" option to "false" for each property', () => {
@@ -46,6 +57,7 @@ describe('PartialType', () => {
key
);
});
+
expect(metadata[0]).toEqual({
isArray: false,
required: false,
@@ -57,47 +69,11 @@ describe('PartialType', () => {
minLength: 10,
type: String
});
- });
- });
- describe('Validation metadata', () => {
- it('should inherit metadata and apply @IsOptional() to each property', () => {
- const validationKeys = getValidationMetadataByTarget(UpdateUserDto).map(
- (item) => item.propertyName
- );
- expect(validationKeys).toEqual(['password', 'login', 'password']);
- });
- describe('when object does not fulfil validation rules', () => {
- it('"validate" should return validation errors', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.password = 1234567 as any;
-
- const validationErrors = await validate(updateDto);
-
- expect(validationErrors.length).toEqual(1);
- expect(validationErrors[0].constraints).toEqual({
- isString: 'password must be a string'
- });
- });
- });
- describe('otherwise', () => {
- it('"validate" should return an empty array', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.login = '1234567891011';
-
- const validationErrors = await validate(updateDto);
- expect(validationErrors.length).toEqual(0);
+ expect(metadata[2]).toEqual({
+ isArray: false,
+ required: false,
+ type: String
});
});
});
-
- describe('Transformer metadata', () => {
- it('should inherit transformer metadata', () => {
- const password = '1234567891011';
- const updateDto = new UpdateUserDto();
- updateDto.password = password;
-
- const transformedDto = classToClass(updateDto);
- expect(transformedDto.password).toEqual(password + '_transformed');
- });
- });
});
diff --git a/test/type-helpers/pick-type.helper.spec.ts b/test/type-helpers/pick-type.helper.spec.ts
index 11439b81d..979728ee5 100644
--- a/test/type-helpers/pick-type.helper.spec.ts
+++ b/test/type-helpers/pick-type.helper.spec.ts
@@ -1,10 +1,10 @@
import { Type } from '@nestjs/common';
-import { classToClass, Transform } from 'class-transformer';
-import { MinLength, validate } from 'class-validator';
+import { Transform } from 'class-transformer';
+import { MinLength } from 'class-validator';
import { ApiProperty } from '../../lib/decorators';
+import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants';
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
import { PickType } from '../../lib/type-helpers';
-import { getValidationMetadataByTarget } from './type-helpers.test-utils';
describe('PickType', () => {
class CreateUserDto {
@@ -16,9 +16,18 @@ describe('PickType', () => {
@MinLength(10)
@ApiProperty({ minLength: 10 })
password: string;
+
+ firstName: string;
+
+ static [METADATA_FACTORY_NAME]() {
+ return {
+ firstName: { required: true, type: () => String },
+ lastName: { required: true, type: () => String }
+ };
+ }
}
- class UpdateUserDto extends PickType(CreateUserDto, ['login']) {}
+ class UpdateUserDto extends PickType(CreateUserDto, ['login', 'firstName']) {}
let modelPropertiesAccessor: ModelPropertiesAccessor;
@@ -28,52 +37,13 @@ describe('PickType', () => {
describe('OpenAPI metadata', () => {
it('should pick "login" property', () => {
- expect(
- modelPropertiesAccessor.getModelProperties(
- (UpdateUserDto.prototype as any) as Type
- )
- ).toEqual(['login']);
- });
- });
- describe('Validation metadata', () => {
- it('should inherit metadata with "password" property excluded', () => {
- const validationKeys = getValidationMetadataByTarget(UpdateUserDto).map(
- (item) => item.propertyName
- );
- expect(validationKeys).toEqual(['login']);
- });
- describe('when object does not fulfil validation rules', () => {
- it('"validate" should return validation errors', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.login = '1234567';
-
- const validationErrors = await validate(updateDto);
-
- expect(validationErrors.length).toEqual(1);
- expect(validationErrors[0].constraints).toEqual({
- minLength: 'login must be longer than or equal to 10 characters'
- });
- });
- });
- describe('otherwise', () => {
- it('"validate" should return an empty array', async () => {
- const updateDto = new UpdateUserDto();
- updateDto.login = '1234567891011';
-
- const validationErrors = await validate(updateDto);
- expect(validationErrors.length).toEqual(0);
- });
- });
- });
-
- describe('Transformer metadata', () => {
- it('should inherit transformer metadata', () => {
- const login = '1234567891011';
- const updateDto = new UpdateUserDto();
- updateDto.login = login;
+ const prototype = (UpdateUserDto.prototype as any) as Type;
- const transformedDto = classToClass(updateDto);
- expect(transformedDto.login).toEqual(login + '_transformed');
+ modelPropertiesAccessor.applyMetadataFactory(prototype);
+ expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([
+ 'login',
+ 'firstName'
+ ]);
});
});
});