hapi/joi descriptive error in nested validation - javascript

I'm trying to use https://github.com/hapijs/joi to do nested array of object validation, my code as below (playground here)
const Joi = require("#hapi/joi")
const schema = Joi.array().required().items(
Joi.object().required().keys({
name: 'room_range',
value: Joi.object({
min: Joi.number().required(),
max: Joi.number().min(Joi.ref('min')).required()
})
}),
Joi.object().required().keys({
name: 'anything',
value: Joi.object({
min: Joi.number().required(),
max: Joi.number().min(Joi.ref('min')).required()
})
}),
)
const result = schema.validate([
{
name: 'room_range',
value: {
min: 'dddd',
max: 2
}
},{
name: 'anything',
value: {
min: 1,
max: 2
}
}
]);
console.log('error: ', result.error);
I got this error
exports.ValidationError: "value" does not contain 1 required value(s)
Then the frontend will not be able to know by the error msg.

You could try this:
value: Joi.object({
min: Joi.number().required().error(() => 'error message here'),
max: Joi.number().min(Joi.ref('min')).required().error(() => 'error message here'),
}),
If you need an error message on the object or array you should be able to do the same thing just on the object.
value: Joi.object({
min: Joi.number().required(),
max: Joi.number().min(Joi.ref('min')).required(),
}).required().error(() => 'error message here'),
The 'error()' takes either an instance of error or a function.
https://hapi.dev/family/joi/?v=16.1.4#anyerrorerr
Maybe you can find more usefull info here:
Node.js + Joi how to display a custom error messages?
Hope this helps as I'm not exactly sure of what you are asking for.

Related

How to validate only when param is exist in express-validator

I'm building a update user method in express. The data is dynamic.
Like: I have 2 field that can be updated: name, phone_number,
In 1 request, the data can be only phone_number or name or both.
I use express-validator to validate data:
update: [
check('name').trim().isLength({ min: 3, max: 30 }).bail(),
(req, res, next) => {
const errors = validationResult(req)
if (!errors.isEmpty()) return res.json({ code: 400, message: 'invalid data' })
next()
},
]
When I want to update only phone_number, the data won't have name.
I want to validate name only when data has name. How can I solve it?
What you're looking for is optional.
This will skip validations when the value of name is not set (undefined):
check('name').optional().trim().isLength({ min: 3, max: 30 }),
But you can also skip them when the value is null or undefined, like this:
check('name').optional({ nullable: true }).trim().isLength({ min: 3, max: 30 }),
And finally, if you'd like to skip validations when the value is null, undefined, false, zero or an empty string, you can do this:
check('name').optional({ checkFalsy: true }).trim().isLength({ min: 3, max: 30 }),

Unexpected MongoServerError 11000 (duplicate) for a field that is NOT even declared in schema

I'm developing an online store Node.js REST API with Mongoose (MongoDB), which I'm new to. I decided to test the orders service and saw that after I had made 1 successful order (so it worked once), it sent me a duplicate key error for the next one, for a key 'name' with value 'null', of the order.products collection that is an Array, and not a kvp object.
I should note that nowhere in my code is 'products.name' mentioned.
ERROR:
MongoServerError: E11000 duplicate key error collection: store.orders index: products.name_1 dup
at {...}{
key: { products.name: null }
index: 0,
code: 11000,
keyPattern: { 'products.name': 1 },
keyValue: { 'products.name': null },
[Symbol(errorLabels)]: Set(0) {}
}
when the error is handled, this message is received and it makes no sense:
{ "message": "Order with products.name "null" already exists" }
Order schema:
const schema = new Schema({
userId: {
type: Types.ObjectId,
ref: 'User'
},
address: {
type: addressSchema,
required: true
},
products: {
type: [orderProductSchema],
required: true,
validate: nonEmptyArray
},
status: {
type: Number,
validate: inCollection(Object.values(ORDER_STATUS))
},
price: { type: Number, required: true, min: 0 }
}, { timestamps: true });
don't bother with the validators or the address/status/user/price, it has nothing to do with them; what is more, nothing is specified as unique: true
As you can see, the 'products' field is just an array of products, no 'name' is declared
orderProductSchema:
const schema = new Schema({
product: {
_id: { type: Types.ObjectId, required: true },
name: {
type: String,
required: true,
maxLength: 250
},
displayImage: String,
price: { type: Number, required: true, min: 0 }
},
quantity: {
type: Number,
required: true,
validate: isInteger,
min: 1
},
}, { _id: false });
I have a 'name' field here, but it's just the name of a product. The error is thrown even when the names are unique.
Orders service:
// get data and format it to fit the Order model
console.dir(products); // --> all is how it's expected to be in the schema
return await Order.create({
userId,
address,
products,
status: ORDER_STATUS.AWAITING_CONFIRMATION,
price: totalOrderPrice
});
It seems to me that this is some weird MongoDB behaviour/specification that I missed. If someone has any idea what could cause the problem - please help.
I tried removing all parts such as other fields and validations to see if they might've caused the problem but that was not the case. I thought maybe I had formatted the object I send to the database wrong, but I console.log-ed it and it was fine ({products: Array})
Thanks to #user20042973 and #MTN I saw that my 'orders' database had index 'products.name' (no idea how it got there).. I just removed the index and the problem is solved.

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.
According to Joi documentation optional does not include null you need to use allow
capacity: Joi.number().optional().integer().min(0).allow(null)
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/joi#17.7.0/dist/joi-browser.min.js"></script>

RangeError [EMBED_FIELD_VALUE]: MessageEmbed field values may not be empty

I have this code, and I keep getting this error:
RangeError [EMBED_FIELD_VALUE]: MessageEmbed field values may not be empty.
at Object.run (C:\Users\Sochum\Desktop\BobloxBot\commands\GroupStats.js:41:2)
Line 41 has this code: .addFields(
Here is the code:
const embed = new Discord.MessageEmbed()
.setTitle(`${groupname}`)
.addFields(
{ name: `🤴 Group Owner`, value: `<#${owner}>` },
{ name: `👑 Group Co-Owner`, value: `<#${co_owner}>` },
{ name: `🚹 Member Count`, value: `${membercount}` },
{ name: `💰 Group Funds`, value: `${funds}` },
{ name: `📦 Group Items`, value: `${group_items}` },
{ name: `🎂Group Birthday`, value: `${Group_Bday}` },
{ name: `🤝Group Sharing Circle`, value: `${sharing_circle}` },
{ name: `📈Group Warwins`, value: `${Group_Warwins}` },
{ name: `📉Group Warlosses`, value: `${Group_Warlosses}` },
)
message.channel.send(embed)
I can't seem to find a problem anywhere, so I am not sure why I am getting this error
I've ran your code and it works fine for me. This error happens when one of the field value is empty, so be sure that all your variables are defined and that they can be read inside of a string.

Joi Validation: How to make values in nested json optional?

So I have a nested json something like below, which is a payload structure for api that I am writing
{"item_id":"1245",
"item_name":"asdffd",
"item_Code":"1244",
"attributes":[{"id":"it1","value":"1"},{"id":"it2","value":"1"}],
"itemUUID":"03741a30-3d62-11e8-b68b-17ec7a13337"}
My Joi validation on the payload is :
validate: {
payload: Joi.object({
item_id: Joi.string().required(),
item_name: Joi.string().required(),
placeId: Joi.string().allow('').allow(null),
itemUUID: Joi.string().allow('').allow(null),
item_Code: Joi.string().required().allow(null),
attributes: Joi.alternatives().try(attributeObjectSchema, attributesArraySchema).optional()
})
}
where
const attributeObjectSchema = Joi.object({
id: Joi.string().optional(),
value: Joi.string().optional()
}).optional();
and
const attributeArraySchema = Joi.array().items(customAttributeObjectSchema).optional();
My question is :
With the above Joi validation, if I edit my payload and send my attributes tag like below (i,e., with "values" as empty)
"attributes":[{"id":"CA1","value":""},{"id":"CA2","value":""}]
It throws an error saying:
"message": "child \"attributes\" fails because [\"attributes\" must be an object, \"attributes\" at position 0 fails because [child \"value\" fails because [\"value\" is not allowed to be empty]]]",
"validation": {
"source": "payload",
"keys": [
"attributes",
"attributes.0.value"
]
What am I doing wrong here? What do I need to do if I need Joi to accept the below:
"attributes":[{"id":"CA1","value":""},{"id":"CA2","value":""}]
Do something like this
attributeArraySchema.customAttributes = [];
attributeArraySchema.customAttributes = [
{"id":"CA1","value":""},
{"id":"CA2","value":""}
];
So I resolved this by Changing the following schema definition from
const attributeObjectSchema = Joi.object({
id: Joi.string().optional(),
value: Joi.string().optional()
}).optional();
To
const attributeObjectSchema = Joi.object({
id: Joi.string().optional(),
value: Joi.string().allow('').allow(null)
}).optional();

Categories