Skip to content

Commit b25597b

Browse files
committed
fix: workaround for @IsEnum({ ..., isArray: true }) when used in @Query
1 parent d0022e6 commit b25597b

File tree

3 files changed

+68
-13
lines changed

3 files changed

+68
-13
lines changed

src/decorators/is-enum.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { Controller, Get, Query } from '@nestjs/common';
12
import { Result } from 'true-myth';
23

3-
import { generateSchemas, input, make } from '../../tests/helpers';
4+
import { generateSchemas, generateSpec, input, make } from '../../tests/helpers';
45
import { IsEnum } from '../nestjs-swagger-dto';
56

67
describe('IsEnum', () => {
@@ -153,6 +154,48 @@ describe('IsEnum', () => {
153154
});
154155
});
155156

157+
it('generates correct schema including minLength and maxLength when used in @Query', async () => {
158+
enum Flag {
159+
On = 'On',
160+
Off = 'Off',
161+
}
162+
163+
class TestQuery {
164+
@IsEnum({
165+
enum: { Flag },
166+
isArray: { minLength: 1, maxLength: 2, force: true },
167+
optional: true,
168+
})
169+
enumField?: Flag;
170+
}
171+
172+
@Controller('x')
173+
class AppController {
174+
@Get()
175+
someEndpoint(@Query() {}: TestQuery) {}
176+
}
177+
178+
const spec = await generateSpec([AppController]);
179+
180+
expect(spec.paths['/x'].get).toStrictEqual({
181+
operationId: 'AppController_someEndpoint',
182+
parameters: [
183+
{
184+
in: 'query',
185+
name: 'enumField',
186+
required: false,
187+
schema: {
188+
items: { $ref: '#/components/schemas/Flag' },
189+
maxItems: 2,
190+
minItems: 1,
191+
type: 'array',
192+
},
193+
},
194+
],
195+
responses: { '200': { description: '' } },
196+
});
197+
});
198+
156199
it('accepts enum arrays', async () => {
157200
expect(await input(Test, { enumField: ['a', 'b'] })).toStrictEqual(
158201
Result.ok(make(Test, { enumField: ['a', 'b'] })),

src/decorators/is-enum.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApiPropertyOptions, getSchemaPath } from '@nestjs/swagger';
12
import { IsIn } from 'class-validator';
23

34
import { compose, PropertyOptions } from '../core';
@@ -20,18 +21,24 @@ export const IsEnum = <T extends number | string>({
2021
}: PropertyOptions<T, { enum: EnumOptions<T> }>): PropertyDecorator => {
2122
const { enumValues, enumName } = getEnumNameAndValues(enumOptions);
2223

23-
return compose(
24-
{ type: typeof enumValues[0], enum: enumValues, enumName },
25-
base,
26-
IsIn(enumValues, { each: !!base.isArray }),
27-
);
24+
let apiPropertyOptions: ApiPropertyOptions = {
25+
type: typeof enumValues[0],
26+
enum: enumValues,
27+
enumName,
28+
};
29+
// workaround for Nest's @Query handling
30+
if (typeof base.isArray === 'object' && base.isArray.force) {
31+
apiPropertyOptions = { type: 'array', items: { $ref: getSchemaPath(enumName) } };
32+
}
33+
34+
return compose(apiPropertyOptions, base, IsIn(enumValues, { each: !!base.isArray }));
2835
};
2936

3037
function getEnumNameAndValues<T extends number | string>(
3138
e: EnumOptions<T>,
3239
): {
3340
enumValues: T[];
34-
enumName?: string;
41+
enumName: string;
3542
} {
3643
const keys = Object.keys(e);
3744
if (keys.length !== 1) {

tests/helpers.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { INestApplication, Type } from '@nestjs/common';
1+
import { INestApplication, Module, Type } from '@nestjs/common';
22
import { NestFactory } from '@nestjs/core';
33
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
44
import type {
@@ -47,14 +47,19 @@ function getValidationError(error: ValidationError): string {
4747
export async function generateSchemas(
4848
models: Type[],
4949
): Promise<Record<string, SchemaObject | ReferenceObject>> {
50+
const spec = await generateSpec([], models);
51+
52+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
53+
return spec.components!.schemas!;
54+
}
55+
56+
export async function generateSpec(controllers: Type[], extraModels: Type[] = []) {
57+
@Module({ controllers })
5058
class AppModule {}
5159

52-
const spec = SwaggerModule.createDocument(
60+
return SwaggerModule.createDocument(
5361
await NestFactory.create<INestApplication>(AppModule, { logger: false }),
5462
new DocumentBuilder().build(),
55-
{ extraModels: models },
63+
{ extraModels },
5664
);
57-
58-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
59-
return spec.components!.schemas!;
6065
}

0 commit comments

Comments
 (0)