Descriptive Hapi/Joi validation error - node.js

I've been trying to implement Joi in our node application (joi as standalone, not with hapi) and it seems to validate the schema properly but the error is always the same
[ValidationError: value must be an object]
name: 'ValidationError',
details:
[ { message: 'value must be an object',
path: 'value',
type: 'object.base',
context: [Object] } ],
_object:.....
I never get the specifics on which key it failed on and description of why it failed.
this is a sample schema I'm using:
exports.workersSchema =
{
workers: joi.array({
id: joi.string().alphanum(),
wID: joi.object({
idValue: joi.string().alphanum()
}),
person: {
governmentIDs: joi.array({itemID: joi.string().alphanum()}),
legalName: joi.object({
givenName: joi.string(),
middleName: joi.string(),
preferredSalutations: joi.array(
{
salutationCode: {
longName: joi.string()
}
}
),
preferredName: joi.object().keys({
FormattedName: joi.string()
}),
}),
birthDate: joi.string().alphanum()
}
})
}
And this is the json object I'm sending :
{"workers" : [
{
"id" : "",
"wID" : {
"idValue" : ""
},
"person" : {
"governmentIDs":[{
"itemID": "asd"
}],
"legalName":{
"givenName" : "PA",
"middleName" : "",
"preferredSalutations" : [{
"salutationCode" : {
"longName" : ""
}
}],
"preferredName" : {
"FormattedName" : ""
},
"birthDate" : ""
}]
}
What am i doing wrong here? I even tried to follow something on the blog and while the examples were showing detailed info I never got anything besides
"value must be an object"
It validates it correctly but when it sees a misfit value it just gives that error and nothing else.
Also, if you look at the 'wID' section it has a 'idValue' object but when I get rid of the idValue and just put a alphanum right on the wID key, it also passes the validation.
ps. When validating keys that are objects. Do I have to validate it with
key: Joi.object({
a:Joi.string()
})
or can I just do?:
key: {
a:Joi.string()
}
Thank you so much for the help!

I think there's a couple of issues. First of all, make sure that the object you're validating against is indeed an object with a workers key. The validation seems to be suggesting that you're not providing an object for this base value (an array perhaps)?
Also in a few instances I think you're using the API incorrectly (e.g. joi.array(...) is not valid). I've modified your schema to work how I think you intended. If not, post a sample object and I'll amend.
var schema = {
workers: Joi.array().required().includes({
id: Joi.string().alphanum(),
wID: {
idValue: Joi.string().alphanum()
},
person: {
governmentIDs: Joi.array().includes(Joi.string().alphanum()),
legalName: {
givenName: Joi.string(),
middleName: Joi.string(),
preferredSalutations: Joi.array().includes(Joi.string()),
preferredName: {
formattedName: Joi.string()
},
},
birthDate: Joi.string().alphanum()
}
})
};
Here's a valid object for that schema:
var goodExample = {
workers: [
{
id: 'bhdsf78473',
wID: {
idValue: 'idvalue1'
},
person: {
governmentIDs: ['id1', 'id2'],
legalName: {
givenName: 'Johnny',
middleName: 'Michael',
preferredSalutations: ['sir', 'Dr'],
preferredName: {
formattedName: 'Sir Johnny Michael Smith'
}
},
birthDate: '2411986'
}
}
]
};
Here's an invalid one:
var badExample = {
workers: [
{
id: 'bhdsf7^£$%^£$%8473', // Here's the issue
wID: {
},
person: {
governmentIDs: ['id1', 'id2'],
legalName: {
givenName: 'Johnny',
middleName: 'Michael',
preferredSalutations: ['sir', 'Dr'],
preferredName: {
formattedName: 'Sir Johnny Michael Smith'
}
},
birthDate: '2411986'
}
},
],
};
Joi should give nice detailed output for Joi.assert(example, schema);:
$ node index.js
/.../node_modules/Joi/lib/index.js:121
throw new Error(message + error.annotate());
^
Error: {
"workers": [
{
"wID": {},
"person": {
"governmentIDs": [
"id1",
"id2"
],
"legalName": {
"givenName": "Johnny",
"middleName": "Michael",
"preferredSalutations": [
"sir",
"Dr"
],
"preferredName": {
"formattedName": "Sir Johnny Michael Smith"
}
},
"birthDate": "2411986"
},
"id" [1]: "bhdsf7^£$%^£$%8473"
}
]
}
[1] workers at position 0 fails because id must only contain alpha-numeric characters
at root.assert (/.../node_modules/Joi/lib/index.js:121:19)
at Object.<anonymous> (/.../index.js:57:5)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:929:3
NOTE: This answer is using Joi 5.1.2 (API: https://github.com/hapijs/joi/blob/v5.1.0/README.md). Joi.array().includes() will be dropped in the next release in favour of Joi.array().items()
The object that you posted is not a valid JavaScript object because it's missing some closing } brackets. Here's the valid version:
var obj = {
"workers" : [{
"id" : "", // <-------- Shouldn't be empty
"wID" : {
"idValue" : ""
},
"person" : {
"governmentIDs":[{
"itemID": "asd"
}],
"legalName":{
"givenName" : "PA",
"middleName" : "",
"preferredSalutations" : [{
"salutationCode" : {
"longName" : ""
}
}],
"preferredName" : {
"FormattedName" : ""
},
},
"birthDate" : ""
}
}]
};
If I validate that with my provided schema, I get the following message (using Joi 5.1.0):
[1] workers at position 0 fails because id is not allowed to be empty

Related

How to prevent unwanted request in node js express?

expected request:
{
"name": "Raju",
"email": "email#email.com"
}
Actual:
{
"name": "Raju",
"email": "email#email.com",
"xyz" : "xxxx"
}
I would like to throw an error or escape for "xyz" in a validation/router level.
Am using the fastest-validator.
is there any other validator supports this feature?
Any help appreciated
You can set $$strict: true like below example:
const schema = {
name: { type: "string" }, // required
$$strict: true // no additional properties allowed
}
v.validate({ name: "John" }, schema); // Valid
v.validate({ name: "John", age: 42 }, schema); // Fail

How to insert multiple JSON document in elastic search

Input Data
[{
"_index": "abc",
"_type": "_doc",
"_id": "QAE",
"_score": 6.514091,
"_source": {
"category": "fruits",
"action": "eating",
"metainfo": {
"hash": "nzUZ1ONm0e167p"
},
"createddate": "2019-10-03T12:37:45.297Z"
}},
{
"_index": "abc",
"_type": "_doc",
"_id": "PQR",
"_score": 6.514091,
"_source": {
"category": "Vegetables",
"action": "eating",
"metainfo": {
"hash": "nzUZ1ONm0e167p"
},
"createddate": "2019-10-03T12:37:45.297Z"
}
}-----------------
----------------]
I have around 30,000 records as input data. How to insert this data in a single query. I tried by
var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
host: '********',
log: 'trace'
});
client.index({
index: "abc",
body: ****input data*****
}).then((res) => {
console.log(res);
}, (err) => {
console.log("err", err);
});
In this code, send input data in the body. but it returns an error. Please suggest to me.
This seems like what are you looking for:
'use strict'
require('array.prototype.flatmap').shim()
const { Client } = require('#elastic/elasticsearch')
const client = new Client({
node: 'http://localhost:9200'
})
async function run () {
await client.indices.create({
index: 'tweets',
body: {
mappings: {
properties: {
id: { type: 'integer' },
text: { type: 'text' },
user: { type: 'keyword' },
time: { type: 'date' }
}
}
}
}, { ignore: [400] })
const dataset = [{
id: 1,
text: 'If I fall, don\'t bring me back.',
user: 'jon',
date: new Date()
}, {
id: 2,
text: 'Winter is coming',
user: 'ned',
date: new Date()
}, {
id: 3,
text: 'A Lannister always pays his debts.',
user: 'tyrion',
date: new Date()
}, {
id: 4,
text: 'I am the blood of the dragon.',
user: 'daenerys',
date: new Date()
}, {
id: 5, // change this value to a string to see the bulk response with errors
text: 'A girl is Arya Stark of Winterfell. And I\'m going home.',
user: 'arya',
date: new Date()
}]
// The major part is below:
const body = dataset.flatMap(doc => [{ index: { _index: 'tweets' } }, doc])
const { body: bulkResponse } = await client.bulk({ refresh: true, body })
//
if (bulkResponse.errors) {
const erroredDocuments = []
// The items array has the same order of the dataset we just indexed.
// The presence of the `error` key indicates that the operation
// that we did for the document has failed.
bulkResponse.items.forEach((action, i) => {
const operation = Object.keys(action)[0]
if (action[operation].error) {
erroredDocuments.push({
// If the status is 429 it means that you can retry the document,
// otherwise it's very likely a mapping error, and you should
// fix the document before to try it again.
status: action[operation].status,
error: action[operation].error,
operation: body[i * 2],
document: body[i * 2 + 1]
})
}
})
console.log(erroredDocuments)
}
const { body: count } = await client.count({ index: 'tweets' })
console.log(count)
}
run().catch(console.log)
Reference link: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/bulk_examples.html

Problem with ottoman not resolving the references

I have two models in my ottoman 1.0.5 setup. One holds contact info which includes an emails array of docs and then the email doc. I can insert new contacts fine as well as emails in docs and the corresponding link in the contact doc for the new email.
Here is my model
const ottoman = require("ottoman")
ottoman.bucket = require("../app").bucket
var ContactModel = ottoman.model("Contact",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
first_name : "string",
last_name : "string",
emails: [
{
ref:"Email"
}
]} )
var EmailModel = ottoman.model("Email",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
type : "string",
address : "string",
name: "string"
} )
module.exports = {
ContactModel : ContactModel,
EmailModel : EmailModel
}
Now to get an contact and all its emails i use this function
app.get("/contacts/:id", function(req, res){
model.ContactModel.getById(req.params.id,{load: ["emails"]}, function(error, contact){
if(error) {
res.status(400).json({ Success: false , Error: error, Message: ""})
}
res.status(200).json({ Success: true , Error: "", Message: "", Data : contact})
})
})
Which returns me this
{
"Success": true,
"Error": "",
"Message": "",
"Data": {
"timestamp": "2019-01-30T23:59:59.188Z",
"emails": [
{
"$ref": "Email",
"$id": "3ec07ba0-aaec-4fd4-a207-c4272cef8d66"
}
],
"_id": "0112f774-4b5d-4b73-b784-60fa9fa2f9ff",
"first_name": "Test",
"last_name": "User"
}
}
if i go and log the contact to my console i get this
OttomanModel(`Contact`, loaded, key:Contact|0112f774-4b5d-4b73-b784-60fa9fa2f9ff, {
timestamp: 2019-01-30T23:59:59.188Z,
emails: [ OttomanModel(`Email`, loaded, key:Email|3ec07ba0-aaec-4fd4-a207-c4272cef8d66, {
timestamp: 2019-01-31T00:36:01.264Z,
_id: '3ec07ba0-aaec-4fd4-a207-c4272cef8d66',
type: 'work',
address: 'test#outlook.com',
name: 'Test Outlook',
}),
OttomanModel(`Email`, loaded, key:Email|93848b71-7696-4ef5-979d-05c19be9d593, {
timestamp: 2019-01-31T04:12:40.603Z,
_id: '93848b71-7696-4ef5-979d-05c19be9d593',
type: 'work',
address: 'newTest#outlook.com',
name: 'Test2 Outlook',
}) ],
_id: '0112f774-4b5d-4b73-b784-60fa9fa2f9ff',
first_name: 'Test',
last_name: 'User',
})
This shows that emails was resolved but why does it not show up in the returned json. On the other hand if i return contact.emails i get the resolved emails just fine. So i hope someone can shed some light on what i am missing here
I asked a similar question on the couchbase forum, and I also found out the solution:
(a slight difference that the result of my search is an array not an object like in your case)
forum.couchbase.com
app.get("/assets", (req, res) => {
AssetModel.find({}, { load: ["assetModelId", "assetGroupId", "assetTypeId"] }, (err, results) => {
if (err) return res.status(400).send("no asset found");
const assets = [];
results.map(asset => {
assets.push({...asset});
});
res.status(200).send(assets)
});
});

Correct Syntax for MongoDB $jsonSchema validation

I am trying to do something similar to the example shown here: https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/
with Node.js and the standard MongoDB driver (I'm not using Mongoose).
I've tried changing the validationAction to warm and the validationLevel to off but even then I still get the same error.
I am assuming I messed up something with the syntax, below is my code
// Database Name
const dbName = 'testproject';
// Connection URL
const url = `mongodb://localhost:27017/${dbName}`;
function createStudents(db) {
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "name", "year", "major", "address.city", "address.street" ],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
gender: {
bsonType: "string",
description: "must be a string and is not required"
},
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
exclusiveMaximum: false,
description: "must be an integer in [ 2017, 3017 ] and is required"
},
major: {
enum: [ "Math", "English", "Computer Science", "History", null ],
description: "can only be one of the enum values and is required"
},
"address.city" : {
bsonType: "string",
description: "must be a string and is required"
},
"address.street" : {
bsonType: "string",
description: "must be a string and is required"
}
}
}
}
})
}
async function insertStudent(db){
await db.collection('students').insertOne({
name: "Alice",
year: 2019,
major: "History",
address: {
city: "NYC",
street: "33rd Street"
}
}).catch(e => console.log(e))
}
// Use connect method to connect to the server
async function connect () {
//Connect to the client
const client = await MongoClient.connect(url, { useNewUrlParser: true }).catch(e => {throw new Error(400)})
const db = client.db(dbName)
//Create the students collection
createStudents(db);
//Insert a Student
await insertStudent(db)
const cursor = await db.collection('students').find({}).toArray();
console.log(cursor)
}
connect();
Cursor is always empty, so no documents gets added to the collection.
The errors I get are the followings:
at Function.create (/Users/myuser/projects/Javascript/mongotest/node_modules/mongodb/node_modules/mongodb-core/lib/error.js:43:12)
at toError (/Users/myuser/projects/Javascript/mongotest/node_modules/mongodb/lib/utils.js:149:22)
at coll.s.topology.insert (/Users/myuser/projects/Javascript/mongotest/node_modules/mongodb/lib/operations/collection_ops.js:848:39)
at /Users/myuser/projects/Javascript/mongotest/node_modules/mongodb/node_modules/mongodb-core/lib/connection/pool.js:532:18
at process._tickCallback (internal/process/next_tick.js:61:11)
driver: true,
name: 'MongoError',
index: 0,
code: 121,
errmsg: 'Document failed validation',
[Symbol(mongoErrorContextSymbol)]: {} }

Sequelize OR condition object

By creating object like this
var condition=
{
where:
{
LastName:"Doe",
FirstName:["John","Jane"],
Age:{
gt:18
}
}
}
and pass it in
Student.findAll(condition)
.success(function(students){
})
It could beautifully generate SQL like this
"SELECT * FROM Student WHERE LastName='Doe' AND FirstName in ("John","Jane") AND Age>18"
However, It is all 'AND' condition, how could I generate 'OR' condition by creating a condition object?
Seems there is another format now
where: {
LastName: "Doe",
$or: [
{
FirstName:
{
$eq: "John"
}
},
{
FirstName:
{
$eq: "Jane"
}
},
{
Age:
{
$gt: 18
}
}
]
}
Will generate
WHERE LastName='Doe' AND (FirstName = 'John' OR FirstName = 'Jane' OR Age > 18)
See the doc: http://docs.sequelizejs.com/en/latest/docs/querying/#where
String based operators will be deprecated in the future (You've probably seen the warning in console).
Getting this to work with symbolic operators was quite confusing for me, and I've updated the docs with two examples.
Post.findAll({
where: {
[Op.or]: [{authorId: 12}, {authorId: 13}]
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Post.findAll({
where: {
authorId: {
[Op.or]: [12, 13]
}
}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
Use Sequelize.or:
var condition = {
where: Sequelize.and(
{ name: 'a project' },
Sequelize.or(
{ id: [1,2,3] },
{ id: { lt: 10 } }
)
)
};
Reference (search for Sequelize.or)
Edit: Also, this has been modified and for the latest method see Morio's answer,
In Sequelize version 5 you might also can use this way (full use Operator Sequelize) :
var condition =
{
[Op.or]: [
{
LastName: {
[Op.eq]: "Doe"
},
},
{
FirstName: {
[Op.or]: ["John", "Jane"]
}
},
{
Age:{
[Op.gt]: 18
}
}
]
}
And then, you must include this :
const Op = require('Sequelize').Op
and pass it in :
Student.findAll(condition)
.success(function(students){
//
})
It could beautifully generate SQL like this :
"SELECT * FROM Student WHERE LastName='Doe' OR FirstName in ("John","Jane") OR Age>18"
For Sequelize 4
Query
SELECT * FROM Student WHERE LastName='Doe'
AND (FirstName = "John" or FirstName = "Jane") AND Age BETWEEN 18 AND 24
Syntax with Operators
const Op = require('Sequelize').Op;
var r = await to (Student.findAll(
{
where: {
LastName: "Doe",
FirstName: {
[Op.or]: ["John", "Jane"]
},
Age: {
// [Op.gt]: 18
[Op.between]: [18, 24]
}
}
}
));
Notes
Avoid alias operators $ (e.g $and, $or ...) as these will be deprecated
Unless you have {freezeTableName: true} set in the table model then Sequelize will query against the plural form of its name ( Student -> Students )
See the docs about querying.
It would be:
$or: [{a: 5}, {a: 6}] // (a = 5 OR a = 6)
where: {
[Op.or]: [
{
id: {
[Op.in]: recordId,
},
}, {
id: {
[Op.eq]: recordId,
},
},
],
},
This Works For Me !
For those who are facing issue in making more complex query like -
// where email = 'xyz#mail.com' AND (( firstname = 'first' OR lastname = 'last' ) AND age > 18)
would be:
[Op.and]: [
{
"email": { [Op.eq]: 'xyz#mail.com' }
// OR "email": 'xyz#mail.com'
},
{
[Op.and]: [
{
[Op.or]: [
{
"firstname": "first"
},
{
"lastname": "last"
}
]
},
{
"age": { [Op.gt]: 18 }
}]
}
]

Resources