Repository: nestjs/swagger
Pull request #2935 (2024-04-29 21:06:33)

feat: add function for enum types
## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [x] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Other... Please describe:

## What is the current behavior?

When using enum values that are dynamic, the value returned depends on the order that modules are imported in 

Issue Number: https://github.com/nestjs/swagger/issues/2786


## What is the new behavior?

enum can be a function which is called at the moment that the document is generated.


## Does this PR introduce a breaking change?
- [ ] Yes
- [x] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information



Patch:
diff --git a/lib/decorators/api-property.decorator.ts b/lib/decorators/api-property.decorator.ts
index 0a30425f..5f5fe741 100644
--- a/lib/decorators/api-property.decorator.ts
+++ b/lib/decorators/api-property.decorator.ts
@@ -6,7 +6,7 @@ import { createPropertyDecorator, getTypeIsArrayTuple } from './helpers';
 export interface ApiPropertyOptions
   extends Omit<SchemaObjectMetadata, 'name' | 'enum'> {
   name?: string;
-  enum?: any[] | Record<string, any>;
+  enum?: any[] | Record<string, any> | (() => (any[] | Record<string, any>));
   enumName?: string;
 }
 
diff --git a/lib/utils/enum.utils.ts b/lib/utils/enum.utils.ts
index 9ddab59b..8ea9634d 100644
--- a/lib/utils/enum.utils.ts
+++ b/lib/utils/enum.utils.ts
@@ -3,7 +3,11 @@ import { SchemaObject } from '../interfaces/open-api-spec.interface';
 import { SchemaObjectMetadata } from '../interfaces/schema-object-metadata.interface';
 import { SwaggerEnumType } from '../types/swagger-enum.type';
 
-export function getEnumValues(enumType: SwaggerEnumType): string[] | number[] {
+export function getEnumValues(enumType: SwaggerEnumType | (() => SwaggerEnumType)): string[] | number[] {
+  if (typeof enumType === 'function') {
+    return getEnumValues(enumType());
+  }
+
   if (Array.isArray(enumType)) {
     return enumType as string[];
   }


Test patch:
diff --git a/test/explorer/swagger-explorer.spec.ts b/test/explorer/swagger-explorer.spec.ts
index 2870f792..06f451f9 100644
--- a/test/explorer/swagger-explorer.spec.ts
+++ b/test/explorer/swagger-explorer.spec.ts
@@ -89,6 +89,12 @@ describe('SwaggerExplorer', () => {
         isArray: true
       })
       enumArr: LettersEnum;
+
+      @ApiProperty({
+        enum: () => LettersEnum,
+        enumName: 'LettersEnum',
+      })
+      enumFunction: LettersEnum;
     }
 
     @Controller('')
@@ -229,7 +235,7 @@ describe('SwaggerExplorer', () => {
       expect(routes[0].root!.method).toEqual('post');
       expect(routes[0].root!.path).toEqual('/globalPrefix/modulePath/foos');
       expect(routes[0].root!.summary).toEqual('Create foo');
-      expect(routes[0].root!.parameters.length).toEqual(5);
+      expect(routes[0].root!.parameters.length).toEqual(6);
       expect(routes[0].root!.parameters).toEqual([
         {
           in: 'query',
@@ -278,6 +284,14 @@ describe('SwaggerExplorer', () => {
             },
             type: 'array'
           }
+        },
+        {
+          in: 'query',
+          name: 'enumFunction',
+          required: true,
+          schema: {
+            $ref: '#/components/schemas/LettersEnum'
+          }
         }
       ]);
       expect(routes[0].root!.requestBody).toEqual({
diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts
index 2a754673..531ab129 100644
--- a/test/services/schema-object-factory.spec.ts
+++ b/test/services/schema-object-factory.spec.ts
@@ -38,6 +38,12 @@ describe('SchemaObjectFactory', () => {
       Third = 3
     }
 
+    enum HairColour {
+      Brown = 'Brown',
+      Blond = 'Blond',
+      Ginger = 'Ginger',
+    }
+
     class CreatePersonDto {
       @ApiProperty()
       name: string;
@@ -57,13 +63,19 @@ describe('SchemaObjectFactory', () => {
 
       @ApiProperty({ enum: Ranking, enumName: 'Ranking', isArray: true })
       rankings: Ranking[];
+
+      @ApiProperty({ enum: () => HairColour, enumName: 'HairColour' })
+      hairColour: HairColour;
+
+      @ApiProperty({ enum: () => ['Pizza', 'Burger', 'Salad'], enumName: 'Food', isArray: true })
+      favouriteFoods: string[];
     }
 
     it('should explore enum', () => {
       const schemas: Record<string, SchemasObject> = {};
       schemaObjectFactory.exploreModelSchema(Person, schemas);
 
-      expect(Object.keys(schemas)).toHaveLength(4);
+      expect(Object.keys(schemas)).toHaveLength(6);
 
       expect(schemas).toHaveProperty('Role');
       expect(schemas.Role).toEqual({
@@ -78,6 +90,10 @@ describe('SchemaObjectFactory', () => {
         type: 'number',
         enum: [1, 2, 3]
       });
+      expect(schemas.HairColour).toEqual({
+        type: 'string',
+        enum: ['Brown', 'Blond', 'Ginger']
+      });
       expect(schemas).toHaveProperty('Person');
       expect(schemas.Person).toEqual({
         type: 'object',
@@ -102,13 +118,22 @@ describe('SchemaObjectFactory', () => {
             items: {
               $ref: '#/components/schemas/Ranking'
             }
+          },
+          'favouriteFoods': {
+            'items': {
+              '$ref': '#/components/schemas/Food'
+            },
+            'type': 'array'
+          },
+          'hairColour': {
+            '$ref': '#/components/schemas/HairColour'
           }
         },
-        required: ['role', 'roles', 'groups', 'rankings']
+        required: ['role', 'roles', 'groups', 'rankings', 'hairColour', 'favouriteFoods']
       });
       schemaObjectFactory.exploreModelSchema(CreatePersonDto, schemas);
 
-      expect(Object.keys(schemas)).toHaveLength(5);
+      expect(Object.keys(schemas)).toHaveLength(7);
       expect(schemas).toHaveProperty('CreatePersonDto');
       expect(schemas.CreatePersonDto).toEqual({
         type: 'object',