AJV validate schema with JSONPath strings - node.js

I'm trying to create a JSON schema that can support validating JSON objects with property values that can either be regular JSON types OR strings representing valid JSONpath expressions.
So for example, given this schema:
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"type": "number"
}
}
}
Either of these JSON objects could be valid:
{
"age": 30
}
{
"age" "$.age"
}
I've gotten stuck trying to add a custom keyword called jsonPath like this:
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"type": "number",
"jsonPath": true
}
}
}
ajv.addKeyword('jsonPath', {
valid: true,
compile: () => data => {
return /^\$./.test(data)
}
})
Ideally I would love to just be able to check if a given property value is a valid JSONPath string and if so, approve it. Otherwise let ajv run it's own validation.
Thanks for any help!

I don't know if you can prevent other keywords from running. There are multiple ways to apply checks in JSON Schema to the same location, so this would likely be pretty difficult and probably not something that's supported by ajv.
You could build this into your schema.
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"anyOf": [
{
"type": "number"
},
{
"pattern": "REGEX FOR JSON PATH"
}
]
}
}
}
You could de-duplicate the regex by using definitions and referencing it using $ref.

Related

Create Query in before hook in feathers js for nested object

how can i create a query in before hook that will match nested object and return desired result ? I have objects like following in rethinkdb and I want to use something like hook.params.query = {paymentAccounting : {Contact : {Name : 'XYZ'}}} in the before find hook . It seems working for a straight forward object but what about nested object?How to match paymentAccounting >Contact > Name ?
{
"id": "5f45451a-653a-4dc7-b135-25dec3aa25b5",
"paymentAccounting": {
"Account": {
"value": "4"
},
"Amount": 50,
"Contact": {
"ContactID": "68",
"Name": "XYZ"
},
"Invoice": {
"Date": "2018-01-05",
"InvoiceID": "230"
},
"PaymentID": "237"
}
}
feathers-rethinkdb does not support querying nested fields at the moment but it is possible to customize the query and add your custom query parameters in a hook:
app.service('mesages').hooks({
before: {
find(context) {
const query = this.createQuery(context.params.query);
const searchString = "my search string";
hook.params.rethinkdb = query.filter(doc => {
return doc('paymentAccounting')('Contact')('name').eq('XYZ')
});
}
}
});

Using string functions in Cloudformation for Lambda Code

I have a cloudformation snippet that looks roughly like this:
"LambdaScale": {
"Type": "AWS::Lambda::Function",
"Properties": {
...
"Code": {
"S3Bucket": {
{
"Ref": "LambdaBucket"
}
},
"S3Key": {
"Fn::Join": [
"/",
[
{
"Ref": "LambdaDirectoryKey"
},
"some_func.zip"
]
]
}
},
...
}
But when I try to run this, I get the following error:
An error occurred (ValidationError) when calling the CreateStack
operation: Template format error:
[/Resources/LambdaScale/Type/Code/S3Bucket] map keys must be strings;
received a map instead
Reading this, it makes me think that the S3Bucket and S3Key properties are expecting a string literal, and do not support the string manipulation functions. Can this really be true? If so, that is a huge barrier for deploying these templates on different environments.
Is there perhaps a workaround I have not considered? Thanks for any advice!
You have:
"S3Bucket": {
{
"Ref": "LambdaBucket"
}
},
it should probably be:
"S3Bucket": {
"Ref": "LambdaBucket"
},

MongoDB: Query model and check if document contains object or not, then mark / group result

I have a Model called Post, witch contains an property array with user-ids for users that have liked this post.
Now, i need to query the post model, and mark the returned results with likedBySelf true/false for use in by client - is this possible?
I dont have to store the likedBySelf property in the database, just modify the results to have that property.
A temporary solution i found was to do 2 queries, one that finds the posts that is liked by user x, and the ones that have not been liked by user x, and en map (setting likedBySelf true/false) and combine the 2 arrays and return the combined array. But this gives some limitations to other query functions such as limit and skip.
So now my queries looks like this:
var notLikedByQuery = Post.find({likedBy: {$ne: req.body.user._id}})
var likedByQuery = Post.find({likedBy: req.body.user._id})
(I'm using the Mongoose lib)
PS. A typical post can look like this (JSON):
{
"_id": {
"$oid": "55fc463c83b2d2501f563544"
},
"__t": "Post",
"groupId": {
"$oid": "55fc463c83b2d2501f563545"
},
"inactiveAfter": {
"$date": "2015-09-25T17:13:32.426Z"
},
"imageUrl": "https://hootappprodstorage.blob.core.windows.net/devphotos/55fc463b83b2d2501f563543.jpeg",
"createdBy": {
"$oid": "55c49e2d40b3b5b80cbe9a03"
},
"inactive": false,
"recentComments": [],
"likes": 8,
"likedBy": [
{
"$oid": "558b2ce70553f7e807f636c7"
},
{
"$oid": "559e8573ed7c830c0a677c36"
},
{
"$oid": "559e85bced7c830c0a677c43"
},
{
"$oid": "559e854bed7c830c0a677c32"
},
{
"$oid": "559e85abed7c830c0a677c40"
},
{
"$oid": "55911104be2f86e81d0fb573"
},
{
"$oid": "559e858fed7c830c0a677c3b"
},
{
"$oid": "559e8586ed7c830c0a677c3a"
}
],
"location": {
"type": "Point",
"coordinates": [
10.01941398718396,
60.96738099591897
]
},
"updatedAt": {
"$date": "2015-09-22T08:45:41.480Z"
},
"createdAt": {
"$date": "2015-09-18T17:13:32.426Z"
},
"__v": 8
}
#tskippe you can use a method like following to process whether the post is liked by the user himself and call the function anywhere you want.
var processIsLiked = function(postId, userId, doc, next){
var q = Post.find({post_id: postId});
q.lean().exec(function(err,res){
if(err) return utils.handleErr(err, res);
else {
if(_.find(doc.post.likedBy,userId)){ //if LikedBy array contains the user
doc.post.isLiked = true;
} else {
doc.post.isLiked = false;
}
});
next(doc);
}
});
}
Because you are using q.lean() you dont need to actually persist the data. You need to just process it , add isLiked field in the post and send back the response. **note that we are manuplating doc directly. Also you chan tweek it to accept doc containing array of posts and iterating it and attach an isLiked field to each post.
I found that MongoDB's aggregation with $project tequnique was my best bet. So i wrote up an aggregation like this.
Explanation:
Since i want to keep the entire document, but $project purpose is to modify the docs, thus you have to specify the properties you want to keep. A simple way of keeping all the properties is to use "$$ROOT".
So i define a $project, set all my original properties to doc: "$$ROOT", then create a new property "likedBySelf", which is marked true / false if a specified USERID is in the $likedBy set.
I think that this is more clean and simple, than querying every single model after a query to set a likedBySelf flag. It may not be faster, but its cleaner.
Model.aggregate([
{ $project: {
doc: "$$ROOT",
likedBySelf: {
$cond: {
"if": { "$setIsSubset": [
[USERID],
"$likedBy"
]},
"then": true,
"else": false
}
}
}}
]);

ElasticSearch MultiField Search Query

I have an endpoint that I am proxying into ElasticSearch API for a simple user search I am conducting.
/users?nickname=myUsername&email=myemail#gmail.com&name=John+Smith
Somet details about these parameters are the following
All parameters are optional
nickname can be searched as a full text search (i.e. 'myUser' would return 'myUsername')
email must be an exact match
name can be searched as full text search for each token (i.e. 'john' would return 'John Smith')
The ElasticSearch search call should treat the parameters collectively as AND'd.
Right now, I am not truly sure where to start as I am able to execute the query on each of the parameters alone, but not all together.
client.search({
index: 'users',
type: 'user',
body: {
"query": {
//NEED TO FILL THIS IN
}
}
}).then(function(resp){
//Do something with search results
});
First you need to create the mapping for this particular use case.
curl -X PUT "http://$hostname:9200/myindex/mytype/_mapping" -d '{
"mytype": {
"properties": {
"email": {
"type": "string",
"index": "not_analyzed"
},
"nickname": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}'
Here by making email as not_analyzed , you are making sure only the exact match works.
Once that is done , you need to make the query.
As we have multiple conditions , it would be a good idea to use bool query.
You can combine multiple queries and how to handle them using bool query
Query -
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "qbox"
}
},
{
"prefix": {
"nickname": "qbo"
}
},
{
"match": {
"email": "me#qbox.io"
}
}
]
}
}
}
Using the prefix query , you are telling Elasticsearch that even if the token starts with qbo , qualify it as a match.
Also prefix query might not be very fast , in that case you can go for ngram analyzer - http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

mongodb update push array

I have the following schema. I am using node.js with mongodb
attributes: {
type: { type: 'string' },
title: { type:'string' },
description: { type:'string' },
is_active: { type:'boolean',defaultsTo:true },
createdBy: { type:'json' },
attachments:{ type:'array' }
}
arr = [{
'id':attResult.id,
'subtype':type,
'title' : attResult.title,
'body' : attResult.body,
'filetype' : attResult.filetype
}];
I am trying to push a attachments into the 'attachments' array that will be unique to the document.
This is the my query.
books.update(
{ id: refid },
{ $push: { attachments: arr } }
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
What is the problem in my query,no error but not updated attachments.
I want my result to be this:
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
Someone who trying to push the element into an array is possible now, using the native mongodb library.
Considering the following mongodb collection object
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
arr = [{
'id':'123456',
'subtype':'book',
'title' : 'c programing',
'body' :' complete tutorial for c',
'filetype' : '.pdf'
},
{
'id':'123457',
'subtype':'book',
'title' : 'Java programing',
'body' :' complete tutorial for Java',
'filetype' : '.pdf'
}
];
The following query can be used to push the array element to "attachments" at the end. $push or $addToSet can be used for this.
This will be inserting one object or element into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments": arr[0] } } //single object will be pushed to attachemnts
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
This will be inserting each object in the array into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments":{$each: arr} } } // arr will be array of objects
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
Looking at your question a little bit more I'm betting that you are actually using "sails" here even though your question is not tagged as such.
The issue here is that the waterline ODM/ORM has it's own ideas about what sort of operations are actually supported since it tries to be agnostic between working with SQL/NoSQL backends and sort of demands a certain may of doing things.
The result is that updates with $push are not really supported at present and you need more of a JavaScript manipulation affair. So in fact you need to manipulate this via a .findOne and .save() operation:
books.findOne(refid).exec(function(err,book) {
book.attachments.push( arr[0] );
book.save(function(err){
// something here
});
});
Part of that is "waterline" shorthand for what would otherwise be considered an interchangeable use of _id and id as terms, where just specifying the id value as a single argument implies that you are referring to the id value in your query selection.
So unless you replace the waterline ODM/ORM you are pretty much stuck with this AFAIK until there is a decision to maintain this logic in a way that is more consistent with the MongoDB API or otherwise allow access to the "raw" driver interface to perform these .update() operations.
For reference though, and has been alluded to, your general "shell" syntax or what would otherwise be supported in MongoDB specific drivers is like this with the deprecation of the $pushAll operator and the intention being to merge the functionality with the $push and $addToSet operators using the $each modifier:
db.collection.update(
{ "_id": ObjectId(refid) }, // should be important to "cast"
{
"$push": {
"attachments": {
"$each": arr
}
}
}
)
So that syntax would work where it applies, but for you I am thinking that in "sails" it will not.
That gives you some food for thought, and some insight into the correct way to do things.
You are trying to insert an array as an element into your array. You may want to look at $pushAll as a short term solution. This operator is deprecated however see here.
Alternatively you can simply iterate over your array, and each iteration push an element from your array into attachments (this is the recommended approach by Mongo devs).

Resources