Projekt

Obecné

Profil

Stáhnout (10.4 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
var resolve = require('./resolve')
4
  , util = require('./util')
5
  , errorClasses = require('./error_classes')
6
  , stableStringify = require('fast-json-stable-stringify');
7

    
8
var validateGenerator = require('../dotjs/validate');
9

    
10
/**
11
 * Functions below are used inside compiled validations function
12
 */
13

    
14
var ucs2length = util.ucs2length;
15
var equal = require('fast-deep-equal');
16

    
17
// this error is thrown by async schemas to return validation errors via exception
18
var ValidationError = errorClasses.Validation;
19

    
20
module.exports = compile;
21

    
22

    
23
/**
24
 * Compiles schema to validation function
25
 * @this   Ajv
26
 * @param  {Object} schema schema object
27
 * @param  {Object} root object with information about the root schema for this schema
28
 * @param  {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution
29
 * @param  {String} baseId base ID for IDs in the schema
30
 * @return {Function} validation function
31
 */
32
function compile(schema, root, localRefs, baseId) {
33
  /* jshint validthis: true, evil: true */
34
  /* eslint no-shadow: 0 */
35
  var self = this
36
    , opts = this._opts
37
    , refVal = [ undefined ]
38
    , refs = {}
39
    , patterns = []
40
    , patternsHash = {}
41
    , defaults = []
42
    , defaultsHash = {}
43
    , customRules = [];
44

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

    
47
  var c = checkCompiling.call(this, schema, root, baseId);
48
  var compilation = this._compilations[c.index];
49
  if (c.compiling) return (compilation.callValidate = callValidate);
50

    
51
  var formats = this._formats;
52
  var RULES = this.RULES;
53

    
54
  try {
55
    var v = localCompile(schema, root, localRefs, baseId);
56
    compilation.validate = v;
57
    var cv = compilation.callValidate;
58
    if (cv) {
59
      cv.schema = v.schema;
60
      cv.errors = null;
61
      cv.refs = v.refs;
62
      cv.refVal = v.refVal;
63
      cv.root = v.root;
64
      cv.$async = v.$async;
65
      if (opts.sourceCode) cv.source = v.source;
66
    }
67
    return v;
68
  } finally {
69
    endCompiling.call(this, schema, root, baseId);
70
  }
71

    
72
  /* @this   {*} - custom context, see passContext option */
73
  function callValidate() {
74
    /* jshint validthis: true */
75
    var validate = compilation.validate;
76
    var result = validate.apply(this, arguments);
77
    callValidate.errors = validate.errors;
78
    return result;
79
  }
80

    
81
  function localCompile(_schema, _root, localRefs, baseId) {
82
    var isRoot = !_root || (_root && _root.schema == _schema);
83
    if (_root.schema != root.schema)
84
      return compile.call(self, _schema, _root, localRefs, baseId);
85

    
86
    var $async = _schema.$async === true;
87

    
88
    var sourceCode = validateGenerator({
89
      isTop: true,
90
      schema: _schema,
91
      isRoot: isRoot,
92
      baseId: baseId,
93
      root: _root,
94
      schemaPath: '',
95
      errSchemaPath: '#',
96
      errorPath: '""',
97
      MissingRefError: errorClasses.MissingRef,
98
      RULES: RULES,
99
      validate: validateGenerator,
100
      util: util,
101
      resolve: resolve,
102
      resolveRef: resolveRef,
103
      usePattern: usePattern,
104
      useDefault: useDefault,
105
      useCustomRule: useCustomRule,
106
      opts: opts,
107
      formats: formats,
108
      logger: self.logger,
109
      self: self
110
    });
111

    
112
    sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode)
113
                   + vars(defaults, defaultCode) + vars(customRules, customRuleCode)
114
                   + sourceCode;
115

    
116
    if (opts.processCode) sourceCode = opts.processCode(sourceCode);
117
    // console.log('\n\n\n *** \n', JSON.stringify(sourceCode));
118
    var validate;
119
    try {
120
      var makeValidate = new Function(
121
        'self',
122
        'RULES',
123
        'formats',
124
        'root',
125
        'refVal',
126
        'defaults',
127
        'customRules',
128
        'equal',
129
        'ucs2length',
130
        'ValidationError',
131
        sourceCode
132
      );
133

    
134
      validate = makeValidate(
135
        self,
136
        RULES,
137
        formats,
138
        root,
139
        refVal,
140
        defaults,
141
        customRules,
142
        equal,
143
        ucs2length,
144
        ValidationError
145
      );
146

    
147
      refVal[0] = validate;
148
    } catch(e) {
149
      self.logger.error('Error compiling schema, function code:', sourceCode);
150
      throw e;
151
    }
152

    
153
    validate.schema = _schema;
154
    validate.errors = null;
155
    validate.refs = refs;
156
    validate.refVal = refVal;
157
    validate.root = isRoot ? validate : _root;
158
    if ($async) validate.$async = true;
159
    if (opts.sourceCode === true) {
160
      validate.source = {
161
        code: sourceCode,
162
        patterns: patterns,
163
        defaults: defaults
164
      };
165
    }
166

    
167
    return validate;
168
  }
169

    
170
  function resolveRef(baseId, ref, isRoot) {
171
    ref = resolve.url(baseId, ref);
172
    var refIndex = refs[ref];
173
    var _refVal, refCode;
174
    if (refIndex !== undefined) {
175
      _refVal = refVal[refIndex];
176
      refCode = 'refVal[' + refIndex + ']';
177
      return resolvedRef(_refVal, refCode);
178
    }
179
    if (!isRoot && root.refs) {
180
      var rootRefId = root.refs[ref];
181
      if (rootRefId !== undefined) {
182
        _refVal = root.refVal[rootRefId];
183
        refCode = addLocalRef(ref, _refVal);
184
        return resolvedRef(_refVal, refCode);
185
      }
186
    }
187

    
188
    refCode = addLocalRef(ref);
189
    var v = resolve.call(self, localCompile, root, ref);
190
    if (v === undefined) {
191
      var localSchema = localRefs && localRefs[ref];
192
      if (localSchema) {
193
        v = resolve.inlineRef(localSchema, opts.inlineRefs)
194
            ? localSchema
195
            : compile.call(self, localSchema, root, localRefs, baseId);
196
      }
197
    }
198

    
199
    if (v === undefined) {
200
      removeLocalRef(ref);
201
    } else {
202
      replaceLocalRef(ref, v);
203
      return resolvedRef(v, refCode);
204
    }
205
  }
206

    
207
  function addLocalRef(ref, v) {
208
    var refId = refVal.length;
209
    refVal[refId] = v;
210
    refs[ref] = refId;
211
    return 'refVal' + refId;
212
  }
213

    
214
  function removeLocalRef(ref) {
215
    delete refs[ref];
216
  }
217

    
218
  function replaceLocalRef(ref, v) {
219
    var refId = refs[ref];
220
    refVal[refId] = v;
221
  }
222

    
223
  function resolvedRef(refVal, code) {
224
    return typeof refVal == 'object' || typeof refVal == 'boolean'
225
            ? { code: code, schema: refVal, inline: true }
226
            : { code: code, $async: refVal && !!refVal.$async };
227
  }
228

    
229
  function usePattern(regexStr) {
230
    var index = patternsHash[regexStr];
231
    if (index === undefined) {
232
      index = patternsHash[regexStr] = patterns.length;
233
      patterns[index] = regexStr;
234
    }
235
    return 'pattern' + index;
236
  }
237

    
238
  function useDefault(value) {
239
    switch (typeof value) {
240
      case 'boolean':
241
      case 'number':
242
        return '' + value;
243
      case 'string':
244
        return util.toQuotedString(value);
245
      case 'object':
246
        if (value === null) return 'null';
247
        var valueStr = stableStringify(value);
248
        var index = defaultsHash[valueStr];
249
        if (index === undefined) {
250
          index = defaultsHash[valueStr] = defaults.length;
251
          defaults[index] = value;
252
        }
253
        return 'default' + index;
254
    }
255
  }
256

    
257
  function useCustomRule(rule, schema, parentSchema, it) {
258
    if (self._opts.validateSchema !== false) {
259
      var deps = rule.definition.dependencies;
260
      if (deps && !deps.every(function(keyword) {
261
        return Object.prototype.hasOwnProperty.call(parentSchema, keyword);
262
      }))
263
        throw new Error('parent schema must have all required keywords: ' + deps.join(','));
264

    
265
      var validateSchema = rule.definition.validateSchema;
266
      if (validateSchema) {
267
        var valid = validateSchema(schema);
268
        if (!valid) {
269
          var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
270
          if (self._opts.validateSchema == 'log') self.logger.error(message);
271
          else throw new Error(message);
272
        }
273
      }
274
    }
275

    
276
    var compile = rule.definition.compile
277
      , inline = rule.definition.inline
278
      , macro = rule.definition.macro;
279

    
280
    var validate;
281
    if (compile) {
282
      validate = compile.call(self, schema, parentSchema, it);
283
    } else if (macro) {
284
      validate = macro.call(self, schema, parentSchema, it);
285
      if (opts.validateSchema !== false) self.validateSchema(validate, true);
286
    } else if (inline) {
287
      validate = inline.call(self, it, rule.keyword, schema, parentSchema);
288
    } else {
289
      validate = rule.definition.validate;
290
      if (!validate) return;
291
    }
292

    
293
    if (validate === undefined)
294
      throw new Error('custom keyword "' + rule.keyword + '"failed to compile');
295

    
296
    var index = customRules.length;
297
    customRules[index] = validate;
298

    
299
    return {
300
      code: 'customRule' + index,
301
      validate: validate
302
    };
303
  }
304
}
305

    
306

    
307
/**
308
 * Checks if the schema is currently compiled
309
 * @this   Ajv
310
 * @param  {Object} schema schema to compile
311
 * @param  {Object} root root object
312
 * @param  {String} baseId base schema ID
313
 * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean)
314
 */
315
function checkCompiling(schema, root, baseId) {
316
  /* jshint validthis: true */
317
  var index = compIndex.call(this, schema, root, baseId);
318
  if (index >= 0) return { index: index, compiling: true };
319
  index = this._compilations.length;
320
  this._compilations[index] = {
321
    schema: schema,
322
    root: root,
323
    baseId: baseId
324
  };
325
  return { index: index, compiling: false };
326
}
327

    
328

    
329
/**
330
 * Removes the schema from the currently compiled list
331
 * @this   Ajv
332
 * @param  {Object} schema schema to compile
333
 * @param  {Object} root root object
334
 * @param  {String} baseId base schema ID
335
 */
336
function endCompiling(schema, root, baseId) {
337
  /* jshint validthis: true */
338
  var i = compIndex.call(this, schema, root, baseId);
339
  if (i >= 0) this._compilations.splice(i, 1);
340
}
341

    
342

    
343
/**
344
 * Index of schema compilation in the currently compiled list
345
 * @this   Ajv
346
 * @param  {Object} schema schema to compile
347
 * @param  {Object} root root object
348
 * @param  {String} baseId base schema ID
349
 * @return {Integer} compilation index
350
 */
351
function compIndex(schema, root, baseId) {
352
  /* jshint validthis: true */
353
  for (var i=0; i<this._compilations.length; i++) {
354
    var c = this._compilations[i];
355
    if (c.schema == schema && c.root == root && c.baseId == baseId) return i;
356
  }
357
  return -1;
358
}
359

    
360

    
361
function patternCode(i, patterns) {
362
  return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');';
363
}
364

    
365

    
366
function defaultCode(i) {
367
  return 'var default' + i + ' = defaults[' + i + '];';
368
}
369

    
370

    
371
function refValCode(i, refVal) {
372
  return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];';
373
}
374

    
375

    
376
function customRuleCode(i) {
377
  return 'var customRule' + i + ' = customRules[' + i + '];';
378
}
379

    
380

    
381
function vars(arr, statement) {
382
  if (!arr.length) return '';
383
  var code = '';
384
  for (var i=0; i<arr.length; i++)
385
    code += statement(i, arr);
386
  return code;
387
}
(5-5/10)