Skip to content

Commit

Permalink
Merge pull request #74 from epoberezkin/v2.0
Browse files Browse the repository at this point in the history
V2.0
  • Loading branch information
Evgeny Poberezkin committed Nov 22, 2015
2 parents 9e32fd5 + 31b1e9c commit f3d0536
Show file tree
Hide file tree
Showing 29 changed files with 1,811 additions and 211 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ before_script:
node_js:
- "0.10"
- "0.12"
- "4"
- "5"
after_script:
- codeclimate-test-reporter < coverage/lcov.info
309 changes: 292 additions & 17 deletions README.md

Large diffs are not rendered by default.

105 changes: 95 additions & 10 deletions lib/ajv.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ var compileSchema = require('./compile')
, Cache = require('./cache')
, SchemaObject = require('./compile/schema_obj')
, stableStringify = require('json-stable-stringify')
, formats = require('./compile/formats');
, formats = require('./compile/formats')
, rules = require('./compile/rules')
, v5 = require('./v5');

module.exports = Ajv;

Expand All @@ -31,6 +33,7 @@ function Ajv(opts) {
this._formats = formats(this.opts.format);
this._cache = this.opts.cache || new Cache;
this._loadingSchemas = {};
this.RULES = rules();

// 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
Expand All @@ -43,13 +46,19 @@ function Ajv(opts) {
this.getSchema = getSchema;
this.removeSchema = removeSchema;
this.addFormat = addFormat;
this.addKeyword = addKeyword;
this.errorsText = errorsText;

this._compile = _compile;

addInitialSchemas();
if (this.opts.formats) addInitialFormats();

if (this.opts.errorDataPath == 'property')
this.opts._errorDataPathProperty = true;

if (this.opts.v5) v5.enable(this);


/**
* Validate data using schema
Expand Down Expand Up @@ -196,17 +205,23 @@ function Ajv(opts) {

/**
* Validate schema
* @param {Object} schema schema to validate
* @param {Object} schema schema to validate
* @param {Boolean} throwOrLogError pass true to throw on error
* @return {Boolean}
*/
function validateSchema(schema) {
function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema || META_SCHEMA_ID;
var currentUriFormat = self._formats.uri;
self._formats.uri = typeof currentUriFormat == 'function'
? SCHEMA_URI_FORMAT_FUNC
: SCHEMA_URI_FORMAT;
var valid = validate($schema, schema);
self._formats.uri = currentUriFormat;
if (!valid && throwOrLogError) {
var message = 'schema is invalid:' + errorsText();
if (self.opts.validateSchema == 'log') console.error(message);
else throw new Error(message);
}
return valid;
}

Expand Down Expand Up @@ -265,13 +280,8 @@ function Ajv(opts) {
var id = resolve.normalizeId(schema.id);
if (id) checkUnique(id);

var ok = skipValidation || self.opts.validateSchema === false
|| validateSchema(schema);
if (!ok) {
var message = 'schema is invalid:' + errorsText();
if (self.opts.validateSchema == 'log') console.error(message);
else throw new Error(message);
}
if (self.opts.validateSchema !== false && !skipValidation)
validateSchema(schema, true);

var localRefs = resolve.ids.call(self, schema);

Expand Down Expand Up @@ -324,6 +334,12 @@ function Ajv(opts) {
}


/**
* Convert array of error message objects to string
* @param {Array<Object>} errors optional array of validation errors, if not passed errors from the instance are used.
* @param {Object} opts optional options with properties `separator` and `dataVar`.
* @return {String}
*/
function errorsText(errors, opts) {
errors = errors || self.errors;
if (!errors) return 'No errors';
Expand All @@ -338,12 +354,81 @@ function Ajv(opts) {
}


/**
* Add custom format
* @param {String} name format name
* @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
*/
function addFormat(name, format) {
if (typeof format == 'string') format = new RegExp(format);
self._formats[name] = format;
}


/**
* Define custom keyword
* @param {String} keyword custom keyword, should be a valid identifier, should be different from all standard, custom and macro keywords.
* @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`.
*/
function addKeyword(keyword, definition) {
if (self.RULES.keywords[keyword])
throw new Error('Keyword ' + keyword + ' is already defined');

if (definition.macro) {
if (definition.type) throw new Error('type cannot be defined for macro keywords');
_addMacro(keyword, definition.macro);
} else {
var dataType = definition.type;
if (Array.isArray(dataType)) {
var i, len = dataType.length;
for (i=0; i<len; i++) checkDataType(dataType[i]);
for (i=0; i<len; i++) _addRule(keyword, dataType[i], definition);
} else {
if (dataType) checkDataType(dataType);
_addRule(keyword, dataType, definition);
}
}

self.RULES.keywords[keyword] = true;
self.RULES.all[keyword] = true;
}


function _addRule(keyword, dataType, definition) {
var ruleGroup;
for (var i=0; i<self.RULES.length; i++) {
var rg = self.RULES[i];
if (rg.type == dataType) {
ruleGroup = rg;
break;
}
}

if (!ruleGroup) {
ruleGroup = { type: dataType, rules: [] };
self.RULES.push(ruleGroup);
}

var rule = { keyword: keyword, definition: definition, custom: true };
ruleGroup.rules.push(rule);
}


function _addMacro(keyword, macro) {
var macros = self.RULES.macros;
var rule = { keyword: keyword, macro: macro };
if (macros) macros[macros.length] = rule;
else self.RULES.macros = [rule];
self.RULES.allMacros = self.RULES.allMacros || {};
self.RULES.allMacros[keyword] = true;
}


function checkDataType(dataType) {
if (!self.RULES.types[dataType]) throw new Error('Unknown type ' + dataType);
}


function addInitialSchemas() {
if (self.opts.meta !== false) {
var metaSchema = require('./refs/json-schema-draft-04.json');
Expand Down
65 changes: 54 additions & 11 deletions lib/compile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

var resolve = require('./resolve')
, util = require('./util')
, equal = require('./equal');
, equal = require('./equal')
, macro = require('./macro')
, stableStringify = require('json-stable-stringify');

try { var beautify = require('' + 'js-beautify').js_beautify; } catch(e) {}

var RULES = require('./rules')
, validateGenerator = require('../dotjs/validate');
var validateGenerator = require('../dotjs/validate');

module.exports = compile;

Expand All @@ -18,11 +19,14 @@ function compile(schema, root, localRefs, baseId) {
, refVal = [ undefined ]
, refs = {}
, patterns = []
, patternsHash = {};
, patternsHash = {}
, customRules = []
, customRulesHash = {};

root = root || { schema: schema, refVal: refVal, refs: refs };

var formats = this._formats;
var RULES = this.RULES;

return localCompile(schema, root, localRefs, baseId);

Expand All @@ -32,6 +36,9 @@ function compile(schema, root, localRefs, baseId) {
if (_root.schema != root.schema)
return compile.call(self, _schema, _root, localRefs, baseId);

if (self.RULES.macros && macro.hasMacro(_schema, RULES))
_schema = util.deepClone(_schema);

var validateCode = validateGenerator({
isTop: true,
schema: _schema,
Expand All @@ -46,26 +53,31 @@ function compile(schema, root, localRefs, baseId) {
resolve: resolve,
resolveRef: resolveRef,
usePattern: usePattern,
useCustomRule: useCustomRule,
expandMacros: macro.expand,
opts: self.opts,
formats: formats
formats: formats,
self: self
});

validateCode = refsCode(refVal) + patternsCode(patterns) + validateCode;
validateCode = refsCode(refVal) + patternsCode(patterns)
+ customRulesCode(customRules) + validateCode;

if (self.opts.beautify) {
var opts = self.opts.beautify === true ? { indent_size: 2 } : self.opts.beautify;
/* istanbul ignore else */
if (beautify) validateCode = beautify(validateCode, opts);
else console.error('"npm install js-beautify" to use beautify option');
}
// console.log('\n\n\n *** \n', validateCode);
var validate;
// try {
try {
eval(validateCode);
refVal[0] = validate;
// } catch(e) {
// console.log('Error compiling schema, function code:', validateCode);
// throw e;
// }
} catch(e) {
console.log('Error compiling schema, function code:', validateCode);
throw e;
}

validate.schema = _schema;
validate.errors = null;
Expand Down Expand Up @@ -137,6 +149,27 @@ function compile(schema, root, localRefs, baseId) {
}
return 'pattern' + index;
}

function useCustomRule(rule, schema, parentSchema, it) {
var compile = rule.definition.compile
, inline = rule.definition.inline;

var validate;
if (compile)
validate = compile.call(self, schema, parentSchema);
else if (inline)
validate = inline.call(self, it, schema, parentSchema);
else
validate = rule.definition.validate;

var index = customRules.length;
customRules[index] = validate;

return {
code: 'customRule' + index,
validate: validate
};
}
}


Expand All @@ -160,6 +193,16 @@ function refCode(i, refVal) {
}


function customRulesCode(customRules) {
return _arrCode(customRules, customRuleCode);
}


function customRuleCode(i, rule) {
return 'var customRule' + i + ' = customRules[' + i + '];';
}


function _arrCode(arr, statement) {
if (!arr.length) return '';
var code = '';
Expand Down
Loading

0 comments on commit f3d0536

Please sign in to comment.