I have a schema like this (the real one is doble the size):
const optionalIfPatch = { patch: (schema: AnySchema) => schema.optional() };
const personValidator = Joi.object({
username: Joi.string().token().lowercase().min(5).required().alter(optionalIfPatch),
password: Joi.string().min(6).required().alter(optionalIfPatch),
fullName: Joi.string().min(5).required().alter(optionalIfPatch),
email: Joi.string().email().required().alter(optionalIfPatch),
userProfile: Joi.string().valid(...Object.keys(UserProfile)).default(UserProfile.default),
address: Joi.object({
streetAddress: Joi.string().min(5),
city: Joi.string().min(3),
state: Joi.string().min(3),
zipCode: Joi.string().min(3),
}),
}).options({ abortEarly: false });
Basically I want make required fields optional with the personValidator.tailor() function at validation time. It works this way, but I was trying to make this behavior into an extension:
const joiExtensions: Joi.ExtensionFactory[] = [
(joi: Joi.Root) => ({
type: /.*/,
rules: {
requiredButPatchable: {
alias: 'reqOrPatch',
method() {
return this.required().alter(optionalIfPatch); // GOT STUCK HERE
}
},
},
}),
];
To be used like this:
Joi.string().token().lowercase().min(5).requiredButPatchable()
But to be honest, I've been researching for three days and couldn't find a proper explanation of how to build an extension like this (using Joi itself).
(Also, when I extend the Joi model, I loose the autocompletion for VSCode... is there a way arround it?)
I've tried the Joi docs, git issues, stack overflow questions... couldn't find a proper answer.
I want to use the alter()and tailor() functionalities as an extension so I can reduce the repeated code...
Related
Using the model.find method I can find documents that match all properties defined within my schema except when a property has a type of mongoose.Schema.ObjectId. In my case, references to the owner of the document. I've tried all sorts of ways to get documents by the owner _id but nothing is working. Anybody know the proper way to do this ?
--EDIT--
Just in case anybody runs into the same issue, I got it working using the following query.
Query:
const users_cars = await Cars.find( { owner: user_id } );
Schema:
const Schema = new mongoose.Schema({
type: Object,
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [ true, 'car must belong to an owner' ]
},
name: String
});
I would like to test the shape of my json in my mocha expectations. Some things I know like 'name' but others(_id) come from the database. For them, I only care that they are set with the proper type.
Here I have my expectation:
expect(object).to.eql({
recipe:
{
_id: '5fa5503a1fa816347f3c93fe',
name: 'thing',
usedIngredients: []
}
})
I would rather do something like this if possible:
expect(object).to.eql({
recipe:
{
_id: is.a('string'),
name: 'thing',
usedIngredients: []
}
})
Does anyone know of a way to accomplish this? Or is it best to just break this up into multiple tests?
You can do this by using chai-json-pattern plugin.
Chai JSON pattern allows you to create blueprints for JavaScript objects to ensure validation of key information. It enables you to use JSON syntax extends with easy to use validators. It came up mostly for testing API with cucumber-js but can be used in any application. Additionally, you can extend base functionality with custom validators
E.g.
const chai = require('chai');
const chaiJsonPattern = require('chai-json-pattern').default;
chai.use(chaiJsonPattern);
const { expect } = chai;
describe('64715893', () => {
it('should pass', () => {
const object = {
recipe: {
_id: Math.random().toString(),
name: 'thing',
usedIngredients: [Math.random() + 'whatever'],
},
};
expect(object).to.matchPattern(`{
"recipe": {
"_id": String,
"name": "thing",
"usedIngredients": Array,
},
}`);
});
});
test result:
64715893
✓ should pass
1 passing (50ms)
If I have in my application the following model:
// employee.js
const mongoose = require('mongoose');
const validators = require('./validators');
const EmployeeSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
referredBy: {
type: Schema.Types.ObjectId,
ref: 'Employee',
validate: {
isAsync: true,
validator: validators.referralValidator
}
}
});
module.exports = mongoose.model('Employee', EmployeeSchema);
Basically, it's a model for an employee in a firm. That employee could have been referred to work at the firm by a different, existing, employee, so there is a self-referencing in the Employees collection.
The validation makes sure that the user doesn't enter an id of an employee who doesn't exist in the database already. It looks like this:
// validators.js
const mongoose = require('mongoose');
function referralValidator(val, callback) {
if (val) {
const Employee = mongoose.model('Employee');
Employee.findById(val.toString(), (err, found) => {
callback(found !== null);
});
} else {
callback(true);
}
}
module.exports = {
referralValidator
};
Now, I would like to test that this validation work (could be one of many other validations that I'll write in the future for other fields that will be added to the model). However, I would like not to hit the database in the test, so I want to stab out the findById to control manually what the value of "found" will be for the test. I couldn't figure out how to go around the mongo driver that mongoose employs behind the scenes.
How could I stab this function? Or, alternatively, is there a better way to implement the validator in order to make it more testable?
This link should be a good starting point on how to test model validations. If you want to mock database, then check out mockgoose
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')
I'm using Joi to validate a JavaScript object in the server. The schema is like the following:
var schema = Joi.object().keys({
displayName: Joi.string().required(),
email: Joi.string().email(),
enabled: Joi.boolean().default(false, "Default as disabled")
}).unknown(false);
The schema above will report an error if there is an unknown key in the object, which is expected, but what I want is to strip all the unknown silently, without an error. Is it possible to be done?
You need to use the stripUnknown option if you want to strip the unknown keys from the objects that you are validating.
cf options on https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback
As in Version 14.3.4, there is a simple solution to this issue. Here is the code that solves the problem for you.
// Sample data for testing.
const user = {
fullname: "jayant malik",
email: "demo#mail.com",
password: "password111",
username: "hello",
name: "Hello"
};
// You define your schema here
const user_schema = joi
.object({
fullname: joi.string().min(4).max(30).trim(),
email: joi.string().email().required().min(10).max(50).trim(),
password: joi.string().min(6).max(20),
username: joi.string().min(5).max(20).alphanum().trim()
})
.options({ stripUnknown: true });
// You validate the object here.
const result = user_schema.validate(user);
// Here is your final result with unknown keys trimmed from object.
console.log("Object with trimmed keys: ", result.value);
const joi = require('joi');
joi.validate(object, schema, {stripUnknown:true}, callback);
Here is the current way to include the strip unknown option:
const validated = customSchema.validate(objForValidation, { stripUnknown: true });
If you pass in an objForValidation that has a key which isn't defined in your customSchema, it will remove that entry before validating.