Skip to content

Commit d3bad9d

Browse files
authoredJul 1, 2024··
Merge pull request #2870 from arkraft/fix/array-enums-queries-or-params
fix(swagger): Query Enums with isArray and enumName have type 'array' in schema
2 parents 6c5a7f6 + d74136b commit d3bad9d

File tree

3 files changed

+108
-48
lines changed

3 files changed

+108
-48
lines changed
 

‎lib/services/schema-object-factory.ts

+35-19
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,24 @@ export class SchemaObjectFactory {
167167

168168
const schemaCombinators = ['oneOf', 'anyOf', 'allOf'];
169169
let keyOfCombinators = '';
170-
if (schemaCombinators.some((_key) => { keyOfCombinators = _key; return _key in property; })) {
171-
if (((property as SchemaObjectMetadata)?.type === 'array' || (property as SchemaObjectMetadata).isArray) && keyOfCombinators) {
172-
(property as SchemaObjectMetadata).items = {};
173-
(property as SchemaObjectMetadata).items[keyOfCombinators] = property[keyOfCombinators];
174-
delete property[keyOfCombinators];
175-
} else {
176-
delete (property as SchemaObjectMetadata).type;
177-
}
170+
if (
171+
schemaCombinators.some((_key) => {
172+
keyOfCombinators = _key;
173+
return _key in property;
174+
})
175+
) {
176+
if (
177+
((property as SchemaObjectMetadata)?.type === 'array' ||
178+
(property as SchemaObjectMetadata).isArray) &&
179+
keyOfCombinators
180+
) {
181+
(property as SchemaObjectMetadata).items = {};
182+
(property as SchemaObjectMetadata).items[keyOfCombinators] =
183+
property[keyOfCombinators];
184+
delete property[keyOfCombinators];
185+
} else {
186+
delete (property as SchemaObjectMetadata).type;
187+
}
178188
}
179189
return property as ParameterObject;
180190
});
@@ -203,7 +213,8 @@ export class SchemaObjectFactory {
203213
if (!propertiesWithType) {
204214
return '';
205215
}
206-
const extensionProperties = Reflect.getMetadata(DECORATORS.API_EXTENSION, type) || {};
216+
const extensionProperties =
217+
Reflect.getMetadata(DECORATORS.API_EXTENSION, type) || {};
207218
const typeDefinition: SchemaObject = {
208219
type: 'object',
209220
properties: mapValues(keyBy(propertiesWithType, 'name'), (property) =>
@@ -273,7 +284,10 @@ export class SchemaObjectFactory {
273284
: undefined;
274285

275286
schemas[enumName] = {
276-
type: param.schema?.['type'] ?? 'string',
287+
type:
288+
(param.isArray
289+
? param.schema?.['items']?.['type']
290+
: param.schema?.['type']) ?? 'string',
277291
enum: _enum
278292
};
279293
}
@@ -301,15 +315,17 @@ export class SchemaObjectFactory {
301315
const $ref = getSchemaPath(enumName);
302316

303317
// Allow given fields to be part of the referenced enum schema
304-
const additionalParams = ['description', 'deprecated', 'default']
305-
const additionalFields = additionalParams.reduce((acc, param) =>
306-
({...acc, ...(metadata[param] && { [param]: metadata[param] })}), {});
307-
308-
const enumType: string = (
309-
metadata.isArray
310-
? metadata.items['type']
311-
: metadata.type
312-
) ?? 'string';
318+
const additionalParams = ['description', 'deprecated', 'default'];
319+
const additionalFields = additionalParams.reduce(
320+
(acc, param) => ({
321+
...acc,
322+
...(metadata[param] && { [param]: metadata[param] })
323+
}),
324+
{}
325+
);
326+
327+
const enumType: string =
328+
(metadata.isArray ? metadata.items['type'] : metadata.type) ?? 'string';
313329

314330
schemas[enumName] = {
315331
type: enumType,

‎test/explorer/swagger-explorer.spec.ts

+28-28
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ describe('SwaggerExplorer', () => {
965965
})
966966
@ApiQuery({ name: 'order', enum: QueryEnum })
967967
@ApiQuery({ name: 'page', enum: ['d', 'e', 'f'], isArray: true })
968-
find(): Promise<Foo[]> {
968+
find(@Param('objectId') objectId: ParamEnum): Promise<Foo[]> {
969969
return Promise.resolve([]);
970970
}
971971
}
@@ -979,7 +979,7 @@ describe('SwaggerExplorer', () => {
979979
})
980980
@ApiQuery({ name: 'order', enum: QueryEnum })
981981
@ApiQuery({ name: 'page', enum: ['d', 'e', 'f'] })
982-
find(): Promise<Foo[]> {
982+
find(@Param('objectId') objectId: ParamEnum, @Query('order') order: QueryEnum, @Query('page') page: 'd' | 'e' | 'f'): Promise<Foo[]> {
983983
return Promise.resolve([]);
984984
}
985985
}
@@ -999,7 +999,7 @@ describe('SwaggerExplorer', () => {
999999
enumName: 'QueryEnum',
10001000
isArray: true
10011001
})
1002-
findBar(): Promise<Foo> {
1002+
findBar(@Param('objectId') objectId: ParamEnum, @Query('order') order: QueryEnum, @Query('page') page: QueryEnum[]): Promise<Foo> {
10031003
return Promise.resolve(null);
10041004
}
10051005
}
@@ -1012,7 +1012,7 @@ describe('SwaggerExplorer', () => {
10121012
enum: [1, 2, 3],
10131013
enumName: 'NumberEnum'
10141014
})
1015-
findBar(): Promise<Foo> {
1015+
findBar(@Param('objectId') objectId: number): Promise<Foo> {
10161016
return Promise.resolve(null);
10171017
}
10181018
}
@@ -1037,6 +1037,15 @@ describe('SwaggerExplorer', () => {
10371037
'/globalPrefix/v3/modulePath/foos/{objectId}'
10381038
);
10391039
expect(routes[0].root!.parameters).toEqual([
1040+
{
1041+
in: 'path',
1042+
name: 'objectId',
1043+
required: true,
1044+
schema: {
1045+
type: 'string',
1046+
enum: ['a', 'b', 'c']
1047+
}
1048+
},
10401049
{
10411050
in: 'query',
10421051
name: 'page',
@@ -1057,15 +1066,6 @@ describe('SwaggerExplorer', () => {
10571066
type: 'number',
10581067
enum: [1, 2, 3]
10591068
}
1060-
},
1061-
{
1062-
in: 'path',
1063-
name: 'objectId',
1064-
required: true,
1065-
schema: {
1066-
type: 'string',
1067-
enum: ['a', 'b', 'c']
1068-
}
10691069
}
10701070
]);
10711071
});
@@ -1083,12 +1083,12 @@ describe('SwaggerExplorer', () => {
10831083

10841084
expect(routes[0].root!.parameters).toEqual([
10851085
{
1086-
in: 'query',
1087-
name: 'page',
1086+
in: 'path',
1087+
name: 'objectId',
10881088
required: true,
10891089
schema: {
10901090
type: 'string',
1091-
enum: ['d', 'e', 'f']
1091+
enum: ['a', 'b', 'c']
10921092
}
10931093
},
10941094
{
@@ -1101,12 +1101,12 @@ describe('SwaggerExplorer', () => {
11011101
}
11021102
},
11031103
{
1104-
in: 'path',
1105-
name: 'objectId',
1104+
in: 'query',
1105+
name: 'page',
11061106
required: true,
11071107
schema: {
11081108
type: 'string',
1109-
enum: ['a', 'b', 'c']
1109+
enum: ['d', 'e', 'f']
11101110
}
11111111
}
11121112
]);
@@ -1126,14 +1126,11 @@ describe('SwaggerExplorer', () => {
11261126

11271127
expect(routes[0].root!.parameters).toEqual([
11281128
{
1129-
in: 'query',
1130-
name: 'page',
1129+
in: 'path',
1130+
name: 'objectId',
11311131
required: true,
11321132
schema: {
1133-
type: 'array',
1134-
items: {
1135-
$ref: '#/components/schemas/QueryEnum'
1136-
}
1133+
$ref: '#/components/schemas/ParamEnum'
11371134
}
11381135
},
11391136
{
@@ -1145,11 +1142,14 @@ describe('SwaggerExplorer', () => {
11451142
}
11461143
},
11471144
{
1148-
in: 'path',
1149-
name: 'objectId',
1145+
in: 'query',
1146+
name: 'page',
11501147
required: true,
11511148
schema: {
1152-
$ref: '#/components/schemas/ParamEnum'
1149+
type: 'array',
1150+
items: {
1151+
$ref: '#/components/schemas/QueryEnum'
1152+
}
11531153
}
11541154
}
11551155
]);

‎test/services/schema-object-factory.spec.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { ApiExtension, ApiProperty } from '../../lib/decorators';
2-
import { SchemasObject } from '../../lib/interfaces/open-api-spec.interface';
2+
import {
3+
BaseParameterObject,
4+
SchemasObject
5+
} from '../../lib/interfaces/open-api-spec.interface';
36
import { ModelPropertiesAccessor } from '../../lib/services/model-properties-accessor';
47
import { SchemaObjectFactory } from '../../lib/services/schema-object-factory';
58
import { SwaggerTypesMapper } from '../../lib/services/swagger-types-mapper';
69
import { CreateUserDto } from './fixtures/create-user.dto';
10+
import { ParamWithTypeMetadata } from '../../lib/services/parameter-metadata-accessor';
711

812
describe('SchemaObjectFactory', () => {
913
let modelPropertiesAccessor: ModelPropertiesAccessor;
@@ -353,4 +357,44 @@ describe('SchemaObjectFactory', () => {
353357
expect(schemas).toEqual({ MyEnum: { enum: [1, 2, 3], type: 'number' } });
354358
});
355359
});
360+
361+
describe('createEnumParam', () => {
362+
it('should create an enum schema definition', () => {
363+
const params: ParamWithTypeMetadata & BaseParameterObject = {
364+
required: true,
365+
isArray: false,
366+
enumName: 'MyEnum',
367+
enum: ['a', 'b', 'c']
368+
};
369+
const schemas = {};
370+
schemaObjectFactory.createEnumParam(params, schemas);
371+
372+
expect(schemas['MyEnum']).toEqual({
373+
enum: ['a', 'b', 'c'],
374+
type: 'string'
375+
});
376+
});
377+
378+
it('should create an enum schema definition for an array', () => {
379+
const params: ParamWithTypeMetadata & BaseParameterObject = {
380+
required: true,
381+
isArray: true,
382+
enumName: 'MyEnum',
383+
schema: {
384+
type: 'array',
385+
items: {
386+
type: 'string',
387+
enum: ['a', 'b', 'c']
388+
}
389+
}
390+
};
391+
const schemas = {};
392+
schemaObjectFactory.createEnumParam(params, schemas);
393+
394+
expect(schemas['MyEnum']).toEqual({
395+
enum: ['a', 'b', 'c'],
396+
type: 'string'
397+
});
398+
});
399+
});
356400
});

0 commit comments

Comments
 (0)
Please sign in to comment.