-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Bug with using Joi.object() for route validation #3061
Comments
@saitheexplorer thanks for the detailed report. First worth checking: @hueniverse, is adding default route validation at connection/server level something we actually support? Scenario 1 is because hapi is trying to merge the properties from your plain object from the defaults into a Joi object, which doesn't work. If it is something we should support, we should check that we never try to recursively merge into a property of an object that contains a Joi object. Scenario 2 is related, the validation for |
seems just as easy to wrap a plain object in Joi.object and use Joi.concat to merge them together, no? |
For the sake of brainstorming, another idea is to have the ability to specify whether certain connection defaults should be shallow-copied versus merged, a la |
@nlf would const Joi = require('joi');
// Connection level
const schema1 = Joi.object({
api_key: Joi.string().required()
});
// Route level
const schema2 = Joi.object().keys({
email: Joi.string(),
user_id: Joi.string()
}).or('email', 'user_id');
const schema = schema1.concat(schema2);
const input = { email: '[email protected]' };
Joi.assert(input, schema); The output:
Not sure about others, but I would expect the 2nd to completely override the connection default and not have to specify an |
i guess it just raises the question of should behavior be merge or overwrite? i think (i haven't looked) currently it's overwrite for everything else, if that's the case you're right it shouldn't even bother concatting |
It's currently using this.settings = Hoek.applyToDefaultsWithShallow(base, route.config || {}, ['bind', 'validate.headers', 'validate.payload', 'validate.params', 'validate.query']); and some tests, along the lines of: it('overrides connection level blah...', (done) => {
const server = new Hapi.Server();
server.connection({
routes: {
validate: {
query: Joi.object({
a: Joi.string().required()
}),
options: {
abortEarly: false
}
}
}
});
const handler = function (request, reply) {
return reply('ok');
};
server.route({
method: 'GET',
path: '/',
handler
});
server.route({
method: 'GET',
path: '/other',
handler,
config: {
validate: {
query: Joi.object({
b: Joi.string().required()
})
}
}
});
server.inject({ url: '/', method: 'GET' }, (res1) => {
expect(res1.statusCode).to.equal(400);
expect(res1.result.message).to.equal('child "a" fails because ["a" is required]');
server.inject({ url: '/?a=1', method: 'GET' }, (res2) => {
expect(res2.statusCode).to.equal(200);
server.inject({ url: '/other', method: 'GET' }, (res3) => {
expect(res3.statusCode).to.equal(400);
expect(res3.result.message).to.equal('child "b" fails because ["b" is required]');
server.inject({ url: '/other?b=1', method: 'GET' }, (res4) => {
expect(res4.statusCode).to.equal(200);
done();
});
});
});
});
});
// Without patch to route.js, the first assertion fails with 'Expected 'child "b" fails because ["b" is required]' to equal specified value' because it inherits /other routes validation
// Expected 'child "b" fails because ["b" is required]' to equal specified value So as you say, the thing to establish is what we should do. Clearly merging with Joi objects (unless we use concat, which might be weird) is pretty fraught so the above seems to me like a reasonable idea. This would also solve the scenario 2 issue which is related to Hoek not cloning Joi objects. |
@mtharrison you really made me copy paste what should have been a PR? :-) This is a bug and the only valid solution is to override. |
I think I've encountered some unexpected behavior with how hapi and joi work together to validate a route. What I'm seeing could be two separate bugs, or side effects of the same bug. included some minimal test scenarios as well.
Scenario 1:
When initializing the server, I want to provide some default validation that will be present on every route. In
server.connection
, I pass my schema as a POJO wrapper around Joi rules: e.g.:In one of my routes, I want to use Joi to say one of two field must be present in the query, e.g.
email
oruser_id
. My route looks as follows:If I hit
/test
, the route hangs and hapi emits an error:This can be solved if I change the default joi route validation to use Joi.object() and not a POJO. However, this behavior could be improved - even if mixing Joi.object()/POJOs is not supported, can that be checked when the route is registered rather than when the route is requested?
Here is a gist with the bug reproduced with latest hapi & joi:
https://gist.github.com/saitheexplorer/e7b108895e37d86857f3
Scenario 2:
With the fix for scenario 1 in place (default validation is a Joi object), I add another route (
/another-test
) - this one does not require any validation other than the connection defaults.Request to
/test
without query (the original route with required query params) fails as expected. Requesting/another-test
without query, however, also fails - it saysemail
oruser_id
are required, despite having no validation for that route.If I change the server connection default validation back to a POJO, my second route replies fine with no validation errors, but because of Scenario 1, the first route now hangs again.
Gist with second route added: https://gist.github.com/saitheexplorer/72c0eef13cf6b9cb357f
I'm stuck if I want to use joi to handle the validation - any ideas? Happy to help with bugfixing if somebody can point me in the right direction.
Thanks!
The text was updated successfully, but these errors were encountered: