JS Json Schema - AJV - javascript

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!

Related

JOI validation for a string with json as a value

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

Creating a document with { strict: false } in mongoose

The task is to store some documents into MongoDB. The documents have the same top-level but from there they could be different.
The structure of the payload is:
{
"types": "a", //the type can be "a", "b" or "c"
"details" : {
... // the details object structure is different for each type
}
}
and this is the model I wrote:
const Details = { strict: false };
const MyOrder = new Schema({
types: {
type: String,
enum: ['a', 'b', 'c'],
},
details: Details,
});
module.exports = Order = mongoose.model('myOrder', MyOrder);
I set details with { strict: false } because I want to get its data no matter what structure it has. Maybe it's wrong something there.
When a POST request is done, the document saved into the database it looks like this:
_id: ObjectId("...")
types: "a"
__v : 0
It saved the types but nothing about the details.
Is it a way to save the details too?
I managed to solve the problem by not creating another Details object like above but adding { strict: false } inside the Schema. Like this:
const MyOrder = new Schema(
{
types: {
type: String,
enum: ['a', 'b', 'c'],
},
},
{ strict: false }
);
module.exports = Order = mongoose.model('myOrder', MyOrder);

GraphQL.js - use Interface as a default (fallback) type in resolveType function

I'm trying to return generic type in resolveType function if none of the provided types is matched. The example below shows this problem: API worked like a charm supporting UserType and MovieType until in database was added BookType (not supported by GraphQL schema).
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList,
GraphQLInterfaceType
} = require("graphql");
const DATA = [
{
// UserType
name: "catherine woolf",
nick: "catherine"
},
{
// MovieType
name: "cat woman",
director: "Jack Wolfgang"
},
{
// --- missing type --- (BookType)
name: "cats secrets",
author: "Nicky Glace"
}
];
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
};
const SearchableType = new GraphQLInterfaceType({
name: "Searchable",
fields: {
name: { type: GraphQLString }
},
resolveType: resolveType
});
const UserType = new GraphQLObjectType({
name: "User",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
nick: { type: GraphQLString }
}
});
const MovieType = new GraphQLObjectType({
name: "Movie",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
director: { type: GraphQLString }
}
});
const schema = new GraphQLSchema({
types: [MovieType, UserType, SearchableType],
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
search: {
type: new GraphQLList(SearchableType),
args: {
text: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(_, { text }) {
return DATA.filter(d => d.name.indexOf(text) !== -1);
}
}
}
})
});
const query = `
{
search(text: "cat") {
name
... on User {
nick
}
... on Movie {
director
}
}
}
`;
graphql(schema, query).then(result => {
console.log(JSON.stringify(result, null, 2));
});
So now this code ends with error:
"Abstract type Searchable must resolve to an Object type at runtime for field RootQueryType.search with value \"[object Object]\", received \"undefined\". Either the Searchable type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function."
This is nothing surprising since currently resolveType may not return any type.
Workaround
Crate type containing the same field like interface SearchableType (1 to 1 implementation):
const _SearchableType = new GraphQLObjectType({
name: '_Searchable',
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
}
});
Use it as a fallback type:
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
return _SearchableType;
};
And add it to types in schema definition:
types: [MovieType, UserType, SearchableType, _SearchableType],
But the problem with this solution is presence of this dummy _SearchableType in documentation like this:
Question
Is there any way to return interface SearchableType or equivalent of it in resolveType? The key point for me is hiding of this "fallback type" in a documentation.
GraphQL is strongly typed and doesn't support generics or some kind of "fallback" mechanism when resolving unions and interfaces. At the end of the day, if your underlying data layer is returning some type that you have not yet implemented in your schema, the simplest solution is to simply add that type to your schema. Migrations to your database and changes to your schema should go hand-in-hand.
If you want to derive your schema from your storage layer, I would suggest looking into something like PostGraphile (formerly PostGraphQL).
That said, if you're bent on using a workaround, you could just fallback to one of the existing types:
const resolveType = data => {
if (data.nick) {
return UserType
}
return MovieType
}
Now a book's name will still be accessible, provided you query it on the interface and not one of the types. The only downside to this approach is that movie-specific fields will be returned for a book and will resolve to null, but that won't cause any issues unless they're specifically defined as non-null in your schema.

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