Search code examples
node.jsvalidationjoi

How can I run a custom validation against a different key in my schema?


I'm using Joi to validate user-submitted form data. The data contains bank details: the relevant ones here are accountNumber and sortCode. I need to take the value of both inputs and run them through an external API to confirm they are valid together.

I'm struggling though to write a custom Joi extension which can get the value of another field in the data. Here's a simplified version of what I've tried:

const baseJoi = require('joi');

const bankDetails = joi => {
    return {
        name: 'bankDetails',
        base: joi.number().required(),
        rules: [
            {
                name: 'accountNumber',
                params: {
                    number: joi.number().required()
                },
                validate(params, value, state, options) {
                    console.log(value); // returns 12-34-46 (sortCode)
                    console.log(params.number); // returns a [Function: ref] object
                    console.log(state.parent.accountNumber); // returns 88888888, but feels "wrong"
                }
            }
        ]
    };
};

const Joi = baseJoi.extend([bankDetails]);

const validateBankDetails = () => {
    return Joi.bankDetails().accountNumber(Joi.ref('accountNumber')).required();
};

const schema = Joi.object({
    accountNumber: Joi.number().required(),
    sortCode: validateBankDetails(),
});

I know I can use Joi.ref() inside Joi's own validators (eg. something like Joi.number().less(Joi.ref('max'))) but here I need to take the value of that key (accountNumber) and pass it through to another function (along with the sortCode value). Is there a Joi-approved way of doing this? I can get the value I want from the state parameters but this feels a bit of an anti-pattern.


Solution

  • Have you tried using a custom method on a plain old Joi object instead of an extension? I think it might get you what you're looking for in a more straightforward manner:

    const schema = Joi.object({
      accountNumber: Joi.number().required(),
      sortCode: Joi.number().required(),
    })
      .custom((obj, helpers) => {
        // you have access to the full object above.
        const { accountNumber, sortCode } = obj;
    
        // Do your validation
        const isValid = doSomething(accountNumber, sortCode);
    
        if (!isValid) {
          throw new Error('Invalid input');
        }
    
        // Just pass the object on through if things work.
        return obj;
      });