Skip to content

feat: add cast array to expected array query params #677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,28 @@ getUsers(@QueryParam("limit") limit: number) {
}
```

You can use `isArray` option to get a query param array. This will cast the query param :

```typescript
@Get("/users/by-multiple-ids")
getUsers(@QueryParam("ids", { isArray: true}) ids: string[]) {
}
```

`GET /users/by-multiple-ids?ids=a` → `ids = ['a']`
`GET /users/by-multiple-ids?ids=a&ids=b` → `ids = ['a', 'b']`

You can combine use `isArray` option with `type` option to get a query param array of one type. This will cast the query param :

```typescript
@Get("/users/by-multiple-ids")
getUsers(@QueryParam("ids", { isArray: true, type: Number}) ids: number[]) {
}
```

`GET /users/by-multiple-ids?ids=1` → `ids = [1]`
`GET /users/by-multiple-ids?ids=1&ids=3.5` → `ids = [1, 3.5]`

If you want to inject all query parameters use `@QueryParams()` decorator.
The bigest benefit of this approach is that you can perform validation of the params.

Expand All @@ -402,12 +424,17 @@ class GetUsersQuery {
@IsBoolean()
isActive: boolean;

@IsArray()
@IsNumber(undefined, { each: true })
@Type(() => Number)
ids: number[];
}

@Get("/users")
getUsers(@QueryParams() query: GetUsersQuery) {
// here you can access query.role, query.limit
// and others valid query parameters
// query.ids will be an array, of numbers, even with one element
}
```

Expand Down Expand Up @@ -1535,7 +1562,7 @@ export class QuestionController {

| Signature | Example | Description |
| ---------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `@Authorized(roles?: string\|string[])` | `@Authorized("SUPER_ADMIN")` get() | Checks if user is authorized and has given roles on a given route. `authorizationChecker` should be defined in routing-controllers options. | |
| `@Authorized(roles?: string\|string[])` | `@Authorized("SUPER_ADMIN")` get() | Checks if user is authorized and has given roles on a given route. `authorizationChecker` should be defined in routing-controllers options. |
| `@CurrentUser(options?: { required?: boolean })` | get(@CurrentUser({ required: true }) user: User) | Injects currently authorized user. `currentUserChecker` should be defined in routing-controllers options. |
| `@Header(headerName: string, headerValue: string)` | `@Header("Cache-Control", "private")` get() | Allows to explicitly set any HTTP header returned in the response. |
| `@ContentType(contentType: string)` | `@ContentType("text/csv")` get() | Allows to explicitly set HTTP Content-Type returned in the response. |
Expand Down
19 changes: 14 additions & 5 deletions src/ActionParameterHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,13 @@ export class ActionParameterHandler<T extends BaseDriver> {
case 'string':
case 'boolean':
case 'date':
return this.normalizeStringValue(value, param.name, param.targetName);
const normalizedValue = this.normalizeStringValue(value, param.name, param.targetName);
return param.isArray ? [normalizedValue] : normalizedValue;
case 'array':
return [value];
}
} else if (Array.isArray(value)) {
return value.map(v => this.normalizeStringValue(v, param.name, param.targetName));
}

// if target type is not primitive, transform and validate it
Expand Down Expand Up @@ -188,10 +193,14 @@ export class ActionParameterHandler<T extends BaseDriver> {
*/
protected parseValue(value: any, paramMetadata: ParamMetadata): any {
if (typeof value === 'string') {
try {
return JSON.parse(value);
} catch (error) {
throw new ParameterParseJsonError(paramMetadata.name, value);
if (['queries', 'query'].includes(paramMetadata.type) && paramMetadata.targetName === 'array') {
return [value];
} else {
try {
return JSON.parse(value);
} catch (error) {
throw new ParameterParseJsonError(paramMetadata.name, value);
}
}
}
return value;
Expand Down
5 changes: 5 additions & 0 deletions src/decorator-options/ParamOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ export interface ParamOptions {
* Explicitly set type which should be used for param to perform transformation.
*/
type?: any;

/**
* Force value to be cast as an array.
*/
isArray?: boolean;
}
1 change: 1 addition & 0 deletions src/decorator/QueryParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function QueryParam(name: string, options?: ParamOptions): Function {
classTransform: options ? options.transform : undefined,
explicitType: options ? options.type : undefined,
validate: options ? options.validate : undefined,
isArray: options?.isArray ?? false,
});
};
}
6 changes: 6 additions & 0 deletions src/metadata/ParamMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ export class ParamMetadata {
*/
transform: (action: Action, value?: any) => Promise<any> | any;

/**
* If true, string values are cast to arrays
*/
isArray?: boolean;

/**
* Additional parameter options.
* For example it can be uploader middleware options or body-parser middleware options.
Expand Down Expand Up @@ -113,6 +118,7 @@ export class ParamMetadata {
this.transform = args.transform;
this.classTransform = args.classTransform;
this.validate = args.validate;
this.isArray = args.isArray;

if (args.explicitType) {
this.targetType = args.explicitType;
Expand Down
5 changes: 5 additions & 0 deletions src/metadata/args/ParamMetadataArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ export interface ParamMetadataArgs {
* Explicitly set type which should be used for Body to perform transformation.
*/
explicitType?: any;

/**
* Explicitly tell that the QueryParam is an array to force routing-controller to cast it
*/
isArray?: boolean;
}
Loading