Remove generated string from Mongoose schema with custom validation - node.js

I have a schema with a custom validation.
const schema = new mongoose.Schema({
username: {
type: String,
required: true,
validate: {
validator: /^[a-zA-Z0-9_]{3,16}$/,
message: "Usernames must be 3 to 16 characters long and contain only alphanumeric characters and underscores (_)."
},
},
// ...other things
});
However, the validation message comes out like this when I type an invalid username:
User validation failed: username: Usernames must be 3 to 16 characters long and contain only alphanumeric characters and underscores (_).
How do I get rid of the part of the string at the start that says User validation failed: username: ?

The format is embedded into the ValidationError class. Short of monkey patching that class, I can't see a way to easily change the format.
One option could be to run validation before being thrown by the model:
const user = new User({ username: 'ab' })
const error = user.validateSync()
console.log(error.errors['username'].message)
Or handle ValidationError when caught:
try {
const user = new User({ username: 'ab' })
await user.save()
} catch (error) {
if (error instanceOf mongoose.Document.ValidationError ) {
console.log(error.errors['username'].message)
}
}

To get rid of the initial string , you could simply use a split method on the string returned before displaying it. Here is a sample code:
let stringa = "User validation failed: username: Usernames must be 3 to 16 characters long and contain only alphanumeric characters and underscores (_)."; //replace this with error message
let stringb = (stringa.split(":")[2]);
console.log(stringb);//This displays the needed output string with Usernames

Your error object has another nested object called properties that has a field called message.
(example: properties.message) This gives you the exact string you wrote in the mongoose schema

Related

Setting mongoose to allow null without breaking validation

Here is how my application works. A user logs in for the first time using Google Sign in. We get the following data from their Google Account:
Given name
Family name
Email ID
We wish to use this information to call our API (POST request) to create a user profile.
The data we send is
{
firstName: firstName ,
lastName: lastName,
email: email
}
Here is where the issue comes from. The user profile has many fields and one of them is designation. When the user logs in for the first time, we don't know their designation.
We are using MongoDB for our database. So we use Mongoose to set up the connection. In Mongoose model, we have added some validation for our schema. Designation is a required field. It should be at least one character of length and maximum of 40 characters. If we set designation as null, the validation would fail.
Is there any way to allow null in a required field in Mongoose?
Rather than setting required to true or false, you can pass it a function:
const user = new Schema({
designation: {
type: String,
minLength: 1,
maxLength: 40,
required: function() {
// Rather than checking a stored variable, you could check
// static functions on the model, a custom value on the
// instance that isn't persisted, etc.
return this.hasLoggedInAtLeastOnce === true;
// If this function returns true, the field is required.
}
}
hasLoggedInAtLeastOnce: {
type: Boolean,
}
});

Mongoose query with await returns undefined

Following code is written in a loop in a express route handler;
const projects = await Projects.find({});//working fine
for (let i = 0; i < projects.length; i++) {
const project = projects[i];
const build = await Build.find({ id: project.latest_build});
//logger.debug(build[0].status); //error
const features = await Feature.find({ build_id: project.latest_build});
logger.debug(features[0].status);
}
The above code gives error at liine 4.
(node:672375) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined
However, It is correctly printed in the logs. Seems like the value of the variable is being filled by a promise lazily.
But Line number 6 always works fine.
Note: I'm not performing any other read/write operations on above collections.
Update
id is unique key for Build collection.
build_id is normal field for Feature collection.
Schemas and Documents are created like this
Schemas.Projects = new mongoose.Schema({
id: {
type: String,
unique: true
},
title: String,
latest_build: String
});
Schemas.Build = new mongoose.Schema({
id: {
type: String,
unique: true
},
run_date: Date,
status: String,
});
Schemas.Feature = new mongoose.Schema({
id : String,
build_id: String,
summary : String,
status: String,
flows: Number,
});
const Projects = mongoose.model('Projects', Schemas.Projects);
const Build = mongoose.model(`Build_${projId}`, Schemas.Build);
const Feature = mongoose.model(`Feature_${projId}`, Schemas.Feature);
The problem with the code in question is that it was overly optimistic in its expectations. This line...
const build = await Build.find({ id: project.latest_build });
logger.debug(build[0].status)
... assumed the query always finds at least one Build document. But, as the code was running as a part of the loop, the results were actually mixed:
at the first iteration, the query did find the Build object by the first project's data, and correctly logged its status
at the second iteration it gave back an empty array instead. Then the code threw an Error on attempting to access status property of build[0] (which was undefined).
The appropriate solution for this issue depends on how you should treat those cases. If each Project must have a corresponding Build (it's a strictly 1-1 relation), then having an Error thrown at you is actually fine, as you just have to fix it.
Still, it might be worth treating it as a 'known error', like this:
const buildId = project.latest_build;
const builds = await Build.find({ id: buildId });
if (!builds.length) {
logger.error(`No build found for ${buildId}`);
continue;
}
In this case (assuming logger is set up correctly) you won't have your code blowing up, yet the error will be logged. But if it's actually an ok situation to have no builds yet, just drop the logging - and treat this case as a known edge case.

How to set Joi validations with custom messages?

I was trying to set some validations with some custom messages in Joi. So, for example, I have discovered that when a string must have at least 3 characters we can use "string.min" key and associate this to a custom message. Example:
username: Joi.string().alphanum().min(3).max(16).required().messages({
"string.base": `Username should be a type of 'text'.`,
"string.empty": `Username cannot be an empty field.`,
"string.min": `Username should have a minimum length of 3.`,
"any.required": `Username is a required field.`,
}),
Now here is my question:
Question
// Code for question
repeat_password: Joi.ref("password").messages({
"string.questionHere": "Passwords must match each other...",
}),
What method (questionHere) name need to set to repeat_password to be able to notify the user that passwords must match? I don't even know if Join.ref("something") accept .messages({...})...
If someone could please show me some help in the Joi docs, I haven't find anything yet by there...
What you are trying to find here is the error type. It can be found in the error object that the joi validate function returns. eg: error.details[0].type will give you what you are looking for.
Regarding your second question, Join.ref("something") doesn't accept .messages({...}). Here you can use valid in conjunction with ref.
eg:
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(16).required().messages({
"string.base": `Username should be a type of 'text'.`,
"string.empty": `Username cannot be an empty field.`,
"string.min": `Username should have a minimum length of 3.`,
"any.required": `Username is a required field.`,
}),
password: Joi.string().required(),
password_repeat: Joi.any().valid(Joi.ref('password')).required().messages({
"any.only" : "Password must match"
})
});
const result = schema.validate({ username: 'abc', password: 'pass', password_repeat: 'pass1'});
// In this example result.error.details[0].type is "any.only"

Message from Yup custom validator returns undefined for the names of path and reference

I am trying to write a custom test for the yup validation library for use in a node/express app that tests whether two fields are the same - use case e.g. testing whether password and confirm password fields match. The logic is working, however the message provided from the method is not.
Custom validator code modified from: https://github.com/jquense/yup/issues/97#issuecomment-306547261
Custom validator
yup.addMethod(yup.string, 'isMatch', function (ref, msg) {
return this.test({
name: 'isMatch',
message: msg || `${this.path} must be equal to ${this.reference}`,
params: {
reference: ref.path
},
test: function (value) {
return value === this.resolve(ref);
}
});
});
Example use
const schema = yup.object().shape({
password: yup.string().min(8),
passwordConfirm: yup.string().isMatch(yup.ref('password'))
})
const payload = {
password: 'correctPassword',
passwordConfirm: 'incorrectPassword'
}
schema.validate(payload)
The above method works as expected from a logic perspective. However, in the error message returned, the value of this.path and this.reference are both undefined (i.e. undefined must be equal to undefined). It should read passwordConfirm must be equal to password.
I had to add this. in front of path and reference, otherwise node crashes with a ReferenceError that path/reference is not defined.
You do not need to write a custom validation function to check if two fields match, you can use the built in validator.oneOf
Whitelist a set of values. Values added are automatically removed
from any blacklist if they are in it. The ${values} interpolation can
be used in the message argument.
Note that undefined does not fail this validator, even when undefined
is not included in arrayOfValues. If you don't want undefined to be a
valid value, you can use mixed.required.
See here ref: oneOf(arrayOfValues: Array, message?: string | function): Schema Alias: equals
So you could re-write your schema as follows (I've added required also to the password fields):
const schema = yup.object().shape({
password: yup.string().min(8).required('Required!'),
passwordConfirm: yup.string().oneOf([Yup.ref('password')], 'Password must be the same!').required('Required!')
})

Nodejs - Joi Check if string is in a given list

I'm using Joi package for server side Validation. I want to check if a given string is in a given list or if it is not in a given list.(define black list or white list for values)
sth like an "in" or "notIn" function. How can I do that?
var schema = Joi.object().keys({
firstname: Joi.string().in(['a','b']),
lastname : Joi.string().notIn(['c','d']),
});
You are looking for the valid and invalid functions.
v16: https://hapi.dev/module/joi/api/?v=16.1.8#anyvalidvalues---aliases-equal
v17: https://hapi.dev/module/joi/api/?v=17.1.1#anyvalidvalues---aliases-equal
As of Joi v16 valid and invalid no longer accepts arrays, they take a variable number of arguments.
Your code becomes
var schema = Joi.object().keys({
firstname: Joi.string().valid(...['a','b']),
lastname: Joi.string().invalid(...['c','d']),
});
Can also just pass in as .valid('a', 'b') if not getting the values from an array (-:
How about:
var schema = Joi.object().keys({
firstname: Joi.string().valid(['a','b']),
lastname : Joi.string().invalid(['c','d']),
});
There are also aliases: .allow and .only
and .disallow and .not
Update from 10 Oct 2022
Now valid function requires Object
Joi.string().valid({ ...values_array }),

Resources