Nodejs - Joi Check if string is in a given list - node.js

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 }),

Related

Remove generated string from Mongoose schema with custom validation

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

Hapi/Joi Validation: How to process text to a specific format

Is it possible to pass a string to hapi/joi and for it to process it and return a required format? For example consider the code below:
const acceptedCitiesSchema = Joi.object({
isCapitalCity: Joi.boolean().default(true),
cityName: Joi.string().valid('MyCity', 'YourCity').required(),
})
How can I make it that even if a user inputs 'myCity' or 'yourCity' joi automatically converts them to a format that the first word is capitalized('MyCity' and 'YourCity' respecively). And returns the desired results? Is it possible to achieve this using hapi/joi?
You can try the following:
const schema = Joi.object({
isCapitalCity: Joi.boolean().default(true),
cityName: Joi.string().valid('MyCity', 'YourCity').insensitive().required()
})
And while validating with schema, you can use the convert:true option as:
schema.validate({"cityName": "myCity"}, {"convert": true})
Alternatively, you can directly provide additional preferences to convert the Joi validated object in the schema:
const schema = Joi.object({
isCapitalCity: Joi.boolean().default(true),
cityName: Joi.string().valid('MyCity', 'YourCity').insensitive().prefs({convert:true}).required()
})
Here is a link to working example: https://repl.it/repls/HarmfulEvenPhases

How would i make the mongoose Schema dynamically according to the other fields value in node.js?

i want to define the schema dynamically according to the condition using mongoose
Schema
new mongoose.Schema({
type: String, //BASIC OR ADVANCE
// only for type = BASIC
name: String,
age: Number
/*
want these fields too but only if type = ADVANCE
email: String,
password: Number
PhoneNumber: String
*/
});
how would i achieve this kind of schema using mongoose.
it depends on what your approach to your database is. you can simply create two types with identifiers as Advance and Basic and use middleware to maintain the flow. Now to answer your question:
something like:
new mongoose.Schema({
type: String, //BASIC OR ADVANCE
// only for type = BASIC
name: String,
age: Number,
advance: []
And now you can check if advance is empty or not. Seriously it all depends on your approach, how you deal with the problem. Once a Schema is declared you can without the advance field, you can still save data like:
const MyModel = mongoose.model('Test', new Schema({ name: String }));
const doc = new MyModel();
doc.advance = {
email: "test#test.com",
password: 1234,
PhoneNumber: 1234
}
doc.save();
But with this structure if you want to know the Schema, you'll think that in your file it is only name and age and later when you start exploring, you'll find out that you are doing something like this and using a proper structure.
Think of moongoose documents as JavaScript Objects, there is a reason it is known as non-Structured data. Hope this explanation helps.

Joi validate optional array from dynamically added fields

I have a form where the user can add more authors, the first one is required and is it has a its own fields validation
When the user add more authors these authors are stored in an array, so if the user does not add more authors it shows this error message:
"\"authors_first_name\" must be an array"
"\"authors_last_name\" must be an array"
"\"authors_country\" must be an array"
How to make the array optional when thare are not more than one author?
But required when authors are added?
const schema = Joi.object().keys({
author_first_name: Joi.string().required().error(() => 'Author First Name is required'),
author_last_name: Joi.string().required().error(() => 'Author Last Name is required'),
author_country: Joi.string().valid(isoCodes).error(() => 'Author Country not valid'),
authors_first_name: Joi.array().items(Joi.string()).optional(),
authors_last_name: Joi.array().items(Joi.string()).optional(),
authors_country: Joi.array().items(Joi.string().valid(isoCodes).error(() => 'Country not valid')).optional()
});
I added the .optional() but the error keeps showing
EDIT: Oct/2022
The old code will not work with the current Joi version as today is 17.7.0, this will work:
const schema = Joi.object().keys({
first: Joi.array().items(Joi.string()).single(),
last: Joi.array().items(Joi.string()).single(),
country: Joi.array().items(Joi.string().valid(...isoCodes)).single(),
}).assert('.first.length', Joi.ref('last.length'))
.assert('.last.length', Joi.ref('country.length'));
Here the test on runkit
Old code
Alright I got it working with this code:
const schema = Joi.object().keys({
first: Joi.array().items(Joi.string()).single(),
last: Joi.array().items(Joi.string()).single(),
country: Joi.array().items(Joi.string().valid(isoCodes)).single(),
}).assert('first.length', Joi.ref('last.length'))
.assert('last.length', Joi.ref('country.length'));
Two additions to my original code:
.single() to the array().items(), this will accept string and arrays in the values
.assert() to check if the array length are equals
Thanks to this post, test code here
I think the approach you are using here is misleading, you should have array of objects even if the user inserts only one author or multiple authors. Taking different arrays for first_name and last_name can result in problems like what if you got 5 first_names and 6 last_names. you can add validations but still this is a kind of buggy approach. You should use schema like this
const schema = Joi.array().required().items(
Joi.object().required().keys({
author_first_name: Joi.string().required().error(() => 'Author First Name is required'),
author_last_name: Joi.string().required().error(() => 'Author Last Name is required'),
author_country: Joi.string().valid(isoCodes).error(() => 'Author Country not valid'),
}))

Mongoose variable key name

I have a mongo object and wish to access it via mongoose for my web app. The schema I've defined has an Object storing user ids and a 3-level value (yes, maybe or no).
e.g.
"user_info": {
"<id_value_1>": "y",
"<id_value_2>": "n"
}
The id_value_*s above are the users session ids so a long string of random characters. How can I create a mongoose Schema for this?
Would user_info: {String, String} work?
I could restructure it so that the user_info is an array of objects { "sessionid": "<value>", "value: "y"}, which would be ok, is this the best option?
You'll be better off if you avoid dynamic keys in your schema and go with your second idea of:
user_info: [{sessionid: String, value: String}]
You can use the $ positional operator to update individual user_info array elements by sessionid.
You may try with Schema Type Mixed like this way
var user = new Schema({
info: [Schema.Types.Mixed]
});
user.info = { any: { thing: 'i want' } };
user.markModified('info');
You can read more about it here
After testing the above, I found that defining the schema as user_info: { String: String } is a valid way to do this (option 1 specified in the question).
You may define objects and arrays in your schema. You may even combine them. For example, this is an array of objects:
var user = new Schema({
foo: [ {
address: {type: String},
email: {type: String, unique: true}
}],
bar: [ "simple", "array" ]
});

Resources