Using negative lookbehind with jsonschema - javascript

I'm using Node 9.2.0 and ajv 6.0.0.
I have a schema that I wish to use negative lookbehind on, it's defined like:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "release format",
"description": "A Release Format",
"type": "object",
"properties": {
"id": {"type": "integer"},
"title": {
"anyOf": [
{
"type": "string",
"pattern": "^((?!itunes).)*$"
},
{
"type": "string",
"pattern": "^((?!exclusive).)*$"
},
{
"type": "string",
"pattern": "^((?!ringtone).)*$"
}
]
}
}
}
However, it appears that when I try and validate this with AJV using the following data: {"id": 123, "title": "world exclusive"} I don't get a validation error.
The code:
const Ajv = require('ajv');
class Validator {
constructor() {
this.releaseFormatSchema = JSON.parse(fs.readFileSync('./schemas/release-format.json'));
this.schemaValidator = new Ajv({allErrors: true});
}
validate(data) {
let validate = this.schemaValidator.compile(this.releaseFormatSchema);
let valid = validate(data);
console.log(valid);
console.log(validate);
}
}
where data would be: {"id": 123, "title": "world exclusive"}. I would expect this to error, however it's currently telling me that the data is valid.

The answer was also found by #sln and #ClasG, anyOf does a union between titles patterns can match : "all except strings which contains itunes" union "all except strings which contains exclusive" union "...", which means all which not contains all the forbidden keywords. It can be fixed either
using allOf instead of anyOF
"title": {
"allOf": [
{
"type": "string",
"pattern": "^((?!itunes).)*$"
},
{
"type": "string",
"pattern": "^((?!exclusive).)*$"
},
{
"type": "string",
"pattern": "^((?!ringtone).)*$"
}
]
}
using single type/pattern :
"title": {
"type": "string",
"pattern": "^((?!itunes|exclusive|ringtone).)*$"
}

Related

How to validate JSON Schema based on combination of an array of enums?

Given:
Say that I am defining a schema for Contacts. But, I can have "Primary Contact", "Student" or one who is both; and different properties that go with all three choices. The contact types are defined in an array of contact_type: [ "Primary Contact", "Student" ] which can be either one, or both.
Say that the fields are as such per contact type:
If Primary Contact, then I want phone_number
If Student, then I want first_name
If Student and Primary Contact then I want phone_number and first_name
Usage
I use Ajv library to validate in Node.js using a code like such:
function validator(json_schema){
const Ajv = require('ajv');
const ajv = new Ajv({allErrors: true});
return ajv.compile(json_schema)
}
const validate = validator(json_schema);
const valid = validate(input);
console.log(!!valid); //true or false
console.log(validate.errors)// object or null
Note: I've had trouble with allErrors: true while using anyOf for this, and I use the output of allErrors to return ALL the missing/invalid fields back to the user rather than returning problems one at a time. Reference: https://github.com/ajv-validator/ajv/issues/980
Schema
I have written the following schema and it works if I do either "Student" or "Primary Contact" but when I pass both, it still wants to validate against ["Student"] or ["Primary Contact"] rather than both.
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"required": [],
"properties": {},
"allOf": [
{
"if": {
"properties": {
"contact_type": {
"contains": {
"allOf": [
{
"type": "string",
"const": "Primary Contact"
},
{
"type": "string",
"const": "Student"
}
]
}
}
}
},
"then": {
"additionalProperties": false,
"properties": {
"contact_type": {
"type": "array",
"items": [
{
"type": "string",
"enum": [
"Student",
"Primary Contact"
]
}
]
},
"phone": {
"type": "string"
},
"first_name": {
"type": "string"
}
},
"required": [
"phone",
"first_name"
]
}
},
{
"if": {
"properties": {
"contact_type": {
"contains": {
"type": "string",
"const": "Student"
}
}
}
},
"then": {
"additionalProperties": false,
"properties": {
"contact_type": {
"type": "array",
"items": [
{
"type": "string",
"enum": [
"Student",
"Primary Contact"
]
}
]
},
"first_name": {
"type": "string"
}
},
"required": [
"first_name"
]
}
},
{
"if": {
"properties": {
"contact_type": {
"contains": {
"type": "string",
"const": "Primary Contact"
}
}
}
},
"then": {
"additionalProperties": false,
"properties": {
"contact_type": {
"type": "array",
"items": [
{
"type": "string",
"enum": [
"Student",
"Primary Contact"
]
}
]
},
"phone": {
"type": "string"
}
},
"required": [
"phone"
]
}
}
]
}
Example Valid Inputs:
For just ["Primary Contact"]:
{
"contact_type":["Primary Contact"],
"phone":"something"
}
For just ["Student"]:
{
"contact_type":["Student"],
"first_name":"something"
}
For ["Primary Contact", "Student"]
{
"contact_type":["Primary Contact", "Student"],
"phone":"something",
"first_name":"something"
}
Question:
I would like this to validate even if allErrors: true, is this possible? If not, how should I change the schema?
Footnotes
I don't want to change the "contact_type" from being an array unless it is the last resort. (it is a requirement, but can be broken only if there's no other way)
I can't allow any additionalItems, therefore I'm fully defining each object in the if statements although contact_type is common. If I move contact_type out, then I get error messages about passing contact_type as an additionalItem (it looks at the if statement's properties and doesn't see contact_type when it's taken out to the common place). This is why my initial properties object is empty.
Here's how I might go about solving the validation issue: https://jsonschema.dev/s/XLSDB
Here's the Schema...
(It's easier if you try to break up concerns)
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
First, we want to define our conditional checking subschemas...
"definitions": {
"is_student": {
"properties": {
"contact_type": {
"contains": {
"const": "Student"
}
}
}
},
"is_primay_contact": {
"properties": {
"contact_type": {
"contains": {
"const": "Primary Contact"
}
}
}
}
},
Next, I'm assuming you always want contact_type
"required": ["contact_type"],
"properties": {
"contact_type": {
"type": "array",
"items": {
"enum": ["Primary Contact", "Student"]
}
},
And we need to define all the allowed properties in order to prevent additional properties. (draft-07 cannot "see through" applicator keywords like allOf. You can with draft 2019-09 and beyond, but that's another story)
"phone": true,
"first_name": true
},
"additionalProperties": false,
Now, we need to define our structural constraints...
"allOf": [
{
If the contact is a student, first name is required.
"if": { "$ref": "#/definitions/is_student" },
"then": { "required": ["first_name"] }
},
{
If the contact is a primary contact, then phone is required.
"if": { "$ref": "#/definitions/is_primay_contact" },
"then": { "required": ["phone"] }
},
{
However, additionally, if the contact is both a student and a primary contact...
"if": {
"allOf": [
{ "$ref": "#/definitions/is_student" },
{ "$ref": "#/definitions/is_primay_contact" }
]
},
Then we require both phone and first name...
"then": {
"required": ["phone", "first_name"]
},
Otherwise, one of phone or first name is fine (which one is covered by the previous section)
"else": {
"oneOf": [
{
"required": ["phone"]
},
{
"required": ["first_name"]
}
]
}
}
]
}
I'm not convinced this is the cleanest approach, but it does work for the requirements you've provided.
As for getting validation errors you can pass back to your END user... given the conditional requirements you lay out, it's not something you can expect with pure JSON Schema...
Having said that, ajv does provide an extension to add custom error messages, which given the way I've broken the validation down into concerns, might be useable to add custom errors as you're looking to do (https://github.com/ajv-validator/ajv-errors).

Form multiple groups for a complex object in uiSchema

Description
I'm using library react-json-schema to display JSON schema as a form.
I have a requirement, where the form is very large, hence I'm using uiSchema groups to form the tabs. But one of my attributes, which is an object, contains many objects. I want to spread these child objects further into the tabs.
I used the code suggested in #784 . I went through GroupedSchema code and it checks only in properties of schema, if the given field is present or not. It doesn't go deep down inside a nested object.
Steps to Reproduce
1.A dummy Schema:
{
"A": {
"type": "string",
"title": "A"
},
"B": {
"type": "string",
"title": "B"
},
"C": {
"type": "string",
"title": "C"
},
"D": {
"type": "object",
"properties": {
"DA": {
"type": "object",
"properties": {
"DAA": {
"type": "string",
"title": "DAA"
},
"DAB": {
"type": "string",
"title": "DAB"
}
}
},
"DB": {
"type": "object",
"properties": {
"DBA": {
"type": "string",
"title": "DBA"
},
"DBB": {
"type": "string",
"title": "DBB"
}
}
},
"DC": {
"type": "object",
"properties": {
"DCA": {
"type": "string",
"title": "DCA"
},
"DCB": {
"type": "string",
"title": "DCB"
}
}
}
}
}
}
Expected behavior
I'm trying to get tabs for A,B,C,DA,DB,DC
Here is the link to codesandbox

Validate an API response against JSON schema using AJV

I have many days reading a lot and doing several tests with this, without the expected results.
I need validate an API response with its JSON schema (Swagger 2.0). The JSON is longer but I've reduced it to simply. I need to know if "code" and "message" keywords in the response have the type and values defined. Here the code I'm using:
var Ajv = require('ajv');
var ajv = new Ajv();
var schema = {
"host": "cert",
"paths": {
"products": {
"get": {
"responses": {
"401": {
"description": "Problem with the client request",
"headers": {
"x-correlator": {
"type": "string",
"format": "uuid",
"description": "Correlation id"
}
},
"schema": {
"$ref": "file:///../errors.json#/definitions/Unauthenticated"
}
}
}
}
},
"products": {
"get": {
"responses": {
"401": {
"description": "Problem with the client request",
"headers": {
"x-correlator": {
"type": "string",
"format": "uuid",
"description": "Correlation id"
}
},
"schema": {
"$ref": "file:///../errors.json#/definitions/Unauthenticated"
},
"examples": {
"application/json": {
"code": "UNAUTHENTICATED",
"message": "Authentication error"
}
}
}
}
}
}
}
}
var errors_schema = {
"info": {
"description": "Common errors",
"version": "3.0.1",
"title": "Common errors",
"contact": {
"name": "Team"
}
},
"definitions": {
"ModelError": {
"type": "object",
"required": [
"message"
],
"properties": {
"message": {
"type": "string",
"description": "A human readable description"
}
}
},
"Unauthenticated": {
"allOf": [
{
"type": "object",
"required": [
"code"
],
"properties": {
"code": {
"type": "string",
"enum": [
"UNAUTHENTICATED"
],
"default": "UNAUTHENTICATED",
"description": "Request not authenticated due to missing, invalid, or expired credentials."
}
}
},
{
"$ref": "#/definitions/ModelError"
}
]
}
}
}
ajv.addSchema(errors_schema, 'file:///../errors.json');
var testajv = ajv.compile(schema);
var response = {"code": 123, "message":"token expired"}
var valid = testajv(response);
console.log(valid);
if(!valid) {
console.log(testajv.errors);
}
As you see the keyword "code" in the response is '123' integer but in the schema it is defined as string. Independently of the value the validation always gets 'true'. What should I do to get my needs? Thanks in advance.
Swagger schema contains multiple JSON schemas organized in API structure, you need to address a correct part of Swagger schema to employ JSON schema for validation.
Please check example code (https://runkit.com/embed/bwj42juwyjo4):
var Ajv = require('ajv');
var ajv = new Ajv();
var schema = {
"host": "cert",
"paths": {
"products": {
"get": {
"responses": {
"401": {
"description": "Problem with the client request",
"headers": {
"x-correlator": {
"type": "string",
"format": "uuid",
"description": "Correlation id"
}
},
"schema": {
"$ref": "errors.json#/definitions/Unauthenticated"
}
}
}
}
},
"products": {
"get": {
"responses": {
"401": {
"description": "Problem with the client request",
"headers": {
"x-correlator": {
"type": "string",
"format": "uuid",
"description": "Correlation id"
}
},
"schema": {
"$ref": "errors.json#/definitions/Unauthenticated"
},
"examples": {
"application/json": {
"code": "UNAUTHENTICATED",
"message": "Authentication error"
}
}
}
}
}
}
}
}
var errors_schema = {
"info": {
"description": "Common errors",
"version": "3.0.1",
"title": "Common errors",
"contact": {
"name": "Team"
}
},
"definitions": {
"ModelError": {
"type": "object",
"required": [
"message"
],
"properties": {
"message": {
"type": "string",
"description": "A human readable description"
}
}
},
"Unauthenticated": {
"allOf": [
{
"type": "object",
"required": [
"code"
],
"properties": {
"code": {
"type": "string",
"enum": [
"UNAUTHENTICATED"
],
"default": "UNAUTHENTICATED",
"description": "Request not authenticated due to missing, invalid, or expired credentials."
}
}
},
{
"$ref": "#/definitions/ModelError"
}
]
}
}
}
ajv.addSchema(errors_schema, 'errors.json');
ajv.addSchema(schema, 'swagger.json')
var testajv = ajv.compile({ $ref: 'errors.json#/definitions/Unauthenticated' });
console.log(testajv({"code": 123, "message":"token expired"}), testajv.errors); // Fails
console.log(testajv({"code": "AAA", "message":"token expired"}), testajv.errors); // Fails
console.log(testajv({"code": "UNAUTHENTICATED", "message":"token expired"}), testajv.errors); // Passes
var testajv2 = ajv.compile({ $ref: 'swagger.json#/paths/products/get/responses/401/schema' });
console.log(testajv2({"code": 123, "message":"token expired"}), testajv2.errors); // Fails
console.log(testajv2({"code": "AAA", "message":"token expired"}), testajv2.errors); // Fails
console.log(testajv2({"code": "UNAUTHENTICATED", "message":"token expired"}), testajv2.errors); // Passes
Some more information in relevant ajv issue: https://github.com/epoberezkin/ajv/issues/195
Use type as number instead of string in code property.
"properties": {
"code": {
"type": "number",
"enum": [
"UNAUTHENTICATED"
],
"default": "UNAUTHENTICATED",
"description": "Request not authenticated due to missing, invalid, or expired credentials."
}
}

json-schema-faker including extra url in results

I'm setting up json-schema-faker to create some mock data for a project.
When I generate the data it includes an extra bit with the following as the last item in the generated data.
"id": "http://json-schema.org/schema#"
Here is my schema (from a file named 'mockDataSchema.js'):
exports.schema = {
"type": "object",
"properties": {
"users": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "object",
"properties": {
"id": {
"type": "number",
"unique": true,
"minimum": 1
},
"firstName": {
"type": "string",
"faker": "name.firstName"
},
"lastName": {
"type": "string",
"faker": "name.lastName"
},
"email": {
"type": "string",
"faker": "internet.email"
}
},
"required": ["id", "firstName", "lastName", "email"]
}
}
},
"required": ["users"]
}
The code to generate the data ('generate-mock-data.js'):
var jsf = require('json-schema-faker')
var schema = require('./mockDataSchema')
var fs = require('fs')
var chalk = require('chalk')
const json = JSON.stringify(jsf(schema))
console.log(json)
fs.writeFile('./src/api/db.json', json, function(err) {
if (err) {
return console.log(chalk.red(err))
} else {
console.log(chalk.green('Mock data generated.'))
}
})
Add the data that is returned:
{
"schema": {
"users": [
{ "id": 25582343, "firstName": "Brycen", "lastName": "Dickens", "email": "Angelica_Jakubowski#hotmail.com" },
{ "id": 39817508, "firstName": "Marisa", "lastName": "Terry", "email": "Arlo.Hermann0#yahoo.com" }
]
},
"id": "http://json-schema.org/schema#"
}
I've been unable to determine why it includes the "id": "http://json-schema.org/schema#"
I would like to get rid of that line. I'll be using this with 'json-server' to provide a mock api and it chokes on that line.
Figured this out.
exports.schema = {
was causing jscon-schema-faker to interpret '.schema' as part of the schema.
Changed to
module.exports = {
and the trailing schema link wnet away.

json schema doesn't work for empty response body

I have write one schema to validate the response body. And set all the items as "required". But when the body return empty array, it till PASS, which supposed should be FAIL. Schema like this:
var schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/MyObject"
},
"definitions": {
"MyObject": {
"type": ["object"],
"properties": {
"transactionId": "integer",
"transactionType": "string",
"bpCode": "string",
"bpId": "string",
"postingDate ": "string",
"dueDate": "string",
"totalAmount": "number",
"balanceDue": "number",
"reconcileAmount": "number",
"debitCredit": "string",
"remarks": "string",
},
"required": ["transactionId", "transactionType", "bpCode", "bpId", "postingDate", "dueDate", "totalAmount", "balanceDue", "reconcileAmount", "debitCredit", "remarks"],
"additionalProperties": false
}
}
};
tests["Valid respong body schema"] = tv4.validate(data.body, schema);
The response like this:
{
"errorCode": null,
"errorMessage": null,
"body": []
}
You should exclude the empty array with:
"type": "array",
"minItems": 1
"items": {
"$ref": "#/definitions/MyObject"
}

Categories

Resources