typescript-rest icon indicating copy to clipboard operation
typescript-rest copied to clipboard

How to throw HttpExceptions at runtime if a parameter (e.g. PathParam, BodyParam) has wrong type

Open wiesson opened this issue 8 years ago • 4 comments

Little example

@Path('/hello')
class HelloService {
    @Path(':name')
    @GET
    sayHello(@PathParam('name') name: number): string {        
        return `Hello ${name}`;
    }
}

How could I check the parameters if the type is wrong (in the example above, name is number) or a required param is missing and throw e.g. bad request. Any hint, how could I implement that nicely? :)

Further, I would use that information within Swagger/OpenAPI.

wiesson avatar Oct 18 '17 10:10 wiesson

There is also some problems with function default values when using Path or Query params (atleast): example

@Path('/news')
@GET
async getNews( @QueryParam('limit') limit: number = 10) {
	console.log(limit) // prints 0
	return new Promise((resolve, reject) => {
		r.table('FeedItem').limit(limit).run(db.connection, (err, cursor) => {
			cursor.toArray((error, result) => {
				resolve(result)
			})
		})
	})
}

axelauvinen avatar Oct 18 '17 13:10 axelauvinen

Hi, @wiesson Typescript-rest does not perform any parameter validation yet, but I realy like the idea to add this support.

We can use typescript-rest-swagger to generate the swagger documentation from the source code that uses typescript-rest decorators. It is possible to implement something that does something similar, or even that uses the generated swagger doc to validate the parameters.

I am very busy these days, but I can try to add this support later.

In the meanwhile, you can use another validation library, like ajv or joi to validate your parameters on your endpoint methods.

@axelauvinen, I will try to reproduce this problem. It should work

thiagobustamante avatar Oct 19 '17 16:10 thiagobustamante

@wiesson ,

See this example using JOI:

    @PUT
    @Path(':login')
    @swagger.Security('Bearer')
    updateUser(@PathParam('login') login: string, user: UserData): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            user.login = login;
            validateUser(user)
                .then((validUser: UserData) => this.service.update(validUser))
                .then(() => resolve())
                .catch(reject);
        });
    }

    validateUser(user: UserData) {
        return new Promise((resolve, reject) => {
            Joi.validate(user, userValidatorSchema, (err, value) => {
                if (err) {
                    reject(new ValidationError(err));
                } else {
                    resolve(value);
                }
            });
        });
    }

And the joi schema:

const userValidatorSchema = Joi.object().keys({
    email: Joi.string().email(),
    login: Joi.string().required(),
    name: Joi.string().required(),
    password: Joi.string(),
    roles: Joi.array().items(Joi.string()).unique()
});

thiagobustamante avatar Oct 19 '17 16:10 thiagobustamante

@thiagobustamante - thanks for the example! That looks promising but my initial intention was to keep the controller lightweight / "clean" from validation.

I also thought about creating some kind of model / class for each (rest) endpoint and then match those against the request object.

wiesson avatar Oct 19 '17 21:10 wiesson