How to use Joi alternatives conditional for schema validation? - javascript

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

Related

Joi array validation ignoring nested keys required

Joi array required validation not working. I have an array userData which contain objects, keys dateMilli and value. I put required everywhere But if I pass an empty array of userData []. It is not throwing any error.
Joi Version is "#hapi/joi": "^16.1.8", struggling with Joi docs but nothing works.
userId is "123" and userData is []
const data = { userId, userData };
const schema = Joi.object({
userId: Joi.string().required(),
userData: Joi.array().items({
dateMilli: Joi.number().required(),
value: Joi.string().valid("YES", "NO").required()
}).required(),
});
let validate = schema.validate(data);
if (!validate || validate.hasOwnProperty("error")) {
return res.send("Invalid parameters");
}
You need to use .min with array to disallow empty arrays.
try this
const data = {"userId": "123", "userData": []};
const Joi = require("#hapi/joi");
const schema = Joi.object({
"userId": Joi.string().required(),
"userData": Joi.array().items({
"dateMilli": Joi.number().required(),
"value": Joi.string().valid("YES", "NO").required()
}).min(1).required()
});
const validate = schema.validate(data);
console.log(validate);

How to validate nested object whose keys should match with outer objects another key whose value is array using Joi?

I have object which I want to validate.
// valid object because all values of keys are present in details object
var object = {
details: {
key1: 'stringValue1',
key2: 'stringValue2',
key3: 'stringValue3'
},
keys: ['key1', 'key2', 'key3']
}
// invalid object as key5 is not present in details
var object = {
details: {
key4: 'stringValue4'
},
keys: ['key4', 'key5']
}
// invalid object as key5 is not present and key8 should not exist in details
var object = {
details: {
key4: 'stringValue4',
key8: 'stringValue8',
},
keys: ['key4', 'key5']
}
All the keys present in keys should be present in details also.
I tried this using Joi.ref()
var schema = Joi.object({
details: Joi.object().keys(Object.assign({}, ...Object.entries({...Joi.ref('keys')}).map(([a,b]) => ({ [b]: Joi.string() })))),
keys: Joi.array()
})
But this is not working because Joi.ref('keys') will get resolved at validation time.
How can I validate this object using Joi?
Using object.pattern and array.length
var schema = Joi.object({
details: Joi.object().pattern(Joi.in('keys'), Joi.string()),
keys: Joi.array().length(Joi.ref('details', {
adjust: (value) => Object.keys(value).length
}))
});
stackblitz
You can validate the array(if you want) then make a dynamic schema and validate that.
const arrSchema = Joi.object({
keys: Joi.array()
});
then,
const newSchema = Joi.object({
details: Joi.object().keys(data.keys.reduce((p, k) => {
p[k] = Joi.string().required();
return p;
},{})),
keys: Joi.array()
})
This should probably do it.
You have to set allowUnknown: true in validate() option.

Joi validation multiple AND conditions

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

spread operator form submission in express.js

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!

GraphQL Cross References throws error: field type must be Output Type but got: [object Object]

I am struggleing at the moment with my GraphQL API and unfortunately it is not working because I get always the error message:
Error: Contact.other field type must be Output Type but got: [object Object].
I still read some articles and posts, like GraphQL with express error : Query.example field type must be Output Type but got: [object Object], but it does not work anyway because the answers does not solve the error reason in my case. I hope you can help me or just give me a hint to solve this problem. I attached the main parts of my code below:
ProfileType.js:
const graphql = require('graphql');
const ContactType = require('./ContactType');
const ObjectType = graphql.GraphQLObjectType;
const List = graphql.GraphQLListType;
const ID = graphql.GraphQLID;
const NonNull = graphql.GraphQLNonNull;
const ProfileType = new ObjectType({
name: 'Profile',
fields: function () {
return {
id: {type: new NonNull(ID)},
contacts: {type: new List(ContactType)},
};
},
});
module.exports = ProfileType;
ContactType.js:
const graphql = require('graphql');
const ProfileType = require('./ProfileType');
const ObjectType = graphql.GraphQLObjectType;
const EnumType = graphql.GraphQLEnumType;
const ContactType = new ObjectType({
name: 'Contact',
fields: function () {
return {
other: {
type: ProfileType
},
status: {
type: new EnumType({
values: {
REQUESTED: {value: 0},
COMMITTED: {value: 1}
},
name: 'ContactStatus'
})
}
};
},
});
module.exports = ContactType;
(Posted on behalf of the OP).
Solved it by moving the required ObjectType to the fields function:
const ContactType = new ObjectType({
name: 'Contact',
fields: function () {
const ProfileType = require('./ProfileType');
// ...
}
});
Otherwise, the ObjectType has problems with the circularity. The same have to be done of course with the ProfileType.

Categories