diff --git a/e2e/api-spec.json b/e2e/api-spec.json index 5cf27e9b8..ca6e7dc09 100644 --- a/e2e/api-spec.json +++ b/e2e/api-spec.json @@ -1042,6 +1042,15 @@ } } }, + "enumWithDescription": { + "enum": [ + "A", + "B", + "C" + ], + "type": "string", + "description": "Enum with description" + }, "enum": { "$ref": "#/components/schemas/LettersEnum" }, @@ -1068,6 +1077,7 @@ "createdAt", "urls", "options", + "enumWithDescription", "enum", "enumArr" ] diff --git a/e2e/src/cats/dto/create-cat.dto.ts b/e2e/src/cats/dto/create-cat.dto.ts index acc33a05e..01ea7f6e7 100644 --- a/e2e/src/cats/dto/create-cat.dto.ts +++ b/e2e/src/cats/dto/create-cat.dto.ts @@ -42,6 +42,11 @@ export class CreateCatDto { }) readonly options?: Record[]; + @ApiProperty({ + description: 'Enum with description' + }) + readonly enumWithDescription: LettersEnum; + @ApiProperty({ enum: LettersEnum, enumName: 'LettersEnum' diff --git a/e2e/validate-schema.e2e-spec.ts b/e2e/validate-schema.e2e-spec.ts index c6c130da2..8f99f0195 100644 --- a/e2e/validate-schema.e2e-spec.ts +++ b/e2e/validate-schema.e2e-spec.ts @@ -67,6 +67,11 @@ describe('Validate OpenAPI schema', () => { import('./src/cats/dto/create-cat.dto'), { CreateCatDto: { + enumWithDescription: { + enum: await import( + './src/cats/dto/pagination-query.dto' + ).then((f) => f.LettersEnum) + }, name: { description: 'Name of the cat' } diff --git a/lib/plugin/metadata-loader.ts b/lib/plugin/metadata-loader.ts index 807689784..ac6b639c1 100644 --- a/lib/plugin/metadata-loader.ts +++ b/lib/plugin/metadata-loader.ts @@ -1,6 +1,12 @@ import { METADATA_FACTORY_NAME } from './plugin-constants'; export class MetadataLoader { + private static readonly refreshHooks = new Array<() => void>(); + + static addRefreshHook(hook: () => void) { + return MetadataLoader.refreshHooks.unshift(hook); + } + async load(metadata: Record) { const pkgMetadata = metadata['@nestjs/swagger']; if (!pkgMetadata) { @@ -13,9 +19,12 @@ export class MetadataLoader { if (controllers) { await this.applyMetadata(controllers); } + this.runHooks(); } - private async applyMetadata(meta: Record) { + private async applyMetadata( + meta: Array<[Promise, Record]> + ) { const loadPromises = meta.map(async ([fileImport, fileMeta]) => { const fileRef = await fileImport; Object.keys(fileMeta).map((key) => { @@ -25,4 +34,8 @@ export class MetadataLoader { }); await Promise.all(loadPromises); } + + private runHooks() { + MetadataLoader.refreshHooks.forEach((hook) => hook()); + } } diff --git a/lib/type-helpers/intersection-type.helper.ts b/lib/type-helpers/intersection-type.helper.ts index 383483e61..3dc98c251 100644 --- a/lib/type-helpers/intersection-type.helper.ts +++ b/lib/type-helpers/intersection-type.helper.ts @@ -1,11 +1,12 @@ import { Type } from '@nestjs/common'; import { - inheritTransformationMetadata, - inheritValidationMetadata, inheritPropertyInitializers, + inheritTransformationMetadata, + inheritValidationMetadata } from '@nestjs/mapped-types'; import { DECORATORS } from '../constants'; import { ApiProperty } from '../decorators'; +import { MetadataLoader } from '../plugin/metadata-loader'; import { ModelPropertiesAccessor } from '../services/model-properties-accessor'; import { clonePluginMetadataFactory } from './mapped-types.utils'; @@ -42,19 +43,29 @@ export function IntersectionType(...classRefs: T) { inheritValidationMetadata(classRef, IntersectionClassType); inheritTransformationMetadata(classRef, IntersectionClassType); - clonePluginMetadataFactory( - IntersectionClassType as Type, - classRef.prototype - ); + function applyFields(fields: string[]) { + clonePluginMetadataFactory( + IntersectionClassType as Type, + classRef.prototype + ); + + fields.forEach((propertyKey) => { + const metadata = Reflect.getMetadata( + DECORATORS.API_MODEL_PROPERTIES, + classRef.prototype, + propertyKey + ); + const decoratorFactory = ApiProperty(metadata); + decoratorFactory(IntersectionClassType.prototype, propertyKey); + }); + } + applyFields(fields); - fields.forEach((propertyKey) => { - const metadata = Reflect.getMetadata( - DECORATORS.API_MODEL_PROPERTIES, - classRef.prototype, - propertyKey + MetadataLoader.addRefreshHook(() => { + const fields = modelPropertiesAccessor.getModelProperties( + classRef.prototype ); - const decoratorFactory = ApiProperty(metadata); - decoratorFactory(IntersectionClassType.prototype, propertyKey); + applyFields(fields); }); }); diff --git a/lib/type-helpers/omit-type.helper.ts b/lib/type-helpers/omit-type.helper.ts index ea37594d4..78c986d26 100644 --- a/lib/type-helpers/omit-type.helper.ts +++ b/lib/type-helpers/omit-type.helper.ts @@ -7,6 +7,7 @@ import { import { omit } from 'lodash'; import { DECORATORS } from '../constants'; import { ApiProperty } from '../decorators'; +import { MetadataLoader } from '../plugin/metadata-loader'; import { ModelPropertiesAccessor } from '../services/model-properties-accessor'; import { clonePluginMetadataFactory } from './mapped-types.utils'; @@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor(); export function OmitType( classRef: Type, keys: readonly K[] -): Type> { +): Type> { const fields = modelPropertiesAccessor .getModelProperties(classRef.prototype) .filter((item) => !keys.includes(item as K)); @@ -31,20 +32,32 @@ 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, + function applyFields(fields: string[]) { + clonePluginMetadataFactory( + OmitTypeClass as Type, classRef.prototype, - propertyKey + (metadata: Record) => omit(metadata, keys) ); - const decoratorFactory = ApiProperty(metadata); - decoratorFactory(OmitTypeClass.prototype, propertyKey); + + fields.forEach((propertyKey) => { + const metadata = Reflect.getMetadata( + DECORATORS.API_MODEL_PROPERTIES, + classRef.prototype, + propertyKey + ); + const decoratorFactory = ApiProperty(metadata); + decoratorFactory(OmitTypeClass.prototype, propertyKey); + }); + } + applyFields(fields); + + MetadataLoader.addRefreshHook(() => { + const fields = modelPropertiesAccessor + .getModelProperties(classRef.prototype) + .filter((item) => !keys.includes(item as K)); + + applyFields(fields); }); - return OmitTypeClass as Type>; + + return OmitTypeClass as Type>; } diff --git a/lib/type-helpers/partial-type.helper.ts b/lib/type-helpers/partial-type.helper.ts index 3e9a8feb5..03d3ddde1 100644 --- a/lib/type-helpers/partial-type.helper.ts +++ b/lib/type-helpers/partial-type.helper.ts @@ -8,6 +8,7 @@ import { import { mapValues } from 'lodash'; import { DECORATORS } from '../constants'; import { ApiProperty } from '../decorators'; +import { MetadataLoader } from '../plugin/metadata-loader'; import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants'; import { ModelPropertiesAccessor } from '../services/model-properties-accessor'; import { clonePluginMetadataFactory } from './mapped-types.utils'; @@ -25,27 +26,37 @@ 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( - DECORATORS.API_MODEL_PROPERTIES, - classRef.prototype, - key - ) || {}; - - const decoratorFactory = ApiProperty({ - ...metadata, - required: false + function applyFields(fields: string[]) { + clonePluginMetadataFactory( + PartialTypeClass as Type, + classRef.prototype, + (metadata: Record) => + mapValues(metadata, (item) => ({ ...item, required: false })) + ); + + fields.forEach((key) => { + const metadata = + Reflect.getMetadata( + DECORATORS.API_MODEL_PROPERTIES, + classRef.prototype, + key + ) || {}; + + const decoratorFactory = ApiProperty({ + ...metadata, + required: false + }); + decoratorFactory(PartialTypeClass.prototype, key); + applyIsOptionalDecorator(PartialTypeClass, key); }); - decoratorFactory(PartialTypeClass.prototype, key); - applyIsOptionalDecorator(PartialTypeClass, key); + } + applyFields(fields); + + MetadataLoader.addRefreshHook(() => { + const fields = modelPropertiesAccessor.getModelProperties( + classRef.prototype + ); + applyFields(fields); }); if (PartialTypeClass[METADATA_FACTORY_NAME]) { diff --git a/lib/type-helpers/pick-type.helper.ts b/lib/type-helpers/pick-type.helper.ts index ae19b439a..1208ffced 100644 --- a/lib/type-helpers/pick-type.helper.ts +++ b/lib/type-helpers/pick-type.helper.ts @@ -7,6 +7,7 @@ import { import { pick } from 'lodash'; import { DECORATORS } from '../constants'; import { ApiProperty } from '../decorators'; +import { MetadataLoader } from '../plugin/metadata-loader'; import { ModelPropertiesAccessor } from '../services/model-properties-accessor'; import { clonePluginMetadataFactory } from './mapped-types.utils'; @@ -15,7 +16,7 @@ const modelPropertiesAccessor = new ModelPropertiesAccessor(); export function PickType( classRef: Type, keys: readonly K[] -): Type> { +): Type> { const fields = modelPropertiesAccessor .getModelProperties(classRef.prototype) .filter((item) => keys.includes(item as K)); @@ -32,21 +33,32 @@ 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, + function applyFields(fields: string[]) { + clonePluginMetadataFactory( + PickTypeClass as Type, classRef.prototype, - propertyKey + (metadata: Record) => pick(metadata, keys) ); - const decoratorFactory = ApiProperty(metadata); - decoratorFactory(PickTypeClass.prototype, propertyKey); + + fields.forEach((propertyKey) => { + const metadata = Reflect.getMetadata( + DECORATORS.API_MODEL_PROPERTIES, + classRef.prototype, + propertyKey + ); + const decoratorFactory = ApiProperty(metadata); + decoratorFactory(PickTypeClass.prototype, propertyKey); + }); + } + applyFields(fields); + + MetadataLoader.addRefreshHook(() => { + const fields = modelPropertiesAccessor + .getModelProperties(classRef.prototype) + .filter((item) => keys.includes(item as K)); + + applyFields(fields); }); - return PickTypeClass as Type>; + return PickTypeClass as Type>; } diff --git a/package-lock.json b/package-lock.json index 6ac5c531c..4f7c61f3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2121,6 +2121,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "dev": true, "engines": { "node": ">=8" } @@ -2138,6 +2139,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.1.0.tgz", "integrity": "sha512-3GNOuDjeAqEVt5Zjia3ZSK55Jg80hIIkq52BOzU+LkCjFgbuEhDot80lCKu05WyntAMAq5wREoDRGEGlSVxENw==", + "dev": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.6.0", @@ -2753,7 +2755,7 @@ "version": "13.7.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz", "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==", - "devOptional": true + "dev": true }, "node_modules/@types/yargs": { "version": "17.0.10", @@ -4752,13 +4754,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "devOptional": true + "dev": true }, "node_modules/class-validator": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", - "devOptional": true, + "dev": true, "dependencies": { "@types/validator": "^13.7.10", "libphonenumber-js": "^1.10.14", @@ -8555,6 +8557,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "dev": true, "engines": { "node": ">=6" } @@ -9322,7 +9325,7 @@ "version": "1.10.15", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.15.tgz", "integrity": "sha512-sLeVLmWX17VCKKulc+aDIRHS95TxoTsKMRJi5s5gJdwlqNzMWcBCtSHHruVyXjqfi67daXM2SnLf2juSrdx5Sg==", - "devOptional": true + "dev": true }, "node_modules/light-my-request": { "version": "5.10.0", @@ -11730,7 +11733,8 @@ "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -12248,6 +12252,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -13479,7 +13484,8 @@ "node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", @@ -13573,6 +13579,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dev": true, "dependencies": { "@lukeed/csprng": "^1.0.0" }, @@ -13782,7 +13789,7 @@ "version": "13.7.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 0.10" } @@ -15791,7 +15798,8 @@ "@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==" + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "dev": true }, "@lukeed/ms": { "version": "2.0.1", @@ -15803,6 +15811,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.1.0.tgz", "integrity": "sha512-3GNOuDjeAqEVt5Zjia3ZSK55Jg80hIIkq52BOzU+LkCjFgbuEhDot80lCKu05WyntAMAq5wREoDRGEGlSVxENw==", + "dev": true, "requires": { "iterare": "1.2.1", "tslib": "2.6.0", @@ -15826,8 +15835,7 @@ "@nestjs/mapped-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", - "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", - "requires": {} + "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==" }, "@nestjs/platform-express": { "version": "10.1.0", @@ -15960,8 +15968,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, - "requires": {} + "dev": true }, "@octokit/plugin-rest-endpoint-methods": { "version": "7.1.2", @@ -16268,7 +16275,7 @@ "version": "13.7.10", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz", "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ==", - "devOptional": true + "dev": true }, "@types/yargs": { "version": "17.0.10", @@ -16454,8 +16461,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.2.0", @@ -17718,13 +17724,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "devOptional": true + "dev": true }, "class-validator": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", - "devOptional": true, + "dev": true, "requires": { "@types/validator": "^13.7.10", "libphonenumber-js": "^1.10.14", @@ -18071,8 +18077,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", - "dev": true, - "requires": {} + "dev": true }, "create-require": { "version": "1.1.1", @@ -18732,8 +18737,7 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.7", @@ -20540,7 +20544,8 @@ "iterare": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", - "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "dev": true }, "iterate-iterator": { "version": "1.0.2", @@ -20786,8 +20791,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.4.3", @@ -21122,7 +21126,7 @@ "version": "1.10.15", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.15.tgz", "integrity": "sha512-sLeVLmWX17VCKKulc+aDIRHS95TxoTsKMRJi5s5gJdwlqNzMWcBCtSHHruVyXjqfi67daXM2SnLf2juSrdx5Sg==", - "devOptional": true + "dev": true }, "light-my-request": { "version": "5.10.0", @@ -22900,7 +22904,8 @@ "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true }, "regexp.prototype.flags": { "version": "1.4.3", @@ -23242,6 +23247,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "dev": true, "requires": { "tslib": "^2.1.0" } @@ -24082,8 +24088,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", - "dev": true, - "requires": {} + "dev": true }, "ts-jest": { "version": "29.1.1", @@ -24171,7 +24176,8 @@ "tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true }, "type-check": { "version": "0.4.0", @@ -24240,6 +24246,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dev": true, "requires": { "@lukeed/csprng": "^1.0.0" } @@ -24395,7 +24402,7 @@ "version": "13.7.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "devOptional": true + "dev": true }, "vary": { "version": "1.1.2", diff --git a/test/type-helpers/fixtures/create-user-dto.fixture.ts b/test/type-helpers/fixtures/create-user-dto.fixture.ts new file mode 100644 index 000000000..affaeb142 --- /dev/null +++ b/test/type-helpers/fixtures/create-user-dto.fixture.ts @@ -0,0 +1,28 @@ +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'; + +export class CreateUserDto { + @IsString() + firstName: string; + + @IsString() + lastName: string; + + @ApiProperty({ required: true }) + login: string; + + @Expose() + @Transform((str) => str + '_transformed') + @IsString() + @ApiProperty({ minLength: 10 }) + password: string; + + static [METADATA_FACTORY_NAME]() { + return { + firstName: { required: true, type: String }, + lastName: { required: true, type: String } + }; + } +} diff --git a/test/type-helpers/fixtures/serialized-metadata.fixture.ts b/test/type-helpers/fixtures/serialized-metadata.fixture.ts new file mode 100644 index 000000000..b7d5f2a21 --- /dev/null +++ b/test/type-helpers/fixtures/serialized-metadata.fixture.ts @@ -0,0 +1,16 @@ +export const SERIALIZED_METADATA = { + '@nestjs/swagger': { + models: [ + [ + import('./create-user-dto.fixture'), + { + CreateUserDto: { + active: { + type: () => Boolean + } + } + } + ] + ] + } +}; diff --git a/test/type-helpers/omit-type.helper.spec.ts b/test/type-helpers/omit-type.helper.spec.ts index e98b8eb3d..05b2a2e86 100644 --- a/test/type-helpers/omit-type.helper.spec.ts +++ b/test/type-helpers/omit-type.helper.spec.ts @@ -1,33 +1,14 @@ import { Type } from '@nestjs/common'; -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 { MetadataLoader } from '../../lib/plugin/metadata-loader'; import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor'; import { OmitType } from '../../lib/type-helpers'; +import { CreateUserDto } from './fixtures/create-user-dto.fixture'; +import { SERIALIZED_METADATA } from './fixtures/serialized-metadata.fixture'; -describe('OmitType', () => { - class CreateUserDto { - @MinLength(10) - @ApiProperty({ required: true }) - login: string; - - @Transform((str) => str + '_transformed') - @MinLength(10) - @ApiProperty({ minLength: 10 }) - password: string; - - lastName: string; +class UpdateUserDto extends OmitType(CreateUserDto, ['login', 'lastName']) {} - static [METADATA_FACTORY_NAME]() { - return { - firstName: { required: true, type: () => String }, - lastName: { required: true, type: () => String } - }; - } - } - - class UpdateUserDto extends OmitType(CreateUserDto, ['login', 'lastName']) {} +describe('OmitType', () => { + const metadataLoader = new MetadataLoader(); let modelPropertiesAccessor: ModelPropertiesAccessor; @@ -36,13 +17,16 @@ describe('OmitType', () => { }); describe('OpenAPI metadata', () => { - it('should omit "login" property', () => { - const prototype = (UpdateUserDto.prototype as any) as Type; + it('should omit "login" property', async () => { + await metadataLoader.load(SERIALIZED_METADATA); + + const prototype = UpdateUserDto.prototype as any as Type; modelPropertiesAccessor.applyMetadataFactory(prototype); expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([ 'password', - 'firstName' + 'firstName', + 'active' ]); }); }); diff --git a/test/type-helpers/partial-type.helper.spec.ts b/test/type-helpers/partial-type.helper.spec.ts index 5d7dbc3ff..04a9798f7 100644 --- a/test/type-helpers/partial-type.helper.spec.ts +++ b/test/type-helpers/partial-type.helper.spec.ts @@ -1,38 +1,16 @@ import { Type } from '@nestjs/common'; -import { Expose, Transform } from 'class-transformer'; -import { IsString, validate } from 'class-validator'; +import { validate } from 'class-validator'; import { DECORATORS } from '../../lib/constants'; -import { ApiProperty } from '../../lib/decorators'; -import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants'; +import { MetadataLoader } from '../../lib/plugin/metadata-loader'; import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor'; import { PartialType } from '../../lib/type-helpers'; +import { CreateUserDto } from './fixtures/create-user-dto.fixture'; +import { SERIALIZED_METADATA } from './fixtures/serialized-metadata.fixture'; -describe('PartialType', () => { - class CreateUserDto { - @IsString() - firstName: string; - - @IsString() - lastName: string; - - @ApiProperty({ required: true }) - login: string; - - @Expose() - @Transform((str) => str + '_transformed') - @IsString() - @ApiProperty({ minLength: 10 }) - password: string; +class UpdateUserDto extends PartialType(CreateUserDto) {} - static [METADATA_FACTORY_NAME]() { - return { - firstName: { required: true, type: String }, - lastName: { required: true, type: String } - }; - } - } - - class UpdateUserDto extends PartialType(CreateUserDto) {} +describe('PartialType', () => { + const metadataLoader = new MetadataLoader(); let modelPropertiesAccessor: ModelPropertiesAccessor; @@ -48,20 +26,23 @@ describe('PartialType', () => { }); }); describe('OpenAPI metadata', () => { - it('should return partial class', () => { - const prototype = (UpdateUserDto.prototype as any) as Type; + it('should return partial class', async () => { + await metadataLoader.load(SERIALIZED_METADATA); + + const prototype = UpdateUserDto.prototype as any as Type; modelPropertiesAccessor.applyMetadataFactory(prototype); expect(modelPropertiesAccessor.getModelProperties(prototype)).toEqual([ 'login', 'password', 'firstName', - 'lastName' + 'lastName', + 'active' ]); }); it('should set "required" option to "false" for each property', () => { - const classRef = (UpdateUserDto.prototype as any) as Type; + const classRef = UpdateUserDto.prototype as any as Type; const keys = modelPropertiesAccessor.getModelProperties(classRef); const metadata = keys.map((key) => { return Reflect.getMetadata(