Skip to content

Commit ea25180

Browse files
committed
params: Add cast array to expected array query param
1 parent 66c33d4 commit ea25180

File tree

6 files changed

+101
-2
lines changed

6 files changed

+101
-2
lines changed

src/ActionParameterHandler.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,11 @@ export class ActionParameterHandler<T extends BaseDriver> {
130130
case 'string':
131131
case 'boolean':
132132
case 'date':
133-
return this.normalizeStringValue(value, param.name, param.targetName);
133+
const normalizedValue = this.normalizeStringValue(value, param.name, param.targetName);
134+
return param.isArray ? [normalizedValue] : normalizedValue;
134135
}
136+
} else if (Array.isArray(value)) {
137+
return value.map(v => this.normalizeStringValue(v, param.name, param.targetName));
135138
}
136139

137140
// if target type is not primitive, transform and validate it
@@ -188,7 +191,7 @@ export class ActionParameterHandler<T extends BaseDriver> {
188191
*/
189192
protected parseValue(value: any, paramMetadata: ParamMetadata): any {
190193
if (typeof value === 'string') {
191-
if (paramMetadata.type === 'queries' && paramMetadata.targetName === 'array') {
194+
if (['queries', 'query'].includes(paramMetadata.type) && paramMetadata.targetName === 'array') {
192195
return [value];
193196
} else {
194197
try {

src/decorator-options/ParamOptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ export interface ParamOptions {
3232
* Explicitly set type which should be used for param to perform transformation.
3333
*/
3434
type?: any;
35+
36+
/**
37+
* Force value to be cast as an array.
38+
*/
39+
isArray?: boolean;
3540
}

src/decorator/QueryParam.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function QueryParam(name: string, options?: ParamOptions): Function {
1818
classTransform: options ? options.transform : undefined,
1919
explicitType: options ? options.type : undefined,
2020
validate: options ? options.validate : undefined,
21+
isArray: options?.isArray ?? false,
2122
});
2223
};
2324
}

src/metadata/ParamMetadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ export class ParamMetadata {
7878
*/
7979
transform: (action: Action, value?: any) => Promise<any> | any;
8080

81+
/**
82+
* If true, string values are cast to arrays
83+
*/
84+
isArray?: boolean;
85+
8186
/**
8287
* Additional parameter options.
8388
* For example it can be uploader middleware options or body-parser middleware options.
@@ -113,6 +118,7 @@ export class ParamMetadata {
113118
this.transform = args.transform;
114119
this.classTransform = args.classTransform;
115120
this.validate = args.validate;
121+
this.isArray = args.isArray;
116122

117123
if (args.explicitType) {
118124
this.targetType = args.explicitType;

src/metadata/args/ParamMetadataArgs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,9 @@ export interface ParamMetadataArgs {
6666
* Explicitly set type which should be used for Body to perform transformation.
6767
*/
6868
explicitType?: any;
69+
70+
/**
71+
* Explicitly tell that the QueryParam is an array to force routing-controller to cast it
72+
*/
73+
isArray?: boolean;
6974
}

test/functional/action-params.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe(``, () => {
3838
let queryParamSortBy: string | undefined,
3939
queryParamCount: string | undefined,
4040
queryParamLimit: number | undefined,
41+
queryParamValues: any[] | undefined,
4142
queryParamShowAll: boolean | undefined,
4243
queryParamFilter: Record<string, any> | undefined;
4344
let queryParams1: { [key: string]: any } | undefined,
@@ -239,6 +240,30 @@ describe(``, () => {
239240
return `<html><body>${limit}</body></html>`;
240241
}
241242

243+
@Get('/photos-query-param-string-array')
244+
getPhotosWithMultipleStringValuesRequired(
245+
@QueryParam('multipleStringValues', { required: true }) values: string[]
246+
): string {
247+
queryParamValues = values;
248+
return `<html><body>${values}</body></html>`;
249+
}
250+
251+
@Get('/photos-query-param-number-array')
252+
getPhotosWithMultipleNumberValuesRequired(
253+
@QueryParam('multipleNumberValues', { required: true, type: Number, isArray: true }) values: number[]
254+
): string {
255+
queryParamValues = values;
256+
return `<html><body>${values}</body></html>`;
257+
}
258+
259+
@Get('/photos-query-param-date-array')
260+
getPhotosWithMultipleDateValuesRequired(
261+
@QueryParam('multipleDateValues', { required: true, type: Date, isArray: true }) values: Date[]
262+
): string {
263+
queryParamValues = values;
264+
return `<html><body>${values}</body></html>`;
265+
}
266+
242267
@Get('/photos-with-json')
243268
getPhotosWithJsonParam(
244269
@QueryParam('filter', { parse: true }) filter: { keyword: string; limit: number }
@@ -621,6 +646,60 @@ describe(``, () => {
621646
expect(queryParamShowAll).toEqual(true);
622647
});
623648

649+
it('@QueryParam should give an array of string with only one query parameter', async () => {
650+
expect.assertions(3);
651+
const response = await axios.get('/photos-query-param-string-array?multipleStringValues=a');
652+
expect(response.status).toEqual(HttpStatusCodes.OK);
653+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
654+
expect(queryParamValues).toEqual(['a']);
655+
});
656+
657+
it('@QueryParam should give an array of string with multiple query parameters', async () => {
658+
expect.assertions(3);
659+
const response = await axios.get(
660+
'/photos-query-param-string-array?multipleStringValues=a&multipleStringValues=b&multipleStringValues=b'
661+
);
662+
expect(response.status).toEqual(HttpStatusCodes.OK);
663+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
664+
expect(queryParamValues).toEqual(['a', 'b', 'b']);
665+
});
666+
667+
it('@QueryParam should give an array of number with only one query parameter', async () => {
668+
expect.assertions(3);
669+
const response = await axios.get('/photos-query-param-number-array?multipleNumberValues=1');
670+
expect(response.status).toEqual(HttpStatusCodes.OK);
671+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
672+
expect(queryParamValues).toEqual([1]);
673+
});
674+
675+
it('@QueryParam should give an array of number with multiple query parameters', async () => {
676+
expect.assertions(3);
677+
const response = await axios.get(
678+
'/photos-query-param-number-array?multipleNumberValues=1&multipleNumberValues=2&multipleNumberValues=2'
679+
);
680+
expect(response.status).toEqual(HttpStatusCodes.OK);
681+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
682+
expect(queryParamValues).toEqual([1, 2, 2]);
683+
});
684+
685+
it('@QueryParam should give an array of date with only one query parameter', async () => {
686+
expect.assertions(3);
687+
const response = await axios.get('/photos-query-param-date-array?multipleDateValues=2021-01-01');
688+
expect(response.status).toEqual(HttpStatusCodes.OK);
689+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
690+
expect(queryParamValues).toEqual([new Date('2021-01-01')]);
691+
});
692+
693+
it('@QueryParam should give an array of date with multiple query parameters', async () => {
694+
expect.assertions(3);
695+
const response = await axios.get(
696+
'/photos-query-param-date-array?multipleDateValues=2021-01-01&multipleDateValues=2020-01-01&multipleDateValues=2021-05-01'
697+
);
698+
expect(response.status).toEqual(HttpStatusCodes.OK);
699+
expect(response.headers['content-type']).toEqual('text/html; charset=utf-8');
700+
expect(queryParamValues).toEqual([new Date('2021-01-01'), new Date('2020-01-01'), new Date('2021-05-01')]);
701+
});
702+
624703
it('@QueryParam when required params must be provided and they should not be empty', async () => {
625704
expect.assertions(6);
626705
let response = await axios.get('/photos-with-required?limit=0');

0 commit comments

Comments
 (0)