"type" rule passes all tests
parent
7dd2218fd4
commit
8902a1bcfe
|
@ -0,0 +1,3 @@
|
|||
[submodule "JSON-Schema-Test-Suite"]
|
||||
path = JSON-Schema-Test-Suite
|
||||
url = https://github.com/epoberezkin/JSON-Schema-Test-Suite
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d319afab25afa93143623a8a395d9cd7211faa33
|
|
@ -1,15 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
var doT = require('dot');
|
||||
var doT = require('dot')
|
||||
, fs = require('fs');
|
||||
|
||||
var RULES = require('./rules')
|
||||
, validateTemplateStr = fs.readFileSync(__dirname + '/validate.dot.js')
|
||||
, validateTemplate = doT.compile(validateTemplateStr);
|
||||
|
||||
module.exports = compile;
|
||||
|
||||
|
||||
function compileSchema(schema) {
|
||||
var self = this; // jv instance
|
||||
function compile(schema) {
|
||||
var validateCode = validateTemplate.call(this, { schema: schema, RULES: RULES });
|
||||
// console.log('\n\n\n *** \n', validateCode);
|
||||
var validate;
|
||||
eval(validateCode);
|
||||
|
||||
return {
|
||||
validate: function(data, opts) { return true; },
|
||||
validate: validate,
|
||||
schema: schema,
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs')
|
||||
, doT = require('dot');
|
||||
|
||||
var RULES = module.exports = {
|
||||
type: { code: fs.readFileSync(__dirname + '/type.dot.js') },
|
||||
enum: { code: '' },
|
||||
allOf: { code: '' },
|
||||
anyOf: { code: '' },
|
||||
oneOf: { code: '' },
|
||||
not: { code: '' },
|
||||
maximum: {
|
||||
code: '',
|
||||
type: 'number'
|
||||
},
|
||||
minimum: {
|
||||
code: '',
|
||||
type: 'number'
|
||||
},
|
||||
multipleOf: {
|
||||
code: '',
|
||||
type: 'number'
|
||||
},
|
||||
maxLength: {
|
||||
code: '',
|
||||
type: 'string'
|
||||
},
|
||||
minLength: {
|
||||
code: '',
|
||||
type: 'string'
|
||||
},
|
||||
pattern: {
|
||||
code: '',
|
||||
type: 'string'
|
||||
},
|
||||
additionalItems: {
|
||||
code: '',
|
||||
type: 'array'
|
||||
},
|
||||
items: {
|
||||
code: '',
|
||||
type: 'array'
|
||||
},
|
||||
maxItems: {
|
||||
code: '',
|
||||
type: 'array'
|
||||
},
|
||||
minItems: {
|
||||
code: '',
|
||||
type: 'array'
|
||||
},
|
||||
uniqueItems: {
|
||||
code: '',
|
||||
type: 'array'
|
||||
},
|
||||
maxProperties: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
minProperties: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
required: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
additionalProperties: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
properties: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
patternProperties: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
},
|
||||
dependencies: {
|
||||
code: '',
|
||||
type: 'object'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
for (var r in RULES) RULES[r].code = doT.compile(RULES[r].code);
|
|
@ -0,0 +1,32 @@
|
|||
{{
|
||||
var $schema = it.schema;
|
||||
var $isArray = Array.isArray($schema);
|
||||
if ($isArray && $schema.length == 1) {
|
||||
$schema = $schema[0];
|
||||
$isArray = false;
|
||||
}
|
||||
}}
|
||||
|
||||
{{? $isArray }}
|
||||
{{? $schema.indexOf('integer') >= 0 }}
|
||||
valid = {{~ $schema:$t:i }}
|
||||
{{? $t != 'integer' }}
|
||||
{{? $notFirst }} || {{?}}
|
||||
{{ var $notFirst = true; }}
|
||||
dataType == '{{=$t}}'
|
||||
{{?}}
|
||||
{{~}};
|
||||
if (!valid) valid = dataType == 'number' && data === parseInt(data);
|
||||
{{??}}
|
||||
valid = {{~ $schema:$t:i }}
|
||||
{{? i }} || {{?}}
|
||||
dataType == '{{=$t}}'
|
||||
{{~}};
|
||||
{{?}}
|
||||
{{??}}
|
||||
valid = dataType == {{? $schema == 'integer' }}
|
||||
'number' && data === parseInt(data)
|
||||
{{??}}
|
||||
'{{= $schema }}'
|
||||
{{?}};
|
||||
{{?}}
|
|
@ -0,0 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
validate = function(data) {
|
||||
var types = [], errors = [], valid, error;
|
||||
|
||||
{{ var $paths = []; /* do I need it? */ }}
|
||||
{{ var $opts = this.opts; }}
|
||||
|
||||
{{ (function $generateValidator($schema) { }}
|
||||
|
||||
var dataType = typeof data;
|
||||
if (dataType == 'object') {
|
||||
if (Array.isArray(data)) dataType = 'array';
|
||||
else if (data == null) dataType = 'null';
|
||||
}
|
||||
types.push(dataType);
|
||||
|
||||
{{ var $schemaKeys = Object.keys($schema); }}
|
||||
{{ /* sort keys so that those that are easier to fail (e.g. type) are validated sooner */ }}
|
||||
|
||||
{{~ $schemaKeys:$key }}
|
||||
{{ var $rule = it.RULES[$key]; }}
|
||||
{{? $rule }}
|
||||
var rule = RULES.{{=$key}};
|
||||
|
||||
/* check if rule applies to data type */
|
||||
if ( !rule.type || rule.type == dataType ) {
|
||||
{{ $paths.push($key); /* do I need it? */ }}
|
||||
{{= $rule.code({ schema: $schema[$key], generate: $generateValidator }) }}
|
||||
|
||||
if (!valid) {
|
||||
{{? $opts.allErrors }}
|
||||
errors.push(error);
|
||||
{{??}}
|
||||
return {
|
||||
valid: false,
|
||||
errors: [error]
|
||||
{{? $opts.verbose }}
|
||||
, data: data
|
||||
{{?}}
|
||||
};
|
||||
{{?}}
|
||||
}
|
||||
|
||||
{{ $paths.pop(); /* do I need it? */ }}
|
||||
{{ /* reporting? */ }}
|
||||
}
|
||||
{{?}}
|
||||
{{~}}
|
||||
|
||||
{{ })(it.schema); }}
|
||||
|
||||
return {
|
||||
valid: {{? $opts.allErrors }}!errors.length{{??}}true{{?}},
|
||||
errors: errors
|
||||
};
|
||||
};
|
133
lib/jv.js
133
lib/jv.js
|
@ -12,90 +12,81 @@ module.exports = Jv;
|
|||
* @return {Object} jv instance
|
||||
*/
|
||||
function Jv(opts) {
|
||||
if (!(this instanceof Jv) return new Jv(opts);
|
||||
this._opts = opts || {};
|
||||
if (!(this instanceof Jv)) return new Jv(opts);
|
||||
var self = this;
|
||||
|
||||
this.opts = opts || {};
|
||||
this._schemas = {};
|
||||
this._byJson = {};
|
||||
}
|
||||
|
||||
Jv.prototype.addSchema = addSchema;
|
||||
Jv.prototype.getSchema = getSchema;
|
||||
Jv.prototype.validate = validate;
|
||||
Jv.prototype.validator = validator;
|
||||
// this is done on purpose, so that methods are bound to the instance
|
||||
// (without using bind) so that they can be used without the instance
|
||||
this.validate = validate;
|
||||
this.compile = compile;
|
||||
this.addSchema = addSchema;
|
||||
this.getSchema = getSchema;
|
||||
|
||||
|
||||
/**
|
||||
* Adds schema to the instance.
|
||||
* @param {String|Object|Array} schema schema or array of schemas. If array is passed, `name` will be ignored.
|
||||
* @param {String} name Optional schema name. Will be used in addition to `schema.id` to find schema by `$ref`.
|
||||
* @return {Object} compiled schema with method `validate` that accepts parameters `data` and `opts`.
|
||||
*/
|
||||
function addSchema(schema, name) {
|
||||
if (Array.isArray(schema)) return schema.map(addSchema, this);
|
||||
name = name || schema.id;
|
||||
if (!name) throw new Error('no schema name');
|
||||
if (this._schemas[name] || this._schemas[schema.id])
|
||||
throw new Error('schema already exists');
|
||||
var compiled = _addSchema.call(this, schema);
|
||||
this._schemas[name] = compiled;
|
||||
if (name != schema.id) this._schemas[schema.id] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
/**
|
||||
* Validate data using schema
|
||||
* Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize.
|
||||
* @param {String|Object} schema
|
||||
* @param {Any} data to be validated
|
||||
* @return {Boolean} validation result. Errors from the last validation will be available in `jv.errors` (and also in compiled schema: `schema.errors`).
|
||||
*/
|
||||
function validate(schema, data) {
|
||||
var compiled = _addSchema(schema);
|
||||
return compiled.validate(data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get schema from the instance by `name` or by `id`
|
||||
* @param {String} name `schema.id` or schema `name` that was passed to `addSchema` (schema will be availbale by id even if the name was passed).
|
||||
* @return {Object} compiled schema with property `schema` and method `validate`.
|
||||
*/
|
||||
function getSchema(name) {
|
||||
return this._schemas[name];
|
||||
}
|
||||
/**
|
||||
* Create validator for passed schema.
|
||||
* @param {String|Object} schema
|
||||
* @return {Object} validation result { valid: true/false, errors: [...] }
|
||||
*/
|
||||
function compile(schema) {
|
||||
var compiled = _addSchema(schema);
|
||||
return function validate(data) {
|
||||
return compiled.validate(data);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function _addSchema(schema) {
|
||||
if (typeof schema == 'string') schema = JSON.parse(schema);
|
||||
if (typeof schema != 'object') throw new Error('schema has invalid type');
|
||||
var str = stableStringify(schema);
|
||||
return (this._byJson[str] = this._byJson[str] || compileSchema.call(this, schema));
|
||||
}
|
||||
/**
|
||||
* Adds schema to the instance.
|
||||
* @param {String|Object|Array} schema schema or array of schemas. If array is passed, `name` will be ignored.
|
||||
* @param {String} id Optional schema id. Will be used in addition to `schema.id` to find schema by `$ref`.
|
||||
* @return {Object} compiled schema with method `validate` that accepts `data`.
|
||||
*/
|
||||
function addSchema(schema, id) {
|
||||
if (Array.isArray(schema)) return schema.map(addSchema);
|
||||
if (!id && !schema.id) throw new Error('no schema id');
|
||||
if (self._schemas[id] || self._schemas[schema.id])
|
||||
throw new Error('schema already exists');
|
||||
var compiled = _addSchema(schema);
|
||||
self._schemas[id] = compiled;
|
||||
self._schemas[schema.id] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate data using schema
|
||||
* Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize.
|
||||
* @param {Any} data to be validated
|
||||
* @param {String|Object} schema
|
||||
* @param {Object} opts optional options that will extend the options passed to the instance.
|
||||
* @return {Boolean} validation result. Errors from the last validation will be available in `jv.errors` (and also in compiled schema: `schema.errors`).
|
||||
*/
|
||||
function validate(data, schema, opts) {
|
||||
var compiled = _addSchema.call(this, schema);
|
||||
var _opts = _getOpts.call(this, opts);
|
||||
return compiled.validate(data, _opts );
|
||||
}
|
||||
/**
|
||||
* Get schema from the instance by by `id`
|
||||
* @param {String} id `schema.id` or `id` that was passed to `addSchema` (schema will be availbale by its internal id even if the id was passed).
|
||||
* @return {Object} compiled schema with property `schema` and method `validate`.
|
||||
*/
|
||||
function getSchema(id) {
|
||||
return self._schemas[id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create validator function for passed schema and options.
|
||||
* @param {String|Object} schema
|
||||
* @param {[type]} opts Optional options
|
||||
* @return {[type]} validation result. Errors from the last validation will be available at `v.errors` where `v` is the created validator function.
|
||||
*/
|
||||
function validator(schema, opts) {
|
||||
var compiled = _addSchema.call(this, schema);
|
||||
var _opts = _getOpts.call(this, opts);
|
||||
var self = this;
|
||||
return function v(data) {
|
||||
var result = compiled.validate(data, _opts);
|
||||
v.errors = compiled.errors;
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function _getOpts(opts) {
|
||||
return opts ? copy(opts, copy(this._opts)) : this._opts;
|
||||
function _addSchema(schema) {
|
||||
if (typeof schema == 'string') schema = JSON.parse(schema);
|
||||
if (typeof schema != 'object') throw new Error('schema has invalid type');
|
||||
var str = stableStringify(schema);
|
||||
return (self._byJson[str] = self._byJson[str] || compileSchema.call(self, schema));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "jv",
|
||||
"version": "0.0.1",
|
||||
"description": "JSON schema validator",
|
||||
"main": "index.js",
|
||||
"main": "lib/jv.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
@ -24,5 +24,9 @@
|
|||
"dependencies": {
|
||||
"dot": "^1.0.3",
|
||||
"json-stable-stringify": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"JSON-Schema-Test-Suite": "git+ssh://git@github.com/json-schema/JSON-Schema-Test-Suite.git",
|
||||
"mocha": "^2.2.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var compileSchema = require('../lib/compile');
|
||||
var mockInstance = { opts: {} };
|
||||
|
||||
describe('Schema compilation', function() {
|
||||
it('works', function() {
|
||||
compileSchema.call(mockInstance, { type: 'string' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, assert = require('assert')
|
||||
, TESTS_PATH = 'JSON-Schema-Test-Suite/tests/draft4/';
|
||||
|
||||
var ONLY_RULES;
|
||||
ONLY_RULES = ['type'];
|
||||
|
||||
|
||||
var jv = require('../lib/jv')();
|
||||
|
||||
describe.only('JSON-Schema tests', function (done) {
|
||||
var testsPath = path.join(__dirname, '..', TESTS_PATH);
|
||||
var files = getTestFilesRecursive(testsPath);
|
||||
|
||||
files.forEach(function (file) {
|
||||
if (ONLY_RULES && ONLY_RULES.indexOf(file.name) == -1) return;
|
||||
describe(file.name, function() {
|
||||
var testSets = require(file.path);
|
||||
testSets.forEach(function (testSet) {
|
||||
describe(testSet.description, function() {
|
||||
var validate;
|
||||
before(function() {
|
||||
validate = jv.compile(testSet.schema);
|
||||
});
|
||||
|
||||
testSet.tests.forEach(function (test) {
|
||||
// if (test.description != 'an integer is valid') return;
|
||||
it(test.description, function() {
|
||||
var result = validate(test.data);
|
||||
assert.equal(result.valid, test.valid);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function getTestFilesRecursive(rootPath) {
|
||||
var list = fs.readdirSync(rootPath);
|
||||
var files = [];
|
||||
list.forEach(function (item) {
|
||||
var itemPath = path.join(rootPath, item);
|
||||
var stat = fs.statSync(itemPath);
|
||||
if (stat.isFile()) files.push({ name: path.basename(item, '.json'), path: itemPath });
|
||||
else if (stat.isDirectory()) {
|
||||
var _files = getTestFilesRecursive(itemPath);
|
||||
_files.forEach(function (f) {
|
||||
files.push({ name: path.join(item, f.name), path: f.path })
|
||||
});
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
|
Loading…
Reference in New Issue