-
Notifications
You must be signed in to change notification settings - Fork 393
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
QueryParam() doesn't throw error, when trying to cast non numerical value into number #234
Comments
Param type is only used to transform and validate if it's a class type. For arrays and primitive values there is no automatic validation. You should consider using |
Then shouldn't it left |
Casting is because query params are always a string in express/koa but they might be a number or boolean, like |
I think we can add error throw if validation is failed - no need to use class-validator for such simple use case. Ideally |
For me this case works as intended - dev expect number, client send string = you receive NaN because string is not a number. For other cases like casting "5" to boolean we should throw NormalizeError as it looks like the request is invalid. I would like to do a PR with extended and better normalization than the one we have now - it was discussed already. So to sum up:
Does it makes sense, @pleerock @NoNameProvided? 😉 |
Sounds good. I want to comment only last proposal:
no need to introduce a new decorator, we can do |
As I commented on the PR as well:
|
Yes I agree there. Its too risky to automatically convert |
I am talking about this: class ReuqestQuery {
@IsPositive()
limit: number;
@IsPositive({ each: true })
@Type(() => Number)
cityId: number[];
} Here we need type decorator because reflected type is generic Array which isn't enough to normalize it. |
I really like the direction this discussion is headed. Just wanted to note that this is very similar to what was discussed in my original proposal #122, the key difference being that here only the query parameters are being taken into account. I'd suggest that we should in fact introduce a new decorator that allows you to populate parameters from anywhere in the request object (perhaps something like For a given property the default lookup order could be export class GetAllUsers {
// uses: req.params['q'] || req.body['q'] || req.query['q']
@Request("q") // <- "catch all" property decorator
search: string;
// uses: req.params['customer'] || req.body['customer'] || req.query['customer']
// (default behavior with no decorator is equivalent to using "catch all")
customer: string;
// uses: req.query['skip']
@QueryParam("skip")
from: number;
// uses: req.query['limit']
@QueryParam("limit")
size: number;
// uses: req.query['flags']
@QueryParam()
@Type(() => Number)
flags: number[];
} So, while I agree support for @JsonController()
export class UserController {
@Get("/users")
getAll(
// transform request object into class
@Request({ type: GetAllUsers }) request: GetAllUsers,
// pull `foo` field from anywhere in the request
@Request('foo') data: string,
) {
console.log(data === (req.params['foo'] || req.body['foo'] || req.query['foo'])) // -> true
console.log(request instanceof GetAllUsers) // -> true
}
} |
// uses: req.params['q'] || req.body['q'] || req.query['q']
@Request("q") // <- "catch all" property decorator I think that this is really misleading for users. The generic client idea is good but I think that we should go by the more flexible way which is generating classes code. The classes would have matching method name, so no need for |
@19majkel94 I think support for extracting parameters from various request options is pretty important. If we can find a way to support multiple decorators on the same property I'd be fine with that, but I think it would pose challenges in certain cases. Consider: export class GetAllUsers {
@Param("q", { required: true })
@QueryParam("q", { required: true })
search: string;
} The above scenario leads to some ambiguity as to how the parameters will be validated. We want the export class GetAllUsers {
@Request("q", {
required: true,
allow: [ ParamType.Param, ParamType.Query ]
})
search: string;
} This @QueryParam("q")
// equivalent to:
@Request("q", { allow: ParamType.Query })
@Param("id", { required: true })
// equivalent to:
@Request("id", { required: true, allow: ParamType.Param }) |
import { Request } from "routing-controllers";
import { Request } from "express";
//...
method(@Request() request: Request) {
const user = request.user;
//...
} And I think that using the same name for query/body/route parameter is an awful idea. I don't know who does it and in what use cases it might help. |
@19majkel94 totally open to suggestions on the name, I agree
We do this a lot, actually. Examples include:
That said, I agree using the exact same name for various param types is probably the less common case, so I think the signature should be something like this instead: @Request({
required: boolean,
allow: ParamType | ParamType[] | { [key: ParamType]: string | string[] | boolean }
}) class {
@Request({
required: true,
allow: ParamType.Query
})
public apiKey: string; // req.query.apiKey
@Request({
required: true,
allow: {
[ParamType.Query]: true
}
})
public apiKey: string; // req.query.apiKey (same as previous)
@Request({
required: true,
allow: {
[ParamType.Query]: [ 'api_key', 'apiKey' ],
[ParamType.Body]: true
[ParamType.Header]: 'X-Auth-Api-Key',
}
})
public apiKey: string; // req.query.api_key || req.query.apiKey || req.body.apiKey || req.header('x-auth-api-key')
} |
I would call that a bad design, everything should have one source of truth. Encouraging to mix things up is not a good idea in my opinion. |
A better example than the ones I originally provided is when you need to support both nested and top-level routes that map to the same request:
@NoNameProvided as mentioned above, I agree that this is not the common case. I think the modified proposal above (#234 (comment)) better encapsulates all use cases by requiring you to specify the allowed name(s) for each param type. By removing the optional "name" as the first parameter, it no longer "encourages mixing things up", but does still support that use case when needed. |
With #289 the So your use case is that you have resource (e.g. category) and some other resource (e.g. product) has a many-to-one relation with this category. So your rest api support two ways of fetching data:
where both endpoints return the list of products of category with id 5. And you create controllers not per path but per feature, so you would like to achieve: @JsonController()
class ProductsController {
@Get("/products")
@Get("/categories/:categoryId/products")
getProductsByCategory(
@Param("categoryId", {
required: true, // should be default true
allow: [ParamType.Query, ParamType.Path],
})
categoryId: number,
) {
return this.productsRepository.getAll({ categoryId });
}
} I think it's a good use case that allow us to reuse controllers actions. @marshall007 go ahead and create a proposal in new issue where we could discuss details. |
Closing this via #289. Will be released in 0.8.0-beta.1 - |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
For example:
Get('/products') async list(@QueryParam('categoryId') categoryId: number)
categoryId will become NaN with
?categoryId=someString
instead of throwing casting errorThe text was updated successfully, but these errors were encountered: