Socket
Socket
Sign inDemoInstall

ajv

Package Overview
Dependencies
Maintainers
1
Versions
355
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ajv - npm Package Compare versions

Comparing version 0.2.9 to 0.3.0

spec/resolve.spec.js

97

lib/ajv.js
'use strict';
var compileSchema = require('./compile')
, resolve = require('./compile/resolve')
, stableStringify = require('json-stable-stringify')

@@ -8,2 +9,4 @@

var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema';
/**

@@ -21,2 +24,4 @@ * Creates validator instance.

this._schemas = {};
this._refs = {};
this._missing = {};
this._byJson = {};

@@ -32,3 +37,3 @@

if (this.opts.meta !== false)
addSchema(require('./refs/json-schema-draft-04.json'));
addSchema(require('./refs/json-schema-draft-04.json'), META_SCHEMA_ID, true);

@@ -38,8 +43,15 @@ /**

* 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 {String|Object} schemaKeyRef key, ref or schema object
* @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 v = _addSchema(schema);
function validate(schemaKeyRef, data) {
if (typeof schemaKeyRef == 'string') {
var v = getSchema(schemaKeyRef);
if (!v) {
v = getRef(schemaKeyRef);
if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"');
}
} else v = _addSchema(schemaKeyRef);
return v(data);

@@ -61,14 +73,13 @@ }

* 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`.
* @param {Object|Array} schema schema or array of schemas. If array is passed, `key` will be ignored.
* @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
* @return {Function} 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 validate = _addSchema(schema);
if (id) self._schemas[id] = validate;
if (schema.id) self._schemas[schema.id] = validate;
function addSchema(schema, key, _skipValidation) {
if (Array.isArray(schema))
return schema.map(function(sch) { return addSchema(sch); });
// can key/id have # inside?
var key = resolve.normalizeId(key || schema.id);
checkUnique(key);
var validate = self._schemas[key] = _addSchema(schema, _skipValidation);
return validate;

@@ -78,18 +89,56 @@ }

function validateSchema(schema) {
var $schema = schema.$schema || META_SCHEMA_ID;
return validate($schema, schema);
}
/**
* 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`.
* Get compiled schema from the instance by `key`.
* @param {String} key `key` that was passed to `addSchema` (or `schema.id`).
* @return {Function} schema validating function (with property `schema`).
*/
function getSchema(id) {
return self._schemas[id];
function getSchema(key) {
key = resolve.normalizeId(key);
return self._schemas[key];
}
function _addSchema(schema) {
if (typeof schema == 'string') schema = JSON.parse(schema);
if (typeof schema != 'object') throw new Error('schema has invalid type');
/**
* Get compiled schema from the instance by `id`.
* @param {String} id `schema.id` or any reference in any of previously passed schemas.
* @return {Function} schema validating function (with property `schema`).
*/
function getRef(ref) {
ref = resolve.normalizeId(ref);
// TODO
return self._refs[ref];
}
function _addSchema(schema, skipValidation) {
if (typeof schema != 'object') throw new Error('schema should be object');
var str = stableStringify(schema);
return (self._byJson[str] = self._byJson[str] || compileSchema.call(self, schema));
if (self._byJson[str]) return self._byJson[str];
var id = resolve.normalizeId(schema.id);
if (id) checkUnique(id);
// var ok = skipValidation || self.opts.validateSchema === false
// || validateSchema(schema);
// if (!ok) throw new Error('schema is invalid');
resolve.ids.call(self, schema);
var validate = self._refs[id] = self._byJson[str] = compileSchema.call(self, schema);
return validate;
}
function checkUnique(id) {
var schemaRef = self._refs[id];
if (self._schemas[id] || (schemaRef && !schemaRef.missing))
throw new Error('schema with key or id "' + id + '" already exists');
}
}

@@ -96,0 +145,0 @@

@@ -32,2 +32,3 @@ 'use strict';

util: util,
resolve: resolve,
resolveRef: resolveRef,

@@ -51,10 +52,13 @@ opts: this.opts

function resolveRef(ref) {
function resolveRef(baseId, ref) {
ref = resolve.url(baseId, ref);
if (refs[ref]) return refs[ref];
var v = resolve.call(self, compile, _rootSchema, ref);
if (v) {
var id = refVal.length;
var refId = refVal.length;
refVal.push(v);
refs[ref] = id;
return id;
refs[ref] = refId;
return refId;
} else {
// TODO register missing ref, create empty entry in refs and return id
}

@@ -61,0 +65,0 @@ }

'use strict';
var url = require('url')
, equal = require('./equal');
module.exports = function resolve(compile, rootSchema, ref) {
if (this._schemas[ref]) return this._schemas[ref];
if (ref[0] != '#') return;
var schema = _resolve(rootSchema, ref);
if (schema) return this._schemas[ref] = compile.call(this, schema, rootSchema);
module.exports = resolve;
resolve.normalizeId = normalizeId;
resolve.fullPath = getFullPath;
resolve.url = resolveUrl;
resolve.ids = resolveIds;
resolve.missing = resolveMissing;
function resolve(compile, rootSchema, ref) {
var refVal = this._refs[ref];
if (typeof refVal == 'string') refVal = this._refs[refVal];
if (typeof refVal == 'function') return refVal;
var refVal = this._schemas[ref];
if (typeof refVal == 'function') return refVal;
var schema = _resolve.call(this, rootSchema, ref);
if (schema) return this._refs[ref] = compile.call(this, schema, rootSchema);
};

@@ -13,10 +27,34 @@

function _resolve(rootSchema, ref) {
var schema = rootSchema
, parts = ref.split('/');
var p = url.parse(ref, false, true)
, refPath = _getFullPath(p)
, baseId = getFullPath(rootSchema.id);
if (refPath !== baseId) {
// rootSchema = undefined; TODO this breaks resolution in meta-schema
var refVal = this._refs[refPath];
if (typeof refVal == 'string') refVal = this._refs[refVal];
if (typeof refVal == 'function') rootSchema = refVal.schema;
else {
var refVal = this._schemas[normalizeId(refPath)];
if (typeof refVal == 'function') rootSchema = refVal.schema;
}
if (!rootSchema) return;
baseId = getFullPath(rootSchema.id);
}
p.hash = p.hash || '';
if (p.hash.slice(0,2) != '#/') return;
var parts = p.hash.split('/');
var schema = rootSchema;
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);
var part = parts[i];
if (part) {
part = unescapeFragment(part);
schema = schema[part];
if (!schema) break;
if (schema.id) baseId = resolveUrl(baseId, schema.id);
if (schema.$ref) {
var $ref = resolveUrl(baseId, schema.$ref);
schema = _resolve.call(this, rootSchema, $ref);
}
}
}

@@ -27,3 +65,3 @@ if (schema != rootSchema) return schema;

function unescape(str) {
function unescapeFragment(str) {
return decodeURIComponent(str)

@@ -33,1 +71,69 @@ .replace(/~1/g, '/')

}
function escapeFragment(str) {
var str = str.replace(/~/g, '~0').replace(/\//g, '~1');
return encodeURIComponent(str);
}
function getFullPath(id, normalize) {
if (normalize !== false) id = normalizeId(id);
var p = url.parse(id, false, true);
return _getFullPath(p);
}
function _getFullPath(p) {
return (p.protocol||'') + (p.protocol?'//':'') + (p.host||'') + (p.path||'') + '#';
}
var TRAILING_SLASH_HASH = /#\/?$/;
function normalizeId(id) {
return id ? id.replace(TRAILING_SLASH_HASH, '') : '';
}
function resolveUrl(baseId, id) {
id = normalizeId(id);
return url.resolve(baseId, id);
}
function resolveIds(schema) {
var id = normalizeId(schema.id);
_resolveIds.call(this, schema, getFullPath(id, false), id);
}
function _resolveIds(schema, fullPath, baseId) {
if (Array.isArray(schema))
for (var i=0; i<schema.length; i++)
_resolveIds.call(this, schema[i], fullPath+'/'+i, baseId);
else if (schema && typeof schema == 'object') {
if (typeof schema.id == 'string') {
var id = baseId = baseId
? url.resolve(baseId, schema.id)
: getFullPath(schema.id);
var refVal = this._refs[id];
if (typeof refVal == 'string') refVal = this._refs[refVal];
if (refVal && refVal.schema) {
if (!equal(schema, refVal.schema))
throw new Error('id "' + id + '" resolves to more than one schema');
} else if (id != normalizeId(fullPath))
this._refs[id] = fullPath;
// TODO check and resolve missing
}
for (var key in schema)
_resolveIds.call(this, schema[key], fullPath+'/'+escapeFragment(key), baseId);
}
}
function resolveMissing(schema, schemaRef) {
}

@@ -7,3 +7,3 @@ {{# def.definitions }}

{{??}}
{{ $id = it.resolveRef($schema); }}
{{ $id = it.resolveRef(it.baseId, $schema); }}
{{? $id === undefined }}

@@ -10,0 +10,0 @@ {{# def.error:'$ref' }}

@@ -20,2 +20,3 @@ {{# def.definitions }}

, $data = 'data';
it.baseId = it.resolve.fullPath(it.schema.id);
delete it.isRoot;

@@ -31,2 +32,4 @@ }}

, $data = 'data' + ($dataLvl || '');
if (it.schema.id) it.baseId = it.resolve.url(it.baseId, it.schema.id);
}}

@@ -113,3 +116,3 @@

function $shouldUseRule($rule) {
var $use = it.schema[$rule.keyword];
var $use = it.schema[$rule.keyword] !== undefined;
if (!$use && $rule.keyword == 'properties') {

@@ -116,0 +119,0 @@ var $pProperties = it.schema.patternProperties

{
"name": "ajv",
"version": "0.2.9",
"version": "0.3.0",
"description": "Another JSON schema Validator",

@@ -29,2 +29,3 @@ "main": "lib/ajv.js",

"devDependencies": {
"chai": "^3.0.0",
"glob": "^5.0.10",

@@ -31,0 +32,0 @@ "js-beautify": "^1.5.6",

@@ -6,5 +6,17 @@ # ajv - Another JSON schema Validator

## JSON-schema standard
ajv implements full JSON-schema draft 4 standard:
- all validation keywords
- full support of remote refs (remote schemas have to be pre-loaded)
- correct string lengths for strings with unicode pairs (can be turned off)
- formats defined by JSON-schema (can be turned off)
ajv passes all the tests from [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (apart from the one that requires that `1.0` is not an integer).
## TODO
- remote refs
- resolve missing remote refs when schemas are added
- custom formats (via options)

@@ -45,7 +57,8 @@ - schema validation before compilation

- _allErrors_: check all rules collecting all errors. Default is to return after the first error.
- _verbose_: include the reference to the validated data in the errors.
- _verbose_: include the reference to the part of the schema and validated data in errors (false by default).
- _format_: validate formats (true by default).
- _meta_: add [meta-schema](http://json-schema.org/documentation.html) so it can be used by other schemas (true by default).
- _uniqueItems_: validate `uniqueItems` (true by default).
- _unicode_: calculate correct length of strings with unicode pairs (true by default - string lengths are calculated correctly but it is slower). Pass `false` to use `string.length` that is faster, but gives "incorrect" lengths of strings with unicode pairs - each unicode pair is counted as two characters.
- _beautify_: format the generated function with [js-beautify](https://github.com/beautify-web/js-beautify). `npm install js-beautify` to use this option.
- _unicode_: calculate correct length of strings with unicode pairs (true by default). Pass `false` to use `.length` of strings that is faster, but gives "incorrect" lengths of strings with unicode pairs - each unicode pair is counted as two characters.
- _beautify_: format the generated function with [js-beautify](https://github.com/beautify-web/js-beautify) (the validating function is generated without line-breaks). `npm install js-beautify` to use this option. `true` or js-beautify options can be passed.

@@ -52,0 +65,0 @@

'use strict';
var Ajv = require('../lib/ajv')
, should = require('chai').should();
describe('Ajv', function () {
var ajv;
beforeEach(function() {
ajv = Ajv();
});
it('should create instance', function() {
ajv .should.be.instanceof(Ajv);
});
describe('compile method', function() {
it('should compile schema and return validating function', function() {
var validate = ajv.compile({ type: 'integer' });
validate .should.be.a('function');
validate(1) .should.equal(true);
validate(1.1) .should.equal(false);
validate('1') .should.equal(false);
});
it('should cache compiled functions for the same schema', function() {
var v1 = ajv.compile({ id: '//e.com/int.json', type: 'integer', minimum: 1 });
var v2 = ajv.compile({ id: '//e.com/int.json', minimum: 1, type: 'integer' });
v1 .should.equal(v2);
})
it('should throw if different schema has the same id', function() {
ajv.compile({ id: '//e.com/int.json', type: 'integer' });
should.throw(function() {
ajv.compile({ id: '//e.com/int.json', type: 'integer', minimum: 1 });
});
})
it.skip('should throw if schema is invalid', function() {
should.throw(function() {
ajv.compile({ type: null });
});
});
});
describe('validate method', function() {
it('should compile schema and validate data against it', function() {
ajv.validate({ type: 'integer' }, 1) .should.equal(true);
ajv.validate({ type: 'integer' }, '1') .should.equal(false);
ajv.validate({ type: 'string' }, 'a') .should.equal(true);
ajv.validate({ type: 'string' }, 1) .should.equal(false);
});
it('should validate against previously compiled schema by id (also see addSchema)', function() {
ajv.validate({ id: '//e.com/int.json', type: 'integer' }, 1) .should.equal(true);
ajv.validate('//e.com/int.json', 1) .should.equal(true);
ajv.validate('//e.com/int.json', '1') .should.equal(false);
ajv.compile({ id: '//e.com/str.json', type: 'string' }) .should.be.a('function');
ajv.validate('//e.com/str.json', 'a') .should.equal(true);
ajv.validate('//e.com/str.json', 1) .should.equal(false);
});
});
describe('addSchema method', function() {
it('should add and compile schema with key', function() {
var validate = ajv.addSchema({ type: 'integer' }, 'int');
validate .should.be.a('function');
validate(1) .should.equal(true);
validate(1.1) .should.equal(false);
validate('1') .should.equal(false);
ajv.validate('int', 1) .should.equal(true);
ajv.validate('int', '1') .should.equal(false);
});
it('should add and compile schema without key', function() {
var validate = ajv.addSchema({ type: 'integer' });
ajv.validate('', 1) .should.equal(true);
ajv.validate('', '1') .should.equal(false);
});
it('should add and compile schema with id', function() {
var validate = ajv.addSchema({ id: '//e.com/int.json', type: 'integer' });
ajv.validate('//e.com/int.json', 1) .should.equal(true);
ajv.validate('//e.com/int.json', '1') .should.equal(false);
});
it('should normalize schema keys and ids', function() {
var validate = ajv.addSchema({ id: '//e.com/int.json#', type: 'integer' }, 'int#');
ajv.validate('int', 1) .should.equal(true);
ajv.validate('int', '1') .should.equal(false);
ajv.validate('//e.com/int.json', 1) .should.equal(true);
ajv.validate('//e.com/int.json', '1') .should.equal(false);
ajv.validate('int#/', 1) .should.equal(true);
ajv.validate('int#/', '1') .should.equal(false);
ajv.validate('//e.com/int.json#/', 1) .should.equal(true);
ajv.validate('//e.com/int.json#/', '1') .should.equal(false);
});
it('should add and compile array of schemas with ids', function() {
var validators = ajv.addSchema([
{ id: '//e.com/int.json', type: 'integer' },
{ id: '//e.com/str.json', type: 'string' }
]);
validators .should.be.an('array').with.length(2);
validators[0](1) .should.equal(true);
validators[0]('1') .should.equal(false);
validators[1]('a') .should.equal(true);
validators[1](1) .should.equal(false);
ajv.validate('//e.com/int.json', 1) .should.equal(true);
ajv.validate('//e.com/int.json', '1') .should.equal(false);
ajv.validate('//e.com/str.json', 'a') .should.equal(true);
ajv.validate('//e.com/str.json', 1) .should.equal(false);
});
it('should throw on duplicate key', function() {
ajv.addSchema({ type: 'integer' }, 'int');
should.throw(function() {
ajv.addSchema({ type: 'integer', minimum: 1 }, 'int');
});
});
it('should throw on duplicate normalized key', function() {
ajv.addSchema({ type: 'number' }, 'num');
should.throw(function() {
ajv.addSchema({ type: 'integer' }, 'num#');
});
should.throw(function() {
ajv.addSchema({ type: 'integer' }, 'num#/');
});
});
it('should allow only one schema without key and id', function() {
ajv.addSchema({ type: 'number' });
should.throw(function() {
ajv.addSchema({ type: 'integer' });
});
should.throw(function() {
ajv.addSchema({ type: 'integer' }, '');
});
should.throw(function() {
ajv.addSchema({ type: 'integer' }, '#');
});
});
});
describe('getSchema method', function() {
it('should return compiled schema by key', function() {
var validate = ajv.addSchema({ type: 'integer' }, 'int');
var v = ajv.getSchema('int');
v .should.equal(validate);
v(1) .should.equal(true);
v('1') .should.equal(false);
});
it('should return compiled schema by id or ref', function() {
var validate = ajv.addSchema({ id: '//e.com/int.json', type: 'integer' });
var v = ajv.getSchema('//e.com/int.json');
v .should.equal(validate);
v(1) .should.equal(true);
v('1') .should.equal(false);
});
it('should return compiled schema without key or with empty key', function() {
var validate = ajv.addSchema({ type: 'integer' });
var v = ajv.getSchema('');
v .should.equal(validate);
v(1) .should.equal(true);
v('1') .should.equal(false);
var v = ajv.getSchema();
v .should.equal(validate);
});
});
});

@@ -17,2 +17,4 @@ 'use strict';

// 'ref',
// 'refRemote',
// 'definitions'
// 'schemas/complex'

@@ -22,3 +24,3 @@ // ];

SKIP_RULES = [
'refRemote',
// 'refRemote',
'optional/zeroTerminatedFloats',

@@ -30,4 +32,4 @@ 'schemas/complex'

var Ajv = require('../lib/ajv')
, ajv = Ajv()
, fullAjv = Ajv({ allErrors: true, verbose: true });
, ajv = Ajv({ beautify: true })
, fullAjv = Ajv({ allErrors: true, verbose: true, beautify: true });

@@ -46,4 +48,6 @@ var remoteRefs = {

addTests('JSON-Schema tests draft4', './json-schema-test-suite/tests/draft4/{**/,}*.json');
addTests('Advanced schema tests', './tests/{**/,}*.json');
describe('Schema validation tests', function() {
addTests('JSON-Schema tests draft4', './json-schema-test-suite/tests/draft4/{**/,}*.json');
addTests('Advanced schema tests', './tests/{**/,}*.json');
});

@@ -63,3 +67,3 @@

testSets.forEach(function (testSet) {
// if (testSet.description != 'allOf with base schema') return;
// if (testSet.description != 'remote ref, containing refs itself') return;
describe(testSet.description, function() {

@@ -74,3 +78,3 @@ var validate, fullValidate;

testSet.tests.forEach(function (test) {
// if (test.description != 'one supplementary Unicode code point is not long enough') return;
// if (test.description != 'changed scope ref valid') return;
// console.log(testSet.schema, '\n\n***\n\n', validate.toString());

@@ -77,0 +81,0 @@ it(test.description, function() {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc