I'm trying to validate my target object with following conditions:
if (target.company === `GP` AND one of target.documents type equals `full`) {
one of target.documents type must equals `part-should-be-error`
} else {
throw new Error()
}
In this example, validation doesn't return any errors, but it should, because of 'part' !== 'part-should-be-error'
I tried https://stackoverflow.com/a/53647449/10432429 but it doesn't work with Joi v15
Since I can't merge array schema with alternative schema, all that I can to do is use $ to get global context, but seems that it doesn't work too
I have codebase on Joi v15.1.1, so please install same version
npm i #hapi/joi#15.1.1
const Joi = require('#hapi/joi');
(async () => {
const target = {
name: 'Jeff',
company: 'GP',
documents: [
{type: 'full', value: 'apple'},
{type: 'part', value: 'tesla'},
],
};
const documents = Joi.object().keys({
type: Joi.string().valid(['full', 'part', 'empty']),
value: Joi.string().min(1).max(40)
.required(),
}).required();
const schema = Joi.object()
.keys({
name: Joi.string().min(1).max(20)
.required(),
company: Joi.string().valid(['GP', 'QW']),
documents: Joi.array().items(documents).min(1)
.when('$', {
is: Joi.object().keys({
company: Joi.string().valid(['GP']),
documents: Joi.array().has(Joi.object({type: Joi.string().valid('full')}).unknown().required()),
}).unknown().required(),
then: Joi.array().has(Joi.object({type: Joi.string().valid(['part-should-be-error'])}).unknown()).required(),
})
.required(),
});
await Joi.validate(target, schema, {context: target});
})();
If I do sth wierd, please feel free to show another way how to solve this
Related
I currently have an object that looks like the following:
const initialValues = {
created: {
position: 1,
name: 'created',
type: 'timestamp',
desc: 'The date and time the lead is created',
mapping: {
name: '',
defaultValue: '',
map: false
}
}
}
I would like the name within the mapping object to become required when the map value within the map object is set to a value of true. I have attempted this by doing the following:
const validationSchema = yup.object({
created: yup.object().when('mapping.map', {
is: true,
then: yup.object({
mapping: yup.object({
name: yup.string().required('name is required')
})
})
})
})
I believe I'm not tunneling enough in order to accurately set up the validation for the mapping object, any and all help/suggestions would be greatly appreciated.
I found the solution doing the following:
const validationSchema = yup.object({
created: yup.object().shape({
mapping: yup.object().shape({
map: yup.boolean(),
defaultValue: yup.string(),
name: yup.string().when('map', {
is: true,
then: yup.string().required('name is required')
})
})
})
})
I have a use case where a schema field is mandatory depending on the value of another field,
eg. If the schema has 2 fields, name and addr,
if the value of name field is "test" only then addr field is required.
I am using Joi for object validation,
Following is my sample code -
const Joi = require('joi');
let test = async() => {
const schema = Joi.object({
name: Joi.string().required(),
addr: Joi.alternatives().conditional('name', {is: 'test', then: Joi.string().required()})
});
const request = {
name: "test"
}
// schema options
const options = {
abortEarly: false, // include all errors
allowUnknown: true, // ignore unknown props
stripUnknown: true // remove unknown props
};
// validate request body against schema
const validationResponse = await schema.validate(request, options);
console.log("validationResponse => ", validationResponse);
return true;
};
test();
current output -
validationResponse => { value: { name: 'test' } }
what I'm expecting is validationResponse to have error message that addr field is missing.
I tried to refer -
https://www.npmjs.com/package/joi
https://joi.dev/api/?v=17.4.2#alternativesconditionalcondition-options
Do you really need Joi.alternatives? Why don't you use Joi.when instead?
Joi.object({
name: Joi.string().required(),
addr: Joi.string().when('name', { is: 'test', then: Joi.required() })
})
I looked at an existing codebase and and I noticed that the codebase had two schema validations for what I feel a single schema could validate as the second schema is an offshoot of the first.
See codebase below.
export const StudentSchema = Joi.object().keys({
_id,
name,
dob,
gender,
grade
});
export const StudentUpdateSchema = Joi.object().keys({
name,
dob,
})
Now these schema are being used in the following routes:
//CREATE ROUTE
{
method: 'POST',
path: '/students/{id}',
handler: async (request) => {
const { id } = request.params;
return Student.create(id, request.payload);
},
options: {
...sharedOptions,
description: 'Enrolling a student',
validate: {
failAction,
payload: StudentSchema,
params: {
studentId: Joi.objectId().required()
},
},
response: {
status: {
200: StudentSchema,
400: Joi.any(),
},
},
},
},
// UPDATE ROUTE
{
method: 'PUT',
path: '/students/{id}',
handler: async (request) => {
const { id } = request.params;
return Student.update(Id, { $set: request.payload });
},
options: {
...sharedOptions,
description: 'Update student details',
validate: {
failAction,
payload: StudentUpdateSchema,
params: {
studentId: Joi.objectId().required(),
},
},
response: {
status: {
200: StudentSchema,
400: Joi.any(),
404: Joi.any(),
},
},
},
}
I am new to Hapi but have some experience with Express and Mongoose and I am inclined to re-writing these validations to use ONLY the StudentSchema in both the 'POST' and 'PUT' method since the StudentSchema contains all that is required to handle both the create and update functionality.
Can anyone with experience in HapiJS validate the pros/cons of using a single schema object (in this case StudentSchema) or should I continue the current paradigm of using the two schemas?
I would argue this design pattern is the better practice, On a partial update you should have a fitting schema to represent the incoming object better. However you can avoid the two schemas while preserving the concept behind it by extending the first one using optionalKeys.
const createSchema = Joi.object().keys({
_id: Joi.objectId(),
name: Joi.string().required(),
dob: Joi.string().required(),
gender: Joi.string().required(),
grade: Joi.number().required()
});
const updateSchema = createSchema.optionalKeys("gender", "grade", "_id");
Joi.validate({name: "this fails"}, createSchema); // error: required fields missing
Joi.validate({name: "this works"}, updateSchema);
This way you have a full schema that protects you while also allowing a partial field update.
I don't know how to make a list of documents with mongoose (Node JS), someone know's how to make that?
new mongoose.Schema({
first_name: String,
last_name: String,
companies: [
{
name_company:String,
post:String,
time_in_post:String,
}
]
});
I need to insert many documents with companies schema at different times but I don't know how to make that with mongoose.
Can someone help me?
Seems like splitting this into different schemas might be a good idea. Especially if other people will be apart of the same company.
For instance, maybe you could try this:
const CompanySchema = new mongoose.Schema({
name: String,
// ... other company specific attributes
});
const Company = mongoose.model("Company", CompanySchema);
const UserSchema = new mongoose.Schema({
firstName: String,
lastName: String,
companies: [
{
company: { type: mongoose.Schema.Types.ObjectId, ref: 'Company' },
post: String,
timeInPost: String,
}
]
});
const User = mongoose.model("User", UserSchema);
And the interaction with the models could look like this:
(async () => {
try {
const company1 = await Company.create({ name: "Company 1" });
const company2 = await Company.create({ name: "Company 2" });
const user1 = await User.create({
firstName: "first",
lastName: "last",
companies: [
{
company: company1._id,
post: "....",
timeInPost: "....",
},
{
company: company2._id,
post: "....",
timeInPost: "....",
},
],
});
const users = await User.find({}).populate("companies.company"); // in order to populate the company with the document from the company collection
// users with companies info
} catch (error) {
console.log(error);
}
})();
Referenced from the mongoose documentation: https://alexanderzeitler.com/articles/mongoose-referencing-schema-in-properties-and-arrays/
You could use this approach, with this way you avoid data duplication:
const companySchema = new mongoose.Schema({
name_company: String,
post: String,
time_in_post: String
});
new mongoose.Schema({
firstName: String,
lastName: String,
companies: [companySchema] // Array of subdocuments
});
See full documentation here: https://mongoosejs.com/docs/guide.html
Can spread operator solve below problem? Imagine I have more fields, then I have to declare req.body.something for every single fields, that's so tedious.
app.use((res,req,next) => {
const obj = {
name: req.body.name,
age: req.body.age,
gender: req.body.gender
}
//
User.saveUser(resp => res.json(resp)) //User model
})
You can use destructuring assignment:
const obj = req.body;
const { name, age, gender } = obj;
But, still you will have to validate it, and count all of them in your scheme.
Update:
Adding some validation example.
Assuming such schema in your route:
const tv4 = require('tv4');
const schema = {
type: 'object',
properties: {
name: 'string',
age: number,
gender: {
type: 'string',
pattern: /f|m/i
}
},
required: ['name']
};
And then, in your handler you validate:
if (tv4.validate(req.body, schema) {
// continue your logic here
} else {
// return 400 status here
}
You can use lodash's pick():
_.pick(object, [paths])
Creates an object composed of the picked object properties.
Example code is:
const _ = require('lodash');
...
const obj = _.pick(req.body, ['name', 'age', 'gender']);
If gender does not exist in req.body, it would be ignored -- the result obj object won't have a gender field.
If all the req.body fields are needed, you can just assign req.body to obj:
const obj = req.body;
To validate req.body content, you can use lodash's .has():
_.has(object, path)
Checks if path is a direct property of object.
Example code would be:
_.has(req.body, ['name', 'age', 'gender']); // return true if all fields exist.
You can use destructuring assignment
const { name, age, gender } = req.body
or if you wanna use spread operation, you can use :
const obj = { ...req.body }
Hope it helps!