more tests, fixed/optimized refs

master
Evgeny Poberezkin 2015-06-06 16:18:52 +01:00
parent 4db4008e2c
commit 64b80cbfff
8 changed files with 355 additions and 69 deletions

View File

@ -16,8 +16,10 @@ var RULES = require('./rules')
module.exports = compile;
function compile(schema) {
var self = this;
function compile(schema, _rootSchema) {
var self = this, refs = [], refIds = {};
_rootSchema = _rootSchema || schema;
var validateCode = validateGenerator({
isRoot: true,
schema: schema,
@ -32,7 +34,8 @@ function compile(schema) {
});
if (this.opts.beautify) {
if (beautify) validateCode = beautify(validateCode, { indent_size: 2 });
var opts = this.opts.beautify === true ? { indent_size: 2 } : this.opts.beautify;
if (beautify) validateCode = beautify(validateCode, opts);
else console.error('"npm install js-beautify" to use beautify option');
}
// console.log('\n\n\n *** \n', validateCode);
@ -45,13 +48,14 @@ function compile(schema) {
return validate;
function resolveRef(ref) {
return resolve.call(self, compile, schema, ref);
}
function validateRef(ref, data) {
var v = ref == '#' ? validate : self._schemas[ref];
var valid = v(data);
return { valid: valid, errors: v.errors };
if (refIds[ref]) return refIds[ref];
var v = resolve.call(self, compile, _rootSchema, ref);
if (v) {
var id = refs.length;
refs.push(v);
refIds[ref] = id;
return id;
}
}
}

View File

@ -2,26 +2,25 @@
module.exports = function resolve(compile, rootSchema, ref) {
var schema = rootSchema;
if (ref[0] != '#')
schema = undefined;
else if (ref != '#') {
if (this._schemas[ref])
schema = this._schemas[ref];
else {
var parts = ref.split('/');
for (var i = 1; i < parts.length; i++) {
if (!schema) break;
var part = unescape(parts[i]);
schema = schema[part];
if (schema.$ref)
schema = resolve.call(this, compile, rootSchema, schema.$ref);
}
if (schema) this._schemas[ref] = compile.call(this, schema);
}
if (ref[0] != '#') return;
if (this._schemas[ref]) return this._schemas[ref];
var schema = _resolve(rootSchema, ref);
if (schema) return this._schemas[ref] = compile.call(this, schema, rootSchema);
};
function _resolve(rootSchema, ref) {
var schema = rootSchema
, parts = ref.split('/');
for (var i = 1; i < parts.length; i++) {
if (!schema) break;
var part = unescape(parts[i]);
schema = schema[part];
if (schema.$ref)
schema = _resolve(rootSchema, schema.$ref);
}
return schema;
};
}
function unescape(str) {

View File

@ -1,11 +1,15 @@
{{# def.definitions }}
{{# def.setup:'$ref' }}
{{? it.resolveRef($schema) }}
var result{{=$lvl}} = validateRef('{{=$schema}}', {{=$data}}, (dataPath || '') + {{= it.errorPath }});
var {{=$valid}} = result{{=$lvl}}.valid;
if (!{{=$valid}}) validate.errors.push.apply(validate.errors, result{{=$lvl}}.errors);
{{? $schema == '#' }}
var {{=$valid}} = validate({{=$data}}, (dataPath || '') + {{= it.errorPath }});
{{??}}
{{# def.error:'$ref' }}
var {{=$valid}} = false;
{{ $refId = it.resolveRef($schema); }}
{{? $refId === undefined }}
{{# def.error:'$ref' }}
var {{=$valid}} = false;
{{??}}
var {{=$valid}} = refs[{{=$refId}}]({{=$data}}, (dataPath || '') + {{= it.errorPath }});
if (!{{=$valid}}) validate.errors.push.apply(validate.errors, refs[{{=$refId}}].errors);
{{?}}
{{?}}

View File

@ -2,6 +2,6 @@
{{# def.setup:'pattern' }}
{{ new RegExp($schema); /* test if regexp is valid to fail at compile time rather than in eval */}}
var {{=$valid}} = /{{=$schema}}/.test({{=$data}});
var {{=$valid}} = new RegExp("{{=$schema}}").test({{=$data}});
{{# def.checkError:'pattern' }}

View File

@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "0.2.7",
"version": "0.2.8",
"description": "Another JSON schema Validator",
"main": "lib/ajv.js",
"scripts": {

View File

@ -38,50 +38,50 @@ for (var id in remoteRefs) {
}
describe('JSON-Schema tests', function () {
addTests('draft4: ', './json-schema-test-suite/tests/draft4/{**/,}*.json');
addTests('JSON-Schema tests draft4', './json-schema-test-suite/tests/draft4/{**/,}*.json');
addTests('Advanced schema tests', './tests/{**/,}*.json');
function addTests(description, testsPath) {
describe(description, function() {
var files = getTestFiles(testsPath);
files.forEach(function (file) {
if (ONLY_RULES && ONLY_RULES.indexOf(file.name) == -1) return;
if (SKIP_RULES && SKIP_RULES.indexOf(file.name) >= 0) return;
function addTests(description, testsPath) {
describe(description, function() {
var files = getTestFiles(testsPath);
describe(file.name, function() {
var testSets = require(file.path);
testSets.forEach(function (testSet) {
// if (testSet.description != 'allOf with base schema') return;
describe(testSet.description, function() {
// it(testSet.description, function() {
var validate = ajv.compile(testSet.schema);
var fullValidate = fullAjv.compile(testSet.schema);
files.forEach(function (file) {
if (ONLY_RULES && ONLY_RULES.indexOf(file.name) == -1) return;
if (SKIP_RULES && SKIP_RULES.indexOf(file.name) >= 0) return;
testSet.tests.forEach(function (test) {
// if (test.description != 'one supplementary Unicode code point is not long enough') return;
// console.log(testSet.schema, '\n\n***\n\n', validate.toString());
it(test.description, function() {
var valid = validate(test.data);
// console.log('result', valid, validate.errors);
assert.equal(valid, test.valid);
if (valid) assert(validate.errors.length == 0);
else assert(validate.errors.length > 0);
describe(file.name, function() {
var testSets = require(file.path);
testSets.forEach(function (testSet) {
// if (testSet.description != 'allOf with base schema') return;
describe(testSet.description, function() {
// it(testSet.description, function() {
var validate = ajv.compile(testSet.schema);
var fullValidate = fullAjv.compile(testSet.schema);
var valid = fullValidate(test.data);
// console.log('full result', valid, fullValidate.errors);
assert.equal(valid, test.valid);
if (valid) assert(fullValidate.errors.length == 0);
else assert(fullValidate.errors.length > 0);
});
testSet.tests.forEach(function (test) {
// if (test.description != 'one supplementary Unicode code point is not long enough') return;
// console.log(testSet.schema, '\n\n***\n\n', validate.toString());
it(test.description, function() {
var valid = validate(test.data);
// console.log('result', valid, validate.errors);
assert.equal(valid, test.valid);
if (valid) assert(validate.errors.length == 0);
else assert(validate.errors.length > 0);
var valid = fullValidate(test.data);
// console.log('full result', valid, fullValidate.errors);
assert.equal(valid, test.valid);
if (valid) assert(fullValidate.errors.length == 0);
else assert(fullValidate.errors.length > 0);
});
});
});
});
});
});
}
});
});
}
function getTestFiles(testsPath) {

View File

@ -0,0 +1,143 @@
[
{
"description": "advanced schema from z-schema benchmark (https://github.com/zaggino/z-schema)",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"/": { "$ref": "#/definitions/entry" }
},
"patternProperties": {
"^(/[^/]+)+$": { "$ref": "#/definitions/entry" }
},
"additionalProperties": false,
"required": [ "/" ],
"definitions": {
"entry": {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/entry/definitions/diskDevice" },
{ "$ref": "#/definitions/entry/definitions/diskUUID" },
{ "$ref": "#/definitions/entry/definitions/nfs" },
{ "$ref": "#/definitions/entry/definitions/tmpfs" }
]
},
"fstype": {
"enum": [ "ext3", "ext4", "btrfs" ]
},
"options": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"uniqueItems": true
},
"readonly": { "type": "boolean" }
},
"definitions": {
"diskDevice": {
"properties": {
"type": { "enum": [ "disk" ] },
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": [ "type", "device" ],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": { "enum": [ "disk" ] },
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": [ "type", "label" ],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": [ "nfs" ] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"anyOf": [
{ "format": "host-name" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": [ "type", "server", "remotePath" ],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": [ "tmpfs" ] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": [ "type", "sizeInMB" ],
"additionalProperties": false
}
}
}
}
},
"tests": [
{
"description": "valid object from z-schema benchmark",
"data": {
"/": {
"storage": {
"type": "disk",
"device": "/dev/sda1"
},
"fstype": "btrfs",
"readonly": true
},
"/var": {
"storage": {
"type": "disk",
"label": "8f3ba6f4-5c70-46ec-83af-0d5434953e5f"
},
"fstype": "ext4",
"options": [ "nosuid" ]
},
"/tmp": {
"storage": {
"type": "tmpfs",
"sizeInMB": 64
}
},
"/var/www": {
"storage": {
"type": "nfs",
"server": "my.nfs.server",
"remotePath": "/exports/mypath"
}
}
},
"valid": true
},
{
"description": "not array",
"data": 1,
"valid": false
}
]
}
]

View File

@ -0,0 +1,136 @@
[
{
"description": "basic schema from z-schema benchmark (https://github.com/zaggino/z-schema)",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product set",
"type": "array",
"items": {
"title": "Product",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "number"
},
"name": {
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
"dimensions": {
"type": "object",
"properties": {
"length": {"type": "number"},
"width": {"type": "number"},
"height": {"type": "number"}
},
"required": ["length", "width", "height"]
},
"warehouseLocation": {
"description": "Coordinates of the warehouse with the product"
}
},
"required": ["id", "name", "price"]
}
},
"tests": [
{
"description": "valid array from z-schema benchmark",
"data": [
{
"id": 2,
"name": "An ice sculpture",
"price": 12.50,
"tags": ["cold", "ice"],
"dimensions": {
"length": 7.0,
"width": 12.0,
"height": 9.5
},
"warehouseLocation": {
"latitude": -78.75,
"longitude": 20.4
}
},
{
"id": 3,
"name": "A blue mouse",
"price": 25.50,
"dimensions": {
"length": 3.1,
"width": 1.0,
"height": 1.0
},
"warehouseLocation": {
"latitude": 54.4,
"longitude": -32.7
}
}
],
"valid": true
},
{
"description": "not array",
"data": 1,
"valid": false
},
{
"description": "array of not onjects",
"data": [1,2,3],
"valid": false
},
{
"description": "missing required properties",
"data": [{}],
"valid": false
},
{
"description": "required property of wrong type",
"data": [{"id": 1, "name": "product", "price": "not valid"}],
"valid": false
},
{
"description": "smallest valid product",
"data": [{"id": 1, "name": "product", "price": 100}],
"valid": true
},
{
"description": "tags should be array",
"data": [{"tags":{}, "id": 1, "name": "product", "price": 100}],
"valid": false
},
{
"description": "dimensions should be object",
"data": [{"dimensions":[], "id": 1, "name": "product", "price": 100}],
"valid": false
},
{
"description": "valid product with tag",
"data": [{"tags":["product"], "id": 1, "name": "product", "price": 100}],
"valid": true
},
{
"description": "dimensions miss required properties",
"data": [{"dimensions":{}, "tags":["product"], "id": 1, "name": "product", "price": 100}],
"valid": false
},
{
"description": "valid product with tag and dimensions",
"data": [{"dimensions":{"length": 7,"width": 12,"height": 9.5}, "tags":["product"], "id": 1, "name": "product", "price": 100}],
"valid": true
}
]
}
]