The below schema allows me to have the optionalField required if value is set to option1 but if value is set to option2 then optionalField can be set to anything the user desires. Instead, I want the validation to fail if value is set to anything other than option1 and optionalField is passed in.
const validValues = ['option1', 'option2']
const sampleSchema = new mongoose.Schema({
value: {
type: String,
enum: validValues,
required: true
}
optionalField: {
type: Number,
required: function() { return this.value === 'option1' }
// validation should fail if this property is passed in when value is anything but option1
}
})
Joi has an excellent way to achieve this using Joi.forbidden():
const validValues = ['option1', 'option2']
const schema = Joi.object({
value: Joi.string().valid(...validValues).required(),
optionalField: Joi.string().when('value', { is: 'option1', then: Joi.required() otherwise: Joi.forbidden() })
})
If I validate with this schema and optionalField is passed in to Joi, the validation will fail unless value is option1.
I am hoping to find a way to achieve the same in Mongoose.
Thank You!
You can use custom validators like this:
optionalField: {
type: Number,
required: function() {
return this.value === "option1";
},
validate: {
validator: function(v) {
console.log({ v });
return !(this.value !== "option1" && v.toString());
},
message: props =>
`${props.value} optionalField is forbidden when value is not option1`
}
}
Sample route:
router.post("/sample", (req, res) => {
const sample = new Sample(req.body);
sample
.save()
.then(doc => res.send(doc))
.catch(err => res.status(500).send(err));
});
Input 1:
{
"value": "option2",
"optionalField": 11
}
Result 1: (error)
{
"errors": {
"optionalField": {
"message": "11 optionalField is forbidden when value is not option1",
"name": "ValidatorError",
"properties": {
"message": "11 optionalField is forbidden when value is not option1",
"type": "user defined",
"path": "optionalField",
"value": 11
},
"kind": "user defined",
"path": "optionalField",
"value": 11
}
},
"_message": "Sample validation failed",
"message": "Sample validation failed: optionalField: 11 optionalField is forbidden when value is not option1",
"name": "ValidationError"
}
Input 2:
{
"value": "option2"
}
Result 2: (success)
{
"_id": "5e031b473cbc432dfc03fa0e",
"value": "option2",
"__v": 0
}
Input 3:
{
"value": "option1"
}
Result 3: (error)
{
"errors": {
"optionalField": {
"message": "Path `optionalField` is required.",
"name": "ValidatorError",
"properties": {
"message": "Path `optionalField` is required.",
"type": "required",
"path": "optionalField"
},
"kind": "required",
"path": "optionalField"
}
},
"_message": "Sample validation failed",
"message": "Sample validation failed: optionalField: Path `optionalField` is required.",
"name": "ValidationError"
}
Input 4:
{
"value": "option1",
"optionalField": 11
}
Result 4: (success)
{
"_id": "5e031ba83cbc432dfc03fa10",
"value": "option1",
"optionalField": 11,
"__v": 0
}
Related
I have a Mongoose Schema ready but when I call it with my api it gives out validation errors in all of the required fields
this is my schema object
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please use a name']
},
email: {
type: String,
required: [true, 'Please use a valid e-mail'],
unique : true,
lowercase: true,
validator: [validator.isEmail, 'Please provide a valid e-mail']
},
password: {
type: String,
required: [true, 'Please provide a password for your profile'],
minlength: 6
},
passwordConfirm: {
type: String,
required: [true, 'Please confirm your password']
}
})
const User = mongoose.model('User', userSchema)
the req. body i sent with the api through JSON is
{
"name": "user1",
"email": "user1#gmail.com",
"password": "pass1234",
"passwordConfirm": "pass1234"
}
I was trying to make a new user through the api and model throuth the controller
exports.signup = async (req, res, next) => {
try {
const newUser = await User.create(req.body)
console.log('in the newUser');
res.status(201)
.json({
status: 'success',
data: {
user: newUser
}
})
}catch (err){
res.status(400).json({
status: 'fail',
message: err
})
}
}
then I get the following errors in postman
{
"status": "fail",
"message": {
"errors": {
"passwordConfirm": {
"name": "ValidatorError",
"message": "Please confirm your password",
"properties": {
"message": "Please confirm your password",
"type": "required",
"path": "passwordConfirm"
},
"kind": "required",
"path": "passwordConfirm"
},
"password": {
"name": "ValidatorError",
"message": "Please provide a password for your profile",
"properties": {
"message": "Please provide a password for your profile",
"type": "required",
"path": "password"
},
"kind": "required",
"path": "password"
},
"email": {
"name": "ValidatorError",
"message": "Please use a valid e-mail",
"properties": {
"message": "Please use a valid e-mail",
"type": "required",
"path": "email"
},
"kind": "required",
"path": "email"
},
"name": {
"name": "ValidatorError",
"message": "Please use a name",
"properties": {
"message": "Please use a name",
"type": "required",
"path": "name"
},
"kind": "required",
"path": "name"
}
},
"_message": "User validation failed",
"name": "ValidationError",
"message": "User validation failed: passwordConfirm: Please confirm your password, password: Please provide a password for your profile, email: Please use a valid e-mail, name: Please use a name"
}
}
Just use required: true in your schema
If the validation fail, an error will be throw in:
const newUser = await User.create(req.body);
so you need to catch it and return an appropriate message to the user here (no in the model validation)
I am building out a new endpoint in my application which uses express-openapi-validator as validator middleware.
/* index.ts */
import * as OpenApiValidator from 'express-openapi-validator';
const whitelistedPaths = [/* regex tested paths */];
app.use(
OpenApiValidator.middleware({
apiSpec: './schema/api.json',
validateResponses: true,
ignorePaths: whitelistedPaths,
validateSecurity: true,
}),
);
/* ... */
app.post(
'/users/:email/validateToken',
bodyParser.json(),
(req) => validateToken(req.params.email, req.body.resetToken),
);
In my configuration (api.json) file I've defined the schema for my endpoint as:
"/users/{email}/validateToken": {
"post": {
"tags": ["users"],
"summary": "Validate user token",
"operationId": "validateToken",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"parameters": [
{
"name": "email",
"in": "path",
"description": "User email",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["resetToken"],
"properties": {
"resetToken": {
"type": "string"
}
}
}
}
}
}
}
},
I've tested with Postman with the following JSON body:
{
"resetToken": "randomd9320ru9"
}
but receive the following error message:
{
"message": "request should have required property 'body'",
"errors": [
{
"path": ".body",
"message": "should have required property 'body'",
"errorCode": "required.openapi.validation"
}
]
}
I'm not sure why it's complaining about the body. I tried putting "required": true under the requestBody config in api.json but that didn't change anything. I just want to make sure that the body includes the required field resetToken.
I suppose you need to use bodyParser.json() before using OpenApiValidator.middleware:
app.use(bodyParser.json());
app.use(
OpenApiValidator.middleware({
apiSpec: './schema/api.json',
validateRequests: true,
validateResponses: true,
ignorePaths: whitelistedPaths,
validateSecurity: true,
}),
);
...
app.post(
'/users/:email/validateToken',
(req) => validateToken(req.params.email, req.body.resetToken),
);
I need to find my data that matches the particular type. Say, I have an array of objects in my DB, with each object having it's leadType : all or leadType: blacklist. Each object has a different kind of leadType value associated with it.
I want to get the complete data that matches the particular leadType, like leadType: 'radar'.
I tried using the following query but along with the documents it matches, it also returns the empty documents that do not matches the specified projection.
This is what I tried:
exports.leads_by_type = (req, res) => {
const { lead_type } = req.body;
Lead.find({}, { leads: { $elemMatch: { leadType: lead_type }}} )
.then(data => {
if (!data || data.length === 0) {
return res.status(404).json({ message: "No data found" });
}
res.status(200).json({ message: "Data found", data });
})
.catch(err => res.status(500).json({ err }));
};
and it returns me the output as follows:
{
"message": "Data found",
"data": [
{
"_id": "5e83427079f7611bdc1e48a0",
"leads": [
{
"education": {
"school": "happy kids corner",
"graduation": "some school"
},
"currentPosition": {
"title": "Salesforce developer",
"endDate": "currently working",
"employmentType": "full-time",
"industry": "information technology"
},
"location": {
"state": "delhi",
"country": "india"
},
"leadType": "radar",
"name": "Ben",
"mobile": 1524524678,
"_id": "5e83427079f7611bdc1e489e"
}
]
},
{
"_id": "5e84cb4fb59fdd1644e7c226",
"leads": [
{
"education": {
"school": "happy kids corner",
"graduation": "some school"
},
"currentPosition": {
"title": "Salesforce developer",
"endDate": "currently working",
"employmentType": "full-time",
"industry": "information technology"
},
"location": {
"state": "delhi",
"country": "india"
},
"leadType": "radar",
"name": "joey",
"mobile": 1524524678,
"_id": "5e84cb4fb59fdd1644e7c224"
}
]
},
{
"_id": "5e84cb70b59fdd1644e7c229",
"leads": []
},
{
"_id": "5e84cb88b59fdd1644e7c22c",
"leads": []
},
{
"_id": "5e84cbb7b59fdd1644e7c232",
"leads": []
},
{
"_id": "5e84cbd9b59fdd1644e7c235",
"leads": [
{
"education": {
"school": "happy kids corner",
"graduation": "some school"
},
"currentPosition": {
"title": "Salesforce developer",
"endDate": "currently working",
"employmentType": "full-time",
"industry": "information technology"
},
"location": {
"state": "delhi",
"country": "india"
},
"leadType": "radar",
"name": "rhea",
"mobile": 1524524678,
"_id": "5e84cbd9b59fdd1644e7c234"
}
]
}
]
}
Please help me out to fix remove these empty objects from the data output
Edit: Adding my DB screenshot:
If you want to match only the documents with leadType equals to radar you should specify this in the find condition.
exports.leads_by_type = (req, res) => {
const { lead_type } = req.body;
Lead.find({ 'leads.leadType': lead_type })
.then(data => {
if (!data || data.length === 0) {
return res.status(404).json({ message: "No data found" });
}
res.status(200).json({ message: "Data found", data });
})
.catch(err => res.status(500).json({ err }));
};
find({ leads: { $elemMatch: { leadType: lead_type }}} )
You can try the above query.
{} you had used empty object along with your condition in find which is the root cause behind the fetched empty data.
Is it possible to get an array value from function if I put the forEach in the array?
Here is my code
res.status(400).send({
reason: err._message,
messages: Object.keys(err.errors).forEach((message) => {
return message; // I want messages will be arrays from err.errors
})
});
it doesn't return any error, and what I tried to achieve is the output like this
{
"reason": "some message from err._message",
"errors": {
"name": "Name field is required",
"etc": "etc field is required",
...
}
}
err.errors output (I don't want to show all the errors)
"errors": {
"name": {
"message": "Name field is required",
"name": "ValidatorError",
"properties": {
"message": "Name field is required",
"type": "required",
"path": "name",
"value": ""
},
"kind": "required",
"path": "name",
"value": ""
}
},
EDIT
as #jonrsharpe comment I tried using map
res.status(400).send({
reason: err._message,
errors: Object.values(err.errors).map((data) => {
return data.message;
})
});
it's already give me a correct value, but I don't know how to get the key
this code returns
{
"reason": "User validation failed",
"errors": [
"Name field is required",
"Email field is required"
]
}
But if possible I want the errors returns as Object
Please .reduce function from javascript.
Object.keys(err.errors).reduce((a,b)=>{
a[b]=x.errors[b]['message'];
return a
}, {})
I get error from Mongoose in the following format
{
"errors": {
"companyName": {
"message": "Error, expected `companyName` to be unique. Value: `priStore`",
"name": "ValidatorError",
"properties": {
"type": "unique",
"message": "Error, expected `{PATH}` to be unique. Value: `{VALUE}`",
"path": "companyName",
"value": "priStore"
},
"kind": "unique",
"path": "companyName",
"value": "priStore",
"$isValidatorError": true
},
"companyEmail": {
"message": "Error, expected `companyEmail` to be unique. Value: `pri#gmail.com`",
"name": "ValidatorError",
"properties": {
"type": "unique",
"message": "Error, expected `{PATH}` to be unique. Value: `{VALUE}`",
"path": "companyEmail",
"value": "pri#gmail.com"
},
"kind": "unique",
"path": "companyEmail",
"value": "pri#gmail.com",
"$isValidatorError": true
}
},
"_message": "Client validation failed",
"message": "Client validation failed: companyName: Error, expected `companyName` to be unique. Value: `priStore`, companyEmail: Error, expected `companyEmail` to be unique. Value: `pri#gmail.com`",
"name": "ValidationError"
}
I need to show at the client in a good format like
Errors
Company Name already exist
Company Email already exist
Should I be parsing the error at my client end or Node.js end? At the Node.js end, I can return appropriate error messages which client can display to the user?
You could transform the errors object to an array of messages in Node.js by creating a helper method that takes the Mongoose error object, traverses the errors property and pushes error message to an array based on a pre-defined dictionary of error types and their messages.
The following function describes the above transformation and an example usage:
const handleError = err => {
const dict = {
'unique': "% already exists.",
'required': "%s is required.",
'min': "%s below minimum.",
'max': "%s above maximum.",
'enum': "%s is not an allowed value."
}
return Object.keys(err.errors).map(key => {
const props = err.errors[key].properties
return dict.hasOwnProperty(props.kind) ?
require('util').format(dict[props.kind], props.path) :
props.hasOwnProperty('message') ?
props.message : props.type
})
}
Example usage:
company.save(err => {
if (err) res.send({ errors: handleError(err) })
}