JOI validation for a string with json as a value - javascript

i am trying to validate the string using the JOI package available in npm, i checked this documentation which has many useful string formats e.g. date, IP, base64 but i need to validate the following JSON which contains a stringified JSON as a value, and there is no example in the documentation for such case
{
"id": 232,
"name": "Trojan Horse",
"file": "download.exe",
"infected": true,
"engines": "['Norton', 'AVG', 'NOD32']"
}
So for example what if i want to check engines have valid JSON value and have at-least one engine defined if infected key is set to true
The following schema works only if the engines value is written as parsed JSON
Joi.object().keys({
id: Joi.number().required(),
name: Joi.string().min(5).required(),
file: Joi.string().min(3).required(),
infected: Joi.boolean().required(),
engines: Joi.array().when('infected', {
is: Joi.exists().valid(true),
then: Joi.min(1).required()
})
});

What you need to do is to create a custom JOI validator by extending the array validator of the JOI package and using that custom validator for the engines property.
const custom = Joi.extend({
type: 'array',
base: Joi.array(),
coerce: {
from: 'string',
method(value, helpers) {
if (typeof value !== 'string' ||
value[0] !== '[' && !/^\s*\[/.test(value)) {
return;
}
try {
return { value: JSON.parse(value) };
}
catch (ignoreErr) { }
}
}
});
const schema = Joi.object({
id: Joi.number().required(),
name: Joi.string().min(5).required(),
file: Joi.string().min(3).required(),
infected: Joi.boolean().required(),
engines: custom.array().when('infected', {
is: true,
then: custom.array().min(1).required()
})
})
const validateTest = async (joiSchema, testObject) => {
try {
const value = await joiSchema.validateAsync(testObject);
console.log(value);
}
catch (err) {
console.error(err)
}
};
validateTest(schema, {
"id": 232,
"name": "Trojan Horse",
"file": "download.exe",
"infected": true,
"engines": `["Norton", "AVG", "NOD32"]`
})
You can see more examples like that here

Related

Using findById to find the id of a schema in an array

Hey I was wondering how do I use findById for a schema inside an array? For example, I have the following Schema:
const GameSchema = new mongoose.Schema({
users: [
{
user: { type: mongoose.Schema.ObjectId, ref: 'User' },
role: {
type: String,
required: true,
enum: ['user', 'moderator', 'creator'],
default: 'user',
},
},
]
}]
I want to find the user with a mongoose function like findById, such as the following:
const user = await game.users.findById({ user: req.user.id })
It doesn't seem to work since users is not a mongodb model. I know I can find the user by using find() like the following:
const user = await game.users.find(
(gameUser) => gameUser.user == req.user.id
)
The only problem is that the type of gameUser and req.user.id is not the same and I can't use '==='. Is there some way to go through the array and use the mongoose function findById?
As docs explains, findById method:
Finds a single document by its _id field
So you have to use findOne() instead of findById().
Also, to return only one field from the entire array you can use projection into find.
Check this example. This query find an object by its id (i.e. user field) and return only the object, not the whole array.
db.collection.find({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})
Using mongoose you can do:
yourModel.findOne(({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})).then(result => {
console.log(result)
}).catch(e => {
// error
})

Get localized data from multilingual schema with GraphQL query

I have following MongoDB schema.
Item {
_id: ObjectId,
translations: [{
language: String
name: String
}]
}
So my Item instance could look something like this.
{
_id: ObjectId("5ba3bf09d3121aba3ba2f488"),
translations: [
{
language: "en"
name: "a Car"
},
{
language: "de",
name: "der Wagen"
}]
}
And I want to be able to query my data with specific language with Graphql this way.
{
item(where: {language: "en"}) {
name
}
}
So it would produce nice output with shape like this.
{
name: "a Car"
}
Please can you tell me some good practice or nice way I can setup my Graphql resolvers map?
I'm using Apollo Server.
Thank you very much!
A general solution for a language specific query (with more than one field) could be:
passing the language parameter to the query resolver
store the language on the resolver context
use the language from the context wherever needed
Query:
query {
item(language: "en") {
name
otherField
}
}
Resolver:
{
item: (_, { language }, context) => {
context.language = language;
return {
name: (_, context) => getNameByLang(context.language),
otherField: (_, context) => getOtherByLang(context.language),
};
},
}
Or if there's only one translated field:
query {
item {
name(language: "en")
}
}
so you get the language directly in the name resolver as an argument.
{
item: () => ({
name: ({ language }) => getNameByLang(language),
})
}

JS Json Schema - AJV

I'm using AJV (JS JSON Schema Validator), and I'm trying to find a way to extend the types it supports.
I'm getting this error because In the schema I have a custom type (DocumentReference that I'm validating in python - jsonschema as well)
Error: schema is invalid: data.properties['allow'].properties['custom_signature'].type should be equal to one of the allowed values, data.properties['allow'].properties['custom_signature'].type[0] should be equal to one of the allowed values, data.properties['allow'].properties['custom_signature'].type should match some schema in anyOf
at Ajv.validateSchema (ajv.js?ea76:183)
at Ajv._addSchema (ajv.js?ea76:312)
at Ajv.compile (ajv.js?ea76:112)
at eval (configs.js?76ed:66)
This is a small sample of the schema:
"custom_signature": {
"type": [
"DocumentReference",
"object",
"null"
]
},
In python jsonschema there is a way to extend the types and define how you want to validate them, is there some equivalent in AJV?
var json = {
"type": "object",
"properties": {
"custom_signature": {
"type": [
"DocumentReference",
"null",
"object"
]
}
}
};
const ajv = new Ajv({
allErrors: true
});
console.log(ajv);
const validate = ajv.compile(json);
console.log(validate({'custom_signature': {}}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.4.0/ajv.min.js"></script>
JSFiddle
I just made a module to simplify some AJV issues. It also includes a new function called .addType():
Github: https://github.com/webarthur/super-ajv
NPM: https://www.npmjs.com/package/super-ajv
const ajv = new Ajv()
ajv.addType('mongoid', {
compile: function () {
return function (data) {
const re = /^(?=[a-f\d]{24}$)(\d+[a-f]|[a-f]+\d)/i
return re.test(data)
}
}
})
const schema = {
properties: {
user_id: { type: 'mongoid' }
}
}
You can also:
const schema = {
properties: {
'*name': 'string',
'*email': 'email',
'age': 'number',
'*message': 'string',
}
}
Enjoy!

GraphqlJS- Type conflict- Cannot use union or interface

const {
makeExecutableSchema
} = require('graphql-tools');
const resolvers = require('./resolvers');
const typeDefs = `
type Link {
args: [Custom]
}
union Custom = One | Two
type One {
first: String
second: String
}
type Two {
first: String
second: [String]
}
type Query {
allLinks: [Link]!
}
`;
const ResolverMap = {
Query: {
__resolveType(Object, info) {
console.log(Object);
if (Object.ofType === 'One') {
return 'One'
}
if (Object.ofType === 'Two') {
return 'Two'
}
return null;
}
},
};
// Generate the schema object from your types definition.
module.exports = makeExecutableSchema({
typeDefs,
resolvers,
ResolverMap
});
//~~~~~resolver.js
const links = [
{
"args": [
{
"first": "description",
"second": "<p>Some description here</p>"
},
{
"first": "category_id",
"second": [
"2",
"3",
]
}
]
}
];
module.exports = {
Query: {
//set data to Query
allLinks: () => links,
},
};
I'm confused because the documentary of graphql is so bad. I don't know how to propertly set resolveMap function to be able to use union or interface in schema. For now when I'm using query execution it shows me error that my generated schema cannot use Interface or Union types for execution. How can I execute properly this schema?
resolvers and ResolverMap should be defined together as resolvers. Also, type resolver should be defined for the Custom union type and not for Query.
const resolvers = {
Query: {
//set data to Query
allLinks: () => links,
},
Custom: {
__resolveType(Object, info) {
console.log(Object);
if (Object.ofType === 'One') {
return 'One'
}
if (Object.ofType === 'Two') {
return 'Two'
}
return null;
}
},
};
// Generate the schema object from your types definition.
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
Update:
OP was getting an error "Abstract type Custom must resolve to an Object type at runtime for field Link.args with value \"[object Object]\", received \"null\".". It’s because the conditions in type resolver Object.ofType === 'One' and Object.ofType === 'Two' are always false as there is no field called ofType inside Object. So the resolved type is always null.
To fix that, either add ofType field to each item in args array (links constant in resolvers.js) or change the conditions to something like typeof Object.second === 'string' and Array.isArray(Object.second)

Feathers-mongoose $like query for MongoDb getting Error

I have the following Mongoose Schema -
const myTableSchema = new Schema({
Category: { type: String, required: false },
Tag: { type: String, required: false },
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
});
Note that, both of them are String. I was trying to do a query like the following -
localhost:1971/api/myTable?Category[$like]=Javascript
I have rows with Javascript in Category column. But getting the following error-
{
"name": "GeneralError",
"message": "Can't use $like with String.",
"code": 500,
"className": "general-error",
"data": {},
"errors": {}
}
I know its been a while since this was discussed, but I've had a similar question and the code provided by #Daff helped me a lot, but it contains an error.
query[field].$like is checked for presence, but then query[field].$search is attached to the final query.
The correct code representation (if following the original question) should be:
exports.searchRegex = function () {
return function (hook) {
const query = hook.params.query;
for (let field in query) {
if(query[field].$like && field.indexOf('$') == -1) {
query[field] = { $regex: new RegExp(query[field].$like) }
}
}
hook.params.query = query
return hook
}
}
MongoDB and Mongoose do not have a $like operator. The common way for implementing search (see this FAQ) is adding a hook that converts the searchable term into a MongoDB $regex query, for example to support the $like syntax you are looking for:
exports.searchRegex = function () {
return function (hook) {
const query = hook.params.query;
for (let field in query) {
if(query[field].$like && field.indexOf('$') == -1) {
query[field] = { $regex: new RegExp(query[field].$search) }
}
}
hook.params.query = query
return hook
}
}

Categories