3 var resolve = require('./resolve')
4 , util = require('./util')
5 , stableStringify = require('json-stable-stringify')
6 , async = require('../async');
10 function loadBeautify(){
11 if (beautify === undefined) {
12 var name = 'js-beautify';
13 try { beautify = require(name).js_beautify; }
14 catch(e) { beautify = false; }
18 var validateGenerator = require('../dotjs/validate');
21 * Functions below are used inside compiled validations function
24 var co = require('co');
25 var ucs2length = util.ucs2length;
26 var equal = require('./equal');
28 // this error is thrown by async schemas to return validation errors via exception
29 var ValidationError = require('./validation_error');
31 module.exports = compile;
35 * Compiles schema to validation function
37 * @param {Object} schema schema object
38 * @param {Object} root object with information about the root schema for this schema
39 * @param {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution
40 * @param {String} baseId base ID for IDs in the schema
41 * @return {Function} validation function
43 function compile(schema, root, localRefs, baseId) {
44 /* jshint validthis: true, evil: true */
45 /* eslint no-shadow: 0 */
48 , refVal = [ undefined ]
55 , keepSourceCode = opts.sourceCode !== false;
57 root = root || { schema: schema, refVal: refVal, refs: refs };
59 var c = checkCompiling.call(this, schema, root, baseId);
60 var compilation = this._compilations[c.index];
61 if (c.compiling) return (compilation.callValidate = callValidate);
63 var formats = this._formats;
64 var RULES = this.RULES;
67 var v = localCompile(schema, root, localRefs, baseId);
68 compilation.validate = v;
69 var cv = compilation.callValidate;
77 if (keepSourceCode) cv.sourceCode = v.sourceCode;
81 endCompiling.call(this, schema, root, baseId);
84 function callValidate() {
85 var validate = compilation.validate;
86 var result = validate.apply(null, arguments);
87 callValidate.errors = validate.errors;
91 function localCompile(_schema, _root, localRefs, baseId) {
92 var isRoot = !_root || (_root && _root.schema == _schema);
93 if (_root.schema != root.schema)
94 return compile.call(self, _schema, _root, localRefs, baseId);
96 var $async = _schema.$async === true;
97 if ($async && !opts.transpile) async.setup(opts);
99 var sourceCode = validateGenerator({
109 validate: validateGenerator,
112 resolveRef: resolveRef,
113 usePattern: usePattern,
114 useDefault: useDefault,
115 useCustomRule: useCustomRule,
121 sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode)
122 + vars(defaults, defaultCode) + vars(customRules, customRuleCode)
127 /* istanbul ignore else */
128 if (beautify) sourceCode = beautify(sourceCode, opts.beautify);
129 else console.error('"npm install js-beautify" to use beautify option');
131 // console.log('\n\n\n *** \n', sourceCode);
132 var validate, validateCode
133 , transpile = opts._transpileFunc;
135 validateCode = $async && transpile
136 ? transpile(sourceCode)
139 var makeValidate = new Function(
154 validate = makeValidate(
168 refVal[0] = validate;
170 console.error('Error compiling schema, function code:', validateCode);
174 validate.schema = _schema;
175 validate.errors = null;
176 validate.refs = refs;
177 validate.refVal = refVal;
178 validate.root = isRoot ? validate : _root;
179 if ($async) validate.$async = true;
180 if (keepSourceCode) validate.sourceCode = sourceCode;
181 if (opts.sourceCode === true) {
191 function resolveRef(baseId, ref, isRoot) {
192 ref = resolve.url(baseId, ref);
193 var refIndex = refs[ref];
194 var _refVal, refCode;
195 if (refIndex !== undefined) {
196 _refVal = refVal[refIndex];
197 refCode = 'refVal[' + refIndex + ']';
198 return resolvedRef(_refVal, refCode);
200 if (!isRoot && root.refs) {
201 var rootRefId = root.refs[ref];
202 if (rootRefId !== undefined) {
203 _refVal = root.refVal[rootRefId];
204 refCode = addLocalRef(ref, _refVal);
205 return resolvedRef(_refVal, refCode);
209 refCode = addLocalRef(ref);
210 var v = resolve.call(self, localCompile, root, ref);
212 var localSchema = localRefs && localRefs[ref];
214 v = resolve.inlineRef(localSchema, opts.inlineRefs)
216 : compile.call(self, localSchema, root, localRefs, baseId);
221 replaceLocalRef(ref, v);
222 return resolvedRef(v, refCode);
226 function addLocalRef(ref, v) {
227 var refId = refVal.length;
230 return 'refVal' + refId;
233 function replaceLocalRef(ref, v) {
234 var refId = refs[ref];
238 function resolvedRef(refVal, code) {
239 return typeof refVal == 'object'
240 ? { code: code, schema: refVal, inline: true }
241 : { code: code, $async: refVal && refVal.$async };
244 function usePattern(regexStr) {
245 var index = patternsHash[regexStr];
246 if (index === undefined) {
247 index = patternsHash[regexStr] = patterns.length;
248 patterns[index] = regexStr;
250 return 'pattern' + index;
253 function useDefault(value) {
254 switch (typeof value) {
259 return util.toQuotedString(value);
261 if (value === null) return 'null';
262 var valueStr = stableStringify(value);
263 var index = defaultsHash[valueStr];
264 if (index === undefined) {
265 index = defaultsHash[valueStr] = defaults.length;
266 defaults[index] = value;
268 return 'default' + index;
272 function useCustomRule(rule, schema, parentSchema, it) {
273 var validateSchema = rule.definition.validateSchema;
274 if (validateSchema && self._opts.validateSchema !== false) {
275 var valid = validateSchema(schema);
277 var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
278 if (self._opts.validateSchema == 'log') console.error(message);
279 else throw new Error(message);
283 var compile = rule.definition.compile
284 , inline = rule.definition.inline
285 , macro = rule.definition.macro;
289 validate = compile.call(self, schema, parentSchema, it);
291 validate = macro.call(self, schema, parentSchema, it);
292 if (opts.validateSchema !== false) self.validateSchema(validate, true);
294 validate = inline.call(self, it, rule.keyword, schema, parentSchema);
296 validate = rule.definition.validate;
299 var index = customRules.length;
300 customRules[index] = validate;
303 code: 'customRule' + index,
311 * Checks if the schema is currently compiled
313 * @param {Object} schema schema to compile
314 * @param {Object} root root object
315 * @param {String} baseId base schema ID
316 * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean)
318 function checkCompiling(schema, root, baseId) {
319 /* jshint validthis: true */
320 var index = compIndex.call(this, schema, root, baseId);
321 if (index >= 0) return { index: index, compiling: true };
322 index = this._compilations.length;
323 this._compilations[index] = {
328 return { index: index, compiling: false };
333 * Removes the schema from the currently compiled list
335 * @param {Object} schema schema to compile
336 * @param {Object} root root object
337 * @param {String} baseId base schema ID
339 function endCompiling(schema, root, baseId) {
340 /* jshint validthis: true */
341 var i = compIndex.call(this, schema, root, baseId);
342 if (i >= 0) this._compilations.splice(i, 1);
347 * Index of schema compilation in the currently compiled list
349 * @param {Object} schema schema to compile
350 * @param {Object} root root object
351 * @param {String} baseId base schema ID
352 * @return {Integer} compilation index
354 function compIndex(schema, root, baseId) {
355 /* jshint validthis: true */
356 for (var i=0; i<this._compilations.length; i++) {
357 var c = this._compilations[i];
358 if (c.schema == schema && c.root == root && c.baseId == baseId) return i;
364 function patternCode(i, patterns) {
365 return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');';
369 function defaultCode(i) {
370 return 'var default' + i + ' = defaults[' + i + '];';
374 function refValCode(i, refVal) {
375 return refVal[i] ? 'var refVal' + i + ' = refVal[' + i + '];' : '';
379 function customRuleCode(i) {
380 return 'var customRule' + i + ' = customRules[' + i + '];';
384 function vars(arr, statement) {
385 if (!arr.length) return '';
387 for (var i=0; i<arr.length; i++)
388 code += statement(i, arr);