joi validation schema : value could be of several length possibles - node.js

I am using joi in a node project, and I would like to put the following rule : my field could be of length 6 or 8 (nothing else), I've tried to do the following but its not working :
Joi.object({
iban: Joi.string().alphanum().length(6).length(8)
})
The last written rules override the first one, so here I only accept value with length 8 and no more value with length 6
Thanks in advance

Try writing custom validator like this.
you can read more about custom validators here
Joi.object({
iban: Joi.string().alphanum().custom((value, helper) => {
if(value.length === 6 || value.length === 8){
return value;
} else {
return helper.message("iban must be 6 or 8 characters long")
}
});
})
``

this one is working well :
Joi.alternatives().try(Joi.string().alphanum().length(6), Joi.string().alphanum().length(8))

Related

Joi "or" operator thinks an empty string is a valid input

I am trying to use the "or" operator in Joi ver.17.4.0
As you can see, in the code below, I want either or both of the attributes/properties to be allowed, but not neither.
The problem is that Joi does not allow a string to be empty. So, to have it empty, I need to:
Joi.string().allow('')
This makes it not empty according to the "or" operator. So I can not get the 'name' to be empty in the eyes of "or".
It won't validate properly.
It validates even when I do this (but it shouldn't):
validatePerson(createPerson(''));
Keep in mind that I'm actually validating POST input on a node express API, so this is some simplified code to illustrate the issue:
const Joi = require('Joi');
function createPerson(name, age) {
const person = { name: name, age: age };
console.log(person);
return person;
}
function validatePerson(person) {
const schema = Joi.object({
name: Joi.string().allow(''),
age: Joi.number(),
}).or("name", "age");
console.log(schema.validate(person));
return schema.validate(person);
}
validatePerson(createPerson('')); // This should fail validation but doesn't
validatePerson(createPerson()); // Should fail and does
validatePerson(createPerson('Bob')); // Should pass and does
validatePerson(createPerson('', 7)); // Should pass and does
validatePerson(createPerson('Bob', 7)); // Should pass and does
As far as I understand, you want to allow the name to be empty an string, only if the age exists.
To acheive that, you can use .when:
name: Joi.string().when('age', { is: Joi.exist(), then: Joi.allow('') })
This way, your first example will fail as you expected.

#IsPhoneNumber() npm class validator how to add multiple countries code

In Nest js dto I want to validate user mobile number with multiple countries Regex. How can I do this?
#IsPhoneNumber('IN', {
message: (args: ValidationArguments) => {
if (args.value.length !== 10) {
throw new BadRequestException(`${args.value} Wrong Phone Number`);
} else {
throw new InternalServerErrorException();
}
},
})
Different countries has different length of phone numbers. And my suggestion is to keep list of country codes instead of custom regex. It's easier to maintain, and it's more readable. So solution is:
parse phone number
if it's valid check country code
if it's valid pass to next built-in decorator
So I've created my own decorator with libphonenumber-js
Usage in DTO:
export class PhoneDto {
#ToPhone
#IsString({ message: 'must be a valid number' })
readonly phone!: string;
}
Implementation:
import { Transform } from 'class-transformer';
import { parsePhoneNumberFromString } from 'libphonenumber-js';
const validCountries = ['US', 'UK'];
export const ToPhone = Transform(
(value: any) => {
if (typeof value !== 'string') return undefined;
const parsed = parsePhoneNumberFromString(value);
if (!parsed) return undefined;
if (!validCountries.includes(parsed.country)) return undefined;
return parsed.number;
},
{ toClassOnly: true },
);
And yes, this solution adds one more library, it could be slower (actually it depends on your countries list) because of parsing, but as I said before It's more readable and maintainable.
Passing a restricted set of locations to #IsPhoneNumber(region: string) is currently not supported.
Your only chance is to pass "ZZ" as region which will force users to enter numbers with the intl. prefix (see docs)

Can I have an if statement inside a map object to handle null values?

So i'm doing some web scraping of a bunch of products on a page & my code works fine as long as the elements are there, though sometimes when a product is out of stock and an element isn't present on a page I get the following error.
I've tried to put some kind of "If statement" inside the map object though can't get it to work.
//This code works if all the elements are present on the page
const productsOnPage = await page.$$eval('.tile-product', as => as.map(productContainer => ({
productTitle: productContainer.querySelector('h3').innerText.trim(),
}))
);
//I want something like this to handle the null values
const productsOnPage = await page.$$eval('.tile-product', as => as.map(productContainer => ({
if ( productContainer.querySelector('h3').innerText.length <= 0 || null ) {
productTitle: productContainer.querySelector('h3').innerText.trim(),
}
else {
productTitle: '',
}
}))
);
If null I expect:
productTitle: "";
but i'm getting:
Error: Evaluation failed: TypeError: Cannot read property 'innerText' of null
You can use ternary operator, E.g
const productsOnPage = await page.$$eval('.tile-product', as => as.map(productContainer => ({
productTitle: productContainer.querySelector('h3') ? productContainer.querySelector('h3').innerText.trim() : '';
}))
);
People already answered with the correct solution. But I believe an explanation is needed. On your if statement productContainer.querySelector('h3').innerText.length you're checking the length of the innerText property from querySelector('h3').
But you must verify if the
querySelector('h3') exists before reading its properties.

Mongoose accepts null for Number field

I have a mongoose schema where I'm storing a port number. I also have a default value set for the field.
port:{
type:Number,
default:1234
}
If I don't get any value via my API, it gets set to 1234.
However, If someone sends null, it accepts null and saves to database.
Shouldn't it covert null to 1234? null is not a number! Am I understanding it wrong?
I am considering the solution given here, but I dont want to add extra code for something that should work without it (unless I'm wrong and its not supposed to convert null to 1234)
See the comments in this issue:
https://github.com/Automattic/mongoose/issues/2438
null is a valid value for a Date property, unless you specify required. Defaults only get set if the value is undefined, not if its falsy.
(it's about dates but it can be applied to numbers just as well.)
Your options are to either:
add required to the field
add a custom validator that would reject it
use hooks/middleware to fix the issue
You might get away with a pre-save or post-validate (or some other) hook like this:
YourCollection.pre('save', function (next) {
if (this.port === null) {
this.port = undefined;
}
next();
});
but probably you'll have to use something like:
YourCollection.pre('save', function (next) {
if (this.port === null) {
this.port = 1234; // get it from the schema object instead of hardcoding
}
next();
});
See also this answer for some tricks on how to make null trigger default values in function invocation:
Passing in NULL as a parameter in ES6 does not use the default parameter when one is provided
This is unfortunate that Mongoose cannot be configured to tread null as undefined (with some "not-null" parameter or something like that) because it is sometimes the case that you work with data that you got in a request as JSON and it can sometimes convert undefined to null:
> JSON.parse(JSON.stringify([ undefined ]));
[ null ]
or even add null values where there was no (explicit) undefined:
> JSON.parse(JSON.stringify([ 1,,2 ]));
[ 1, null, 2 ]
As explained in mongoose official docs here
Number
To declare a path as a number, you may use either the Number global constructor or the string 'Number'.
const schema1 = new Schema({ age: Number }); // age will be cast to a Number
const schema2 = new Schema({ age: 'Number' }); // Equivalent
const Car = mongoose.model('Car', schema2);
There are several types of values that will be successfully cast to a Number.
new Car({ age: '15' }).age; // 15 as a Number
new Car({ age: true }).age; // 1 as a Number
new Car({ age: false }).age; // 0 as a Number
new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number
If you pass an object with a valueOf() function that returns a Number, Mongoose will call it and assign the returned value to the path.
The values null and undefined are not cast.
NaN, strings that cast to NaN, arrays, and objects that don't have a valueOf() function will all result in a CastError.

How to write regex in a sequelize.js field validation.isIn.args

I had wrote two regex in isIn.args, i want the phone validator can check my input value is one of these, but failed.
var ValidateMe = sequelize.define('Foo', {
phone: {
type: Sequelize.STRING(20),
validate: {
isIn: {
args: [[
// moible number of china
/^(13|14|15|17|18)\d{9}$/i,
// telphone number of china
/^((\(\d{2,3}\))|(\d{3}\-)|(\d{3}))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/i
]],
msg: "moible or telphone number format error!"
}
}
}
})
I want get the result:
var inputNumber = '15208282123'; // or '8008123'
( /^(13|14|15|17|18)\d{9}$/i.test(inputNumber)
|| /^((\(\d{2,3}\))|(\d{3}\-)|(\d{3}))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/i.test(inputNumber)
) === true;
Run it in the browser console, result is true.
sequelize "isIn" validator doesn't provide regex support, it checks weather value is directly present in the list of args or not, instead "is" validator supports regex, but i don't think it will support more than one regex at a time, you need to either convert 2 reg ex into 1 or create custom validator which checks both the regex and return true if one of them is passed, something like below
var ValidateMe = sequelize.define('Foo', {
phone: {
type: Sequelize.STRING(20),
validate: {
validatePhone: function(value) {
if(!/^(13|14|15|17|18)\d{9}$/i.test(value) && !/^((\(\d{2,3}\))|(\d{3}\-)|(\d{3}))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/i.test(value)) {
throw new Error('phone format error!')
}
}
}
}
})

Resources