How to add custom validator function in Joi?
Asked Answered
C

4

33

I have Joi schema and want to add a custom validator for validating data which isn't possible with default Joi validators.

Currently, I'm using the version 16.1.7 of Joi

const method = (value, helpers) => {
  // for example if the username value is (something) then it will throw an 
  // error with the following message but it throws an error inside (value) 
  // object without error message. It should throw error inside the (error) 
  // object with a proper error message
        
  if (value === "something") {
    return new Error("something is not allowed as username");
  }

  return value; // Return the value unchanged
};
        
const createProfileSchema = Joi.object().keys({
  username: Joi.string()
    .required()
    .trim()
    .empty()
    .min(5)
    .max(20)
    .lowercase()
    .custom(method, "custom validation")
});
        
const { error, value } = createProfileSchema.validate({
  username: "something" 
});
        
console.log(value); // returns {username: Error}
console.log(error); // returns undefined

But I couldn't implement it the right way. I read Joi documents but it seems a little bit confusing to me. Can anyone help me to figure it out?

Criseyde answered 17/10, 2019 at 5:30 Comment(0)
D
37

Your custom method must be like this:

const method = (value, helpers) => {
  // for example if the username value is (something) then it will throw an 
  // error with the following message but it throws an error inside (value) 
  // object without error message. It should throw error inside the (error) 
  // object with a proper error message

  if (value === "something") {
    return helpers.error("any.invalid");
  }

  return value; // Return the value unchanged
};

Docs:

https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description

Output for value :

{ username: 'something' }

Output for error:

[Error [ValidationError]: "username" contains an invalid value] {
  _original: { username: 'something' },
  details: [
    {
      message: '"username" contains an invalid value',
      path: [Array],
      type: 'any.invalid',
      context: [Object]
    }
  ]
}
Duchamp answered 17/10, 2019 at 6:20 Comment(8)
@ShifutHossain I tried it and it gives the error [ValidationError]: "username" contains an invalid value]Duchamp
codesandbox.io/s/billowing-leaf-zwo19?fontsize=14 I've also tried it hereCriseyde
Did you try on your local computer?Duchamp
@ShifutHossain as I know joi is not compatible in browser, can you check in your node application?Duchamp
I added the output in to the answer, when used in a node app. Isn't it what you want?Duchamp
Yeah, now it's working on node environment. It was my mistake I always use Codesandbox for practicing.Criseyde
In codesandbox there is template project for node apps, but I didn't tried.Duchamp
Thanks, but how do you define a custom error text?Feudality
C
44
const Joi = require('@hapi/joi');
    
Joi.object({
  password: Joi
    .string()
    .custom((value, helper) => {
      if (value.length < 8) {
        return helper.message("Password must be at least 8 characters long");
      }

      return true;
    })
}).validate({
    password: '1234'
});
Certainty answered 6/5, 2020 at 15:8 Comment(4)
Hmm, types say the 1st arg of helper.message needs to be Record<string, string>, not string...Concentrate
You can ignore the type error... but I think it should return value instead of true at the endBaluchi
To fix type issue return helper.message({custom: 'put error message here'})Semicolon
You don't really need an else statement if you are returning in the if clause, though I recommend commenting and // else to improve readabilityCalamine
D
37

Your custom method must be like this:

const method = (value, helpers) => {
  // for example if the username value is (something) then it will throw an 
  // error with the following message but it throws an error inside (value) 
  // object without error message. It should throw error inside the (error) 
  // object with a proper error message

  if (value === "something") {
    return helpers.error("any.invalid");
  }

  return value; // Return the value unchanged
};

Docs:

https://github.com/hapijs/joi/blob/master/API.md#anycustommethod-description

Output for value :

{ username: 'something' }

Output for error:

[Error [ValidationError]: "username" contains an invalid value] {
  _original: { username: 'something' },
  details: [
    {
      message: '"username" contains an invalid value',
      path: [Array],
      type: 'any.invalid',
      context: [Object]
    }
  ]
}
Duchamp answered 17/10, 2019 at 6:20 Comment(8)
@ShifutHossain I tried it and it gives the error [ValidationError]: "username" contains an invalid value]Duchamp
codesandbox.io/s/billowing-leaf-zwo19?fontsize=14 I've also tried it hereCriseyde
Did you try on your local computer?Duchamp
@ShifutHossain as I know joi is not compatible in browser, can you check in your node application?Duchamp
I added the output in to the answer, when used in a node app. Isn't it what you want?Duchamp
Yeah, now it's working on node environment. It was my mistake I always use Codesandbox for practicing.Criseyde
In codesandbox there is template project for node apps, but I didn't tried.Duchamp
Thanks, but how do you define a custom error text?Feudality
A
2

This is how I validated my code, have a look at it and try to format yours

const busInput = (req) => {
  const schema = Joi.object().keys({
    routId: Joi.number().integer().required().min(1)
      .max(150),
    bus_plate: Joi.string().required().min(5),
    currentLocation: Joi.string().required().custom((value, helper) => {
      const coordinates = req.body.currentLocation.split(',');
      const lat = coordinates[0].trim();
      const long = coordinates[1].trim();
      const valRegex = /-?\d/;
      if (!valRegex.test(lat)) {
        return helper.message('Laltitude must be numbers');
      }
      if (!valRegex.test(long)) {
        return helper.message('Longitude must be numbers');
      }
    }),
    bus_status: Joi.string().required().valid('active', 'inactive'),
  });
  return schema.validate(req.body);
};
Aidoneus answered 6/10, 2020 at 20:56 Comment(3)
While this might answer the question, if possible you should edit your answer to include an explanation of how this code block answers the question. This helps to provide context and makes your answer much more useful to future readers.Community
First argument of the message() is LanguageMessages which is Record<string, string>.Feudality
you could validate currentLocation as an array of numbersJanettejaneva
H
0

recently faced the same isuue, hope this helps.

//joi object schema     
password: Joi.custom(validatePassword, "validate password").required(),


// custom validator with custom message
// custom.validation.ts

import { CustomHelpers } from "joi";

const validatePassword = (value: string, helpers: CustomHelpers) => {
  if (value.length < 6) {
    return helpers.message({
      custom: "Password must have at least 6 characters.",
    });
  }

  if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
    return helpers.message({
      custom: "Password must contain at least 1 letter and 1 number.",
    });
  }

  return value;
};

export { validatePassword };
Hylton answered 5/5 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.