Search code examples
javascriptschemajoi

Joi, add extension type with argument


I want to add splitArray type to Joi which converts string to an array using method Array.split. As you know, Array.split can split by argument, and I want to pass this argument during creation of the schema and not during it's usage.

So, currently I have this extension that allows me to split string to an array during validation with the use of helpers:

const splitArrayExt = joi => ({
  base: joi.array(),
  coerce: (value, helpers) => ({
    value: value.split ? value.split(helpers.prefs.split ? helpers.prefs.split : /\s+/) : value
  }),
  type: 'splitArray',
)}

The problems is - I have to pass { split: <ANOTHER SPLIT ARG> } every time I call validate.

Is it possible to just do something like this:

const JoiBase = require('joi');
...
const Joi = JoiBase.extend(splitArrayExt);

const schema1 = Joi.splitArray().splitBy('|').items(Joi.string())
const schema2 = Joi.splitArray().splitBy(',').items(Joi.string())
schema1.validate('A|B,C') // values: [ 'A', 'B,C' ]
schema2.validate('A|B,C') // values: [ 'A|B', 'C' ]

Solution

  • So, anyway, I've figured it out:

    const customJoi = Joi.extend(joi => ({
      type: 'splitArray',  // add type with custom name
      base: joi.array(),   // let's base it on Joi.array
      messages: {          // this is required to yell if we didn't get a string before parse
        'split.base': '"value" must be a string',
      },
      coerce(value, helpers) {  // this executes before 'validate' method is called, so we can convert string to an array
        if (typeof(value) !== 'string') {
          return { errors: helpers.error('split.base') };
        }
        const rule = helpers.schema.$_getRule('splitBy');  // this is needed to determent delimiter (default: /\s+/)
        return { value: value.split(rule ? rule.args.delimiter : /\s+/) };
      },
      rules: {
        splitBy: {  // add new rule to set different delimiter from default
          convert: true,
          method(delimiter) {
            return this.$_addRule({ name: 'splitBy', args: { delimiter } });
          },
        },
      }
    }));
    
    ...
    
    // and voilà
    customJoi.splitArray().items(customJoi.number()).validate('1 2\n3\t4')
    // [ 1, 2, 3, 4 ]
    
    customJoi.splitArray().splitBy('|').validate('A|B,C');
    // [ 'A', 'B,C' ]
    
    customJoi.splitArray().splitBy(',').validate('A|B,C');
    // [ 'A|B', 'C' ]