Yob
Yob

Reputation: 285

Joi validation - allow field to be optional but when supplied must be a positive integer

I have a field in a JOI schema that I would like to be optional (i.e. undefined is accepted, and null is also accepted), however if a value for it is supplied, it must be a positive integer. How might I go about achieving this?

Here is what I have tried so far, with the field to validate being "capacity" however it does not seem to work, it appears the ".when" statement is just being ignored:

const divebarSchema = joi.object({
  divebar: joi
    .object({
      title: joi.string().required(),
      capacity: joi.optional().allow(null),
      description: joi.string().required(),
      location: joi.string().required(),
      image: joi.string().optional().allow(""),
      map: joi.string().optional().allow(""),
    })
    .required()
    .when(joi.object({ capacity: joi.exist() }), {
      then: joi.object({ capacity: joi.number().integer().min(0) }),
    }),
});

Before the above I originally had no .when and instead the capacity rule was:

  capacity: joi.number().optional().allow(null).integer().min(0),

However that also did not work, it kept throwing the error "must be a number" when submitting a null value.

Upvotes: 8

Views: 13390

Answers (3)

KushalSeth
KushalSeth

Reputation: 4729

To Allow null:

capacity: Joi.string().max(255).allow(null)

To Allow empty string:

capacity: Joi.string().max(255).allow('')

To Allow both:

capacity: Joi.string().max(255).allow(null, '')

Upvotes: 0

Zachary Haber
Zachary Haber

Reputation: 11037

You need to use any.empty rather than any.allow to make it so that null ends up considered as an empty value. This will mean that null gets stripped out of the resulting value. If you don't want null capacity to be stripped from the resulting object, you can use allow(null) as mentioned in the other answer.

I've included a snippet of code that uses both for comparison and shows the validation rules fully applying for both.

const divebarSchemaEmpty = joi.object({
  divebar: joi
    .object({
      title: joi.string().required(),
      capacity: joi.number().empty(null).integer().min(0),
      description: joi.string().required(),
      location: joi.string().required(),
      image: joi.string().allow(""),
      map: joi.string().allow(""),
    })
    .required(),
});

const divebarSchemaAllow = joi.object({
  divebar: joi
    .object({
      title: joi.string().required(),
      capacity: joi.number().allow(null).integer().min(0),
      description: joi.string().required(),
      location: joi.string().required(),
      image: joi.string().allow(""),
      map: joi.string().allow(""),
    })
    .required(),
});

const baseObject = {
  divebar: {
    title: 'capacity',
    description: 'test',
    location: 'here',
  }
};
const schemaRuns = [{
  title: 'Empty',
  schema: divebarSchemaEmpty
}, {
  title: 'Allow',
  schema: divebarSchemaAllow
}];
const runs = [{
    title: 'null capacity',
    data: {
      capacity: null
    }
  },
  {
    title: 'missing capacity',
    data: {}
  },
  {
    title: 'undefined capacity',
    data: {
      capacity: undefined
    }
  },
  {
    title: 'positive capacity',
    data: {
      capacity: 5
    }
  },
  {
    title: 'negative capacity',
    data: {
      capacity: -1
    },
    fails: true
  },
  {
    title: 'float capacity',
    data: {
      capacity: 0.25
    },
    fails: true
  }
];

for (const {
    title: baseTitle,
    data: override,
    fails
  } of runs) {
  for (const {
      title: schemaTitle,
      schema
    } of schemaRuns) {
    const title = `${schemaTitle}->${baseTitle}`;
    const data = { ...baseObject,
      divebar: { ...baseObject.divebar,
        ...override,
        title
      }
    };
    try {
      const result = joi.attempt(data, schema);
      if (fails) {
        throw new Error(`${title} succeeded validation when expected to fail`)
      }
      console.log(`${title} passed with data`, result);
    } catch (err) {
      if (joi.isError(err)) {
        if (fails) {
          console.log(`${title} passed with error object`, err)
        } else {
          // unexpected error!
          console.error(err);
        }
      } else {
        console.error(err);
      }
    }
  }
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/joi-browser.min.js"></script>

Upvotes: 0

shyam
shyam

Reputation: 9368

According to Joi documentation optional does not include null you need to use allow

capacity: Joi.number().optional().integer().min(0).allow(null)

Upvotes: 5

Related Questions