Initial commit
[yaffs-website] / node_modules / ajv / lib / ajv.js
1 'use strict';
2
3 var compileSchema = require('./compile')
4   , resolve = require('./compile/resolve')
5   , Cache = require('./cache')
6   , SchemaObject = require('./compile/schema_obj')
7   , stableStringify = require('json-stable-stringify')
8   , formats = require('./compile/formats')
9   , rules = require('./compile/rules')
10   , v5 = require('./v5')
11   , util = require('./compile/util')
12   , async = require('./async')
13   , co = require('co');
14
15 module.exports = Ajv;
16
17 Ajv.prototype.compileAsync = async.compile;
18
19 var customKeyword = require('./keyword');
20 Ajv.prototype.addKeyword = customKeyword.add;
21 Ajv.prototype.getKeyword = customKeyword.get;
22 Ajv.prototype.removeKeyword = customKeyword.remove;
23 Ajv.ValidationError = require('./compile/validation_error');
24
25 var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema';
26 var SCHEMA_URI_FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i;
27 function SCHEMA_URI_FORMAT_FUNC(str) {
28   return SCHEMA_URI_FORMAT.test(str);
29 }
30
31 var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ];
32
33 /**
34  * Creates validator instance.
35  * Usage: `Ajv(opts)`
36  * @param {Object} opts optional options
37  * @return {Object} ajv instance
38  */
39 function Ajv(opts) {
40   if (!(this instanceof Ajv)) return new Ajv(opts);
41   var self = this;
42
43   opts = this._opts = util.copy(opts) || {};
44   this._schemas = {};
45   this._refs = {};
46   this._fragments = {};
47   this._formats = formats(opts.format);
48   this._cache = opts.cache || new Cache;
49   this._loadingSchemas = {};
50   this._compilations = [];
51   this.RULES = rules();
52
53   // this is done on purpose, so that methods are bound to the instance
54   // (without using bind) so that they can be used without the instance
55   this.validate = validate;
56   this.compile = compile;
57   this.addSchema = addSchema;
58   this.addMetaSchema = addMetaSchema;
59   this.validateSchema = validateSchema;
60   this.getSchema = getSchema;
61   this.removeSchema = removeSchema;
62   this.addFormat = addFormat;
63   this.errorsText = errorsText;
64
65   this._addSchema = _addSchema;
66   this._compile = _compile;
67
68   opts.loopRequired = opts.loopRequired || Infinity;
69   if (opts.async || opts.transpile) async.setup(opts);
70   if (opts.beautify === true) opts.beautify = { indent_size: 2 };
71   if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true;
72   this._metaOpts = getMetaSchemaOptions();
73
74   if (opts.formats) addInitialFormats();
75   addDraft4MetaSchema();
76   if (opts.v5) v5.enable(this);
77   if (typeof opts.meta == 'object') addMetaSchema(opts.meta);
78   addInitialSchemas();
79
80
81   /**
82    * Validate data using schema
83    * 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.
84    * @param  {String|Object} schemaKeyRef key, ref or schema object
85    * @param  {Any} data to be validated
86    * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`).
87    */
88   function validate(schemaKeyRef, data) {
89     var v;
90     if (typeof schemaKeyRef == 'string') {
91       v = getSchema(schemaKeyRef);
92       if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"');
93     } else {
94       var schemaObj = _addSchema(schemaKeyRef);
95       v = schemaObj.validate || _compile(schemaObj);
96     }
97
98     var valid = v(data);
99     if (v.$async === true)
100       return self._opts.async == '*' ? co(valid) : valid;
101     self.errors = v.errors;
102     return valid;
103   }
104
105
106   /**
107    * Create validating function for passed schema.
108    * @param  {Object} schema schema object
109    * @param  {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords.
110    * @return {Function} validating function
111    */
112   function compile(schema, _meta) {
113     var schemaObj = _addSchema(schema, undefined, _meta);
114     return schemaObj.validate || _compile(schemaObj);
115   }
116
117
118   /**
119    * Adds schema to the instance.
120    * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored.
121    * @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`.
122    * @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead.
123    * @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
124    */
125   function addSchema(schema, key, _skipValidation, _meta) {
126     if (Array.isArray(schema)){
127       for (var i=0; i<schema.length; i++) addSchema(schema[i], undefined, _skipValidation, _meta);
128       return;
129     }
130     // can key/id have # inside?
131     key = resolve.normalizeId(key || schema.id);
132     checkUnique(key);
133     self._schemas[key] = _addSchema(schema, _skipValidation, _meta, true);
134   }
135
136
137   /**
138    * Add schema that will be used to validate other schemas
139    * options in META_IGNORE_OPTIONS are alway set to false
140    * @param {Object} schema schema object
141    * @param {String} key optional schema key
142    * @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
143    */
144   function addMetaSchema(schema, key, skipValidation) {
145     addSchema(schema, key, skipValidation, true);
146   }
147
148
149   /**
150    * Validate schema
151    * @param {Object} schema schema to validate
152    * @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid
153    * @return {Boolean} true if schema is valid
154    */
155   function validateSchema(schema, throwOrLogError) {
156     var $schema = schema.$schema || self._opts.defaultMeta || defaultMeta();
157     var currentUriFormat = self._formats.uri;
158     self._formats.uri = typeof currentUriFormat == 'function'
159                         ? SCHEMA_URI_FORMAT_FUNC
160                         : SCHEMA_URI_FORMAT;
161     var valid;
162     try { valid = validate($schema, schema); }
163     finally { self._formats.uri = currentUriFormat; }
164     if (!valid && throwOrLogError) {
165       var message = 'schema is invalid: ' + errorsText();
166       if (self._opts.validateSchema == 'log') console.error(message);
167       else throw new Error(message);
168     }
169     return valid;
170   }
171
172
173   function defaultMeta() {
174     var meta = self._opts.meta;
175     self._opts.defaultMeta = typeof meta == 'object'
176                               ? meta.id || meta
177                               : self._opts.v5
178                                 ? v5.META_SCHEMA_ID
179                                 : META_SCHEMA_ID;
180     return self._opts.defaultMeta;
181   }
182
183
184   /**
185    * Get compiled schema from the instance by `key` or `ref`.
186    * @param  {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id).
187    * @return {Function} schema validating function (with property `schema`).
188    */
189   function getSchema(keyRef) {
190     var schemaObj = _getSchemaObj(keyRef);
191     switch (typeof schemaObj) {
192       case 'object': return schemaObj.validate || _compile(schemaObj);
193       case 'string': return getSchema(schemaObj);
194       case 'undefined': return _getSchemaFragment(keyRef);
195     }
196   }
197
198
199   function _getSchemaFragment(ref) {
200     var res = resolve.schema.call(self, { schema: {} }, ref);
201     if (res) {
202       var schema = res.schema
203         , root = res.root
204         , baseId = res.baseId;
205       var v = compileSchema.call(self, schema, root, undefined, baseId);
206       self._fragments[ref] = new SchemaObject({
207         ref: ref,
208         fragment: true,
209         schema: schema,
210         root: root,
211         baseId: baseId,
212         validate: v
213       });
214       return v;
215     }
216   }
217
218
219   function _getSchemaObj(keyRef) {
220     keyRef = resolve.normalizeId(keyRef);
221     return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef];
222   }
223
224
225   /**
226    * Remove cached schema(s).
227    * If no parameter is passed all schemas but meta-schemas are removed.
228    * If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
229    * Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
230    * @param  {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object
231    */
232   function removeSchema(schemaKeyRef) {
233     if (schemaKeyRef instanceof RegExp) {
234       _removeAllSchemas(self._schemas, schemaKeyRef);
235       _removeAllSchemas(self._refs, schemaKeyRef);
236       return;
237     }
238     switch (typeof schemaKeyRef) {
239       case 'undefined':
240         _removeAllSchemas(self._schemas);
241         _removeAllSchemas(self._refs);
242         self._cache.clear();
243         return;
244       case 'string':
245         var schemaObj = _getSchemaObj(schemaKeyRef);
246         if (schemaObj) self._cache.del(schemaObj.jsonStr);
247         delete self._schemas[schemaKeyRef];
248         delete self._refs[schemaKeyRef];
249         return;
250       case 'object':
251         var jsonStr = stableStringify(schemaKeyRef);
252         self._cache.del(jsonStr);
253         var id = schemaKeyRef.id;
254         if (id) {
255           id = resolve.normalizeId(id);
256           delete self._schemas[id];
257           delete self._refs[id];
258         }
259     }
260   }
261
262
263   function _removeAllSchemas(schemas, regex) {
264     for (var keyRef in schemas) {
265       var schemaObj = schemas[keyRef];
266       if (!schemaObj.meta && (!regex || regex.test(keyRef))) {
267         self._cache.del(schemaObj.jsonStr);
268         delete schemas[keyRef];
269       }
270     }
271   }
272
273
274   function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
275     if (typeof schema != 'object') throw new Error('schema should be object');
276     var jsonStr = stableStringify(schema);
277     var cached = self._cache.get(jsonStr);
278     if (cached) return cached;
279
280     shouldAddSchema = shouldAddSchema || self._opts.addUsedSchema !== false;
281
282     var id = resolve.normalizeId(schema.id);
283     if (id && shouldAddSchema) checkUnique(id);
284
285     var willValidate = self._opts.validateSchema !== false && !skipValidation;
286     var recursiveMeta;
287     if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema))
288       validateSchema(schema, true);
289
290     var localRefs = resolve.ids.call(self, schema);
291
292     var schemaObj = new SchemaObject({
293       id: id,
294       schema: schema,
295       localRefs: localRefs,
296       jsonStr: jsonStr,
297       meta: meta
298     });
299
300     if (id[0] != '#' && shouldAddSchema) self._refs[id] = schemaObj;
301     self._cache.put(jsonStr, schemaObj);
302
303     if (willValidate && recursiveMeta) validateSchema(schema, true);
304
305     return schemaObj;
306   }
307
308
309   function _compile(schemaObj, root) {
310     if (schemaObj.compiling) {
311       schemaObj.validate = callValidate;
312       callValidate.schema = schemaObj.schema;
313       callValidate.errors = null;
314       callValidate.root = root ? root : callValidate;
315       if (schemaObj.schema.$async === true)
316         callValidate.$async = true;
317       return callValidate;
318     }
319     schemaObj.compiling = true;
320
321     var currentOpts;
322     if (schemaObj.meta) {
323       currentOpts = self._opts;
324       self._opts = self._metaOpts;
325     }
326
327     var v;
328     try { v = compileSchema.call(self, schemaObj.schema, root, schemaObj.localRefs); }
329     finally {
330       schemaObj.compiling = false;
331       if (schemaObj.meta) self._opts = currentOpts;
332     }
333
334     schemaObj.validate = v;
335     schemaObj.refs = v.refs;
336     schemaObj.refVal = v.refVal;
337     schemaObj.root = v.root;
338     return v;
339
340
341     function callValidate() {
342       var _validate = schemaObj.validate;
343       var result = _validate.apply(null, arguments);
344       callValidate.errors = _validate.errors;
345       return result;
346     }
347   }
348
349
350   /**
351    * Convert array of error message objects to string
352    * @param  {Array<Object>} errors optional array of validation errors, if not passed errors from the instance are used.
353    * @param  {Object} options optional options with properties `separator` and `dataVar`.
354    * @return {String} human readable string with all errors descriptions
355    */
356   function errorsText(errors, options) {
357     errors = errors || self.errors;
358     if (!errors) return 'No errors';
359     options = options || {};
360     var separator = options.separator === undefined ? ', ' : options.separator;
361     var dataVar = options.dataVar === undefined ? 'data' : options.dataVar;
362
363     var text = '';
364     for (var i=0; i<errors.length; i++) {
365       var e = errors[i];
366       if (e) text += dataVar + e.dataPath + ' ' + e.message + separator;
367     }
368     return text.slice(0, -separator.length);
369   }
370
371
372   /**
373    * Add custom format
374    * @param {String} name format name
375    * @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
376    */
377   function addFormat(name, format) {
378     if (typeof format == 'string') format = new RegExp(format);
379     self._formats[name] = format;
380   }
381
382
383   function addDraft4MetaSchema() {
384     if (self._opts.meta !== false) {
385       var metaSchema = require('./refs/json-schema-draft-04.json');
386       addMetaSchema(metaSchema, META_SCHEMA_ID, true);
387       self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID;
388     }
389   }
390
391
392   function addInitialSchemas() {
393     var optsSchemas = self._opts.schemas;
394     if (!optsSchemas) return;
395     if (Array.isArray(optsSchemas)) addSchema(optsSchemas);
396     else for (var key in optsSchemas) addSchema(optsSchemas[key], key);
397   }
398
399
400   function addInitialFormats() {
401     for (var name in self._opts.formats) {
402       var format = self._opts.formats[name];
403       addFormat(name, format);
404     }
405   }
406
407
408   function checkUnique(id) {
409     if (self._schemas[id] || self._refs[id])
410       throw new Error('schema with key or id "' + id + '" already exists');
411   }
412
413
414   function getMetaSchemaOptions() {
415     var metaOpts = util.copy(self._opts);
416     for (var i=0; i<META_IGNORE_OPTIONS.length; i++)
417       delete metaOpts[META_IGNORE_OPTIONS[i]];
418     return metaOpts;
419   }
420 }