Set field max to equals another field (array) min value - javascript

I'm currently using joi to valite my data and I want to adchieve the next:
const schema = Joi.object({
arr: Joi.array().min(1).required(),
qty: Joi.number().min(1).max(/* arr.min */)
});
Is there a way to do so? If so, how? I've been trying with:
const schema = Joi.object({
arr: Joi.array().min(1).required(),
qty: Joi.number().min(1).max(Joi.ref('arr'))
});
But it doesn't seem to work.
Thanks in advance!

Related

Mongoose: Count array elements

I have the following Schema with a array of ObjectIds:
const userSchema = new Schema({
...
article: [{
type: mongoose.Schema.Types.ObjectId,
}],
...
},
I will count the array elements in the example above the result should be 10.
I have tried the following but this doesn't worked for me. The req.query.id is the _id from the user and will filter the specific user with the matching article array.
const userData = User.aggregate(
[
{
$match: {_id: id}
},
{
$project: {article: {$size: '$article'}}
},
]
)
console.log(res.json(userData));
The console.log(article.length) give me currently 0. How can I do this? Is the aggregate function the right choice or is a other way better to count elements of a array?
Not sure why to use aggregate when array of ids is already with user object.
Define articles field as reference:
const {Schema} = mongoose.Schema;
const {Types} = Schema;
const userSchema = new Schema({
...
article: {
type: [Types.ObjectId],
ref: 'Article',
index: true,
},
...
});
// add virtual if You want
userSchema.virtual('articleCount').get(function () {
return this.article.length;
});
and get them using populate:
const user = await User.findById(req.query.id).populate('articles');
console.log(user.article.length);
or simply have array of ids:
const user = await User.findById(req.query.id);
console.log(user.article.length);
make use of virtual field:
const user = await User.findById(req.query.id);
console.log(user.articleCount);
P.S. I use aggregate when I need to do complex post filter logic which in fact is aggregation. Think about it like You have resultset, but You want process resultset on db side to have more specific information which would be ineffective if You would do queries to db inside loop. Like if I need to get users which added specific article by specific day and partition them by hour.

How to force an attribute to be present only if another one is true?

I'm trying to validate a simple object using Joi. The schema i defined is the following:
const singleReq = Joi.object({
subscribing: Joi.boolean(),
duration: Joi.number().integer().positive(),
});
I would like duration to be present (not null) only when subscribing is true. I'm trying to do it with asserts but i can't figure out how.
You could try something like below:
const singleReq = Joi.object({
subscribing: Joi.boolean(),
duration: Joi.number()
.when('subscribing', { is: true, then: Joi.integer().positive().required() })
});
If you want change of type, you could also try to use alternatives like the below example:
const schema = {
a: Joi.alternatives().when('b', { is: 5, then: Joi.string(), otherwise: Joi.number() }),
b: Joi.any()
};
references

how to validate joi sub-schema in best way

Is it possible to validate joi schema without getting casting error? i.e. I have N fields but I want to validate 1 field only.
I have tried 2 ways, as below:
const Joi = require("joi");
const _ = require('lodash');
const testSchema = Joi.object().keys({
name: Joi.string().trim().min(5).max(25).required(),
allowed: Joi.number().integer().min(0).max(1).default(0)
});
// works smoothly; no error
// const {error, value} = Joi.validate({name :"abc", allowed: 1}, testSchema);
// (Way 1) --> Error: "value" must be a number
// const {error, value} = Joi.validate({name :"abc", allowed: 1}, Joi.reach(testSchema, 'allowed'));
// (Way 2) --> Error: "value" must be a number
const {error, value} = Joi.validate({name :"abc", allowed: 1}, _.find(testSchema._inner.children, {key: 'allowed'}).schema);
console.log(error);
P.S. I know the 3rd approach to compose final schema from smaller schema(s) but I don't want to go for that.
Instead of key value object just pass the value of the key, like:
Joi.validate(1, Joi.reach(testSchema, 'allowed'));

Joi array Object validation based on root key value

I have a complex scenario that I want to validate using Joi
here sample Joi Object Schema
const itemSchema = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(0)
});
let objSchema = {
items: Joi.array().items(itemSchema).required().min(1),
item_return_flag: Joi.string().optional().valid(true, false)
};
depending opon item_return_flag key value true or false, I want to change the items.quantity min value requirement. When true, quantity will be 0 , otherwise it will be 1.
Is there anyway, to control the definition of validation of the object in an array, based on the root object in Joi
The sample code that will switch the schema based one the parent key item_return_flag. Schema of the array need to switch based using Joi.altertnatives()
let itemArr = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(0)
});
let itemArr2 = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(1)
});
let itemSchema = Joi.alternatives()
.when('item_return_flag', { is: true, then: Joi.array().items(itemArr).required().min(1), otherwise: Joi.array().items(itemArr2).required().min(1)}) ;
let objSchema = {
items: itemSchema,
item_return_flag: Joi.string().optional().valid(true, false)
};
It looks to me like you could, following the API docs, do something like this:
let objSchema = {
items: Joi.array().items(Joi.object({
product_id: Joi.string().required(),
quantity: Joi.alternatives().when('item_return_flag', {
is: true, then: Joi.number().required().min(0),
otherwise: Joi.number().required().min(1)
})
})).required().min(1),
item_return_flag: Joi.string().optional().valid(true, false)
};
I'm not 100% sure that's the exact correct structure, but it's close. The Joi.alternatives() is provided for just such use cases.

Joi nested schemas and default values

I'm trying to get Joi to enforce default values on a secondary schema referenced by another. I have two schemas like so:
const schemaA = Joi.object().keys({
title: Joi.string().default(''),
time: Joi.number().min(1).default(5000)
})
const schemaB = Joi.object().keys({
enabled: Joi.bool().default(false),
a: schemaA
})
What I want is to provide an object where a is not defined and have Joi apply the default values for it instead like this:
const input = {enabled: true}
const {value} = schemaB.validate(input)
//Expect value to equal this:
const expected = {
enabled: true,
a: {
title: '',
time: 5000
}
}
The problem is that since the key is optional it is simply not enforced. So what I want is for it to be optional yet properly filled with schemaA defaults if not present. I've been looking through the documentation, but can't seem to find any info on this though I'm probably missing something obvious. Any tips?
Update : April, 2020.
Now, you can use default() in nested objects. Here is the commit in repo with test.
var schema = Joi.object({
a: Joi.number().default(42),
b: Joi.object({
c: Joi.boolean().default(true),
d: Joi.string()
}).default()
}).default();
This should do it:
const schemaA = Joi.object().keys({
title: Joi.string().default(''),
time: Joi.number().min(1).default(5000),
});
const schemaB = Joi.object().keys({
enabled: Joi.bool().default(false),
a: schemaA.default(schemaA.validate({}).value),
});
Although it would be much better if they would implement a feature to let us pass in Joi schema objects for defaults, like so: schemaA.default(schemaA) or schemaA.default('object')

Categories

Resources