Project

General

Profile

Download (31.4 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / common / jsonschema / attribute.js @ 57ce414c

1
/* SPDX-License-Identifier: MIT
2
 *
3
 * jsonschema is licensed under MIT license.
4
 *
5
 * Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
8
 * this software and associated documentation files (the "Software"), to deal in
9
 * the Software without restriction, including without limitation the rights to
10
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
 * of the Software, and to permit persons to whom the Software is furnished to do
12
 * so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25

    
26
#IMPORT common/jsonschema/helpers.js
27

    
28
/** @type ValidatorResult */
29
var ValidatorResult = helpers.ValidatorResult;
30
/** @type SchemaError */
31
var SchemaError = helpers.SchemaError;
32

    
33
var attribute = {};
34

    
35
const ignoreProperties = {
36
  // informative properties
37
  'id': true,
38
  'default': true,
39
  'description': true,
40
  'title': true,
41
  // arguments to other properties
42
  'additionalItems': true,
43
  'then': true,
44
  'else': true,
45
  // special-handled properties
46
  '$schema': true,
47
  '$ref': true,
48
  'extends': true,
49
};
50
#EXPORT ignoreProperties
51

    
52
/**
53
 * @name validators
54
 */
55
const validators = {};
56

    
57
/**
58
 * Validates whether the instance if of a certain type
59
 * @param instance
60
 * @param schema
61
 * @param options
62
 * @param ctx
63
 * @return {ValidatorResult|null}
64
 */
65
validators.type = function validateType (instance, schema, options, ctx) {
66
  // Ignore undefined instances
67
  if (instance === undefined) {
68
    return null;
69
  }
70
  var result = new ValidatorResult(instance, schema, options, ctx);
71
  var types = Array.isArray(schema.type) ? schema.type : [schema.type];
72
  if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) {
73
    var list = types.map(function (v) {
74
      if(!v) return;
75
      var id = v.$id || v.id;
76
      return id ? ('<' + id + '>') : (v+'');
77
    });
78
    result.addError({
79
      name: 'type',
80
      argument: list,
81
      message: "is not of a type(s) " + list,
82
    });
83
  }
84
  return result;
85
};
86

    
87
function testSchemaNoThrow(instance, options, ctx, callback, schema){
88
  var throwError = options.throwError;
89
  var throwAll = options.throwAll;
90
  options.throwError = false;
91
  options.throwAll = false;
92
  var res = this.validateSchema(instance, schema, options, ctx);
93
  options.throwError = throwError;
94
  options.throwAll = throwAll;
95

    
96
  if (!res.valid && callback instanceof Function) {
97
    callback(res);
98
  }
99
  return res.valid;
100
}
101

    
102
/**
103
 * Validates whether the instance matches some of the given schemas
104
 * @param instance
105
 * @param schema
106
 * @param options
107
 * @param ctx
108
 * @return {ValidatorResult|null}
109
 */
110
validators.anyOf = function validateAnyOf (instance, schema, options, ctx) {
111
  // Ignore undefined instances
112
  if (instance === undefined) {
113
    return null;
114
  }
115
  var result = new ValidatorResult(instance, schema, options, ctx);
116
  var inner = new ValidatorResult(instance, schema, options, ctx);
117
  if (!Array.isArray(schema.anyOf)){
118
    throw new SchemaError("anyOf must be an array");
119
  }
120
  if (!schema.anyOf.some(
121
    testSchemaNoThrow.bind(
122
      this, instance, options, ctx, function(res){inner.importErrors(res);}
123
    ))) {
124
    var list = schema.anyOf.map(function (v, i) {
125
      var id = v.$id || v.id;
126
      if(id) return '<' + id + '>';
127
      return(v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
128
    });
129
    if (options.nestedErrors) {
130
      result.importErrors(inner);
131
    }
132
    result.addError({
133
      name: 'anyOf',
134
      argument: list,
135
      message: "is not any of " + list.join(','),
136
    });
137
  }
138
  return result;
139
};
140

    
141
/**
142
 * Validates whether the instance matches every given schema
143
 * @param instance
144
 * @param schema
145
 * @param options
146
 * @param ctx
147
 * @return {String|null}
148
 */
149
validators.allOf = function validateAllOf (instance, schema, options, ctx) {
150
  // Ignore undefined instances
151
  if (instance === undefined) {
152
    return null;
153
  }
154
  if (!Array.isArray(schema.allOf)){
155
    throw new SchemaError("allOf must be an array");
156
  }
157
  var result = new ValidatorResult(instance, schema, options, ctx);
158
  var self = this;
159
  schema.allOf.forEach(function(v, i){
160
    var valid = self.validateSchema(instance, v, options, ctx);
161
    if(!valid.valid){
162
      var id = v.$id || v.id;
163
      var msg = id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
164
      result.addError({
165
        name: 'allOf',
166
        argument: { id: msg, length: valid.errors.length, valid: valid },
167
        message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:',
168
      });
169
      result.importErrors(valid);
170
    }
171
  });
172
  return result;
173
};
174

    
175
/**
176
 * Validates whether the instance matches exactly one of the given schemas
177
 * @param instance
178
 * @param schema
179
 * @param options
180
 * @param ctx
181
 * @return {String|null}
182
 */
183
validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
184
  // Ignore undefined instances
185
  if (instance === undefined) {
186
    return null;
187
  }
188
  if (!Array.isArray(schema.oneOf)){
189
    throw new SchemaError("oneOf must be an array");
190
  }
191
  var result = new ValidatorResult(instance, schema, options, ctx);
192
  var inner = new ValidatorResult(instance, schema, options, ctx);
193
  var count = schema.oneOf.filter(
194
    testSchemaNoThrow.bind(
195
      this, instance, options, ctx, function(res) {inner.importErrors(res);}
196
    ) ).length;
197
  var list = schema.oneOf.map(function (v, i) {
198
    var id = v.$id || v.id;
199
    return id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
200
  });
201
  if (count!==1) {
202
    if (options.nestedErrors) {
203
      result.importErrors(inner);
204
    }
205
    result.addError({
206
      name: 'oneOf',
207
      argument: list,
208
      message: "is not exactly one from " + list.join(','),
209
    });
210
  }
211
  return result;
212
};
213

    
214
/**
215
 * Validates "then" or "else" depending on the result of validating "if"
216
 * @param instance
217
 * @param schema
218
 * @param options
219
 * @param ctx
220
 * @return {String|null}
221
 */
222
validators.if = function validateIf (instance, schema, options, ctx) {
223
  // Ignore undefined instances
224
  if (instance === undefined) return null;
225
  if (!helpers.isSchema(schema.if)) throw new Error('Expected "if" keyword to be a schema');
226
  var ifValid = testSchemaNoThrow.call(this, instance, options, ctx, null, schema.if);
227
  var result = new ValidatorResult(instance, schema, options, ctx);
228
  var res;
229
  if(ifValid){
230
    if (schema.then === undefined) return;
231
    if (!helpers.isSchema(schema.then)) throw new Error('Expected "then" keyword to be a schema');
232
    res = this.validateSchema(instance, schema.then, options, ctx.makeChild(schema.then));
233
    result.importErrors(res);
234
  }else{
235
    if (schema.else === undefined) return;
236
    if (!helpers.isSchema(schema.else)) throw new Error('Expected "else" keyword to be a schema');
237
    res = this.validateSchema(instance, schema.else, options, ctx.makeChild(schema.else));
238
    result.importErrors(res);
239
  }
240
  return result;
241
};
242

    
243
function getEnumerableProperty(object, key){
244
  // Determine if `key` shows up in `for(var key in object)`
245
  // First test Object.hasOwnProperty.call as an optimization: that guarantees it does
246
  if(Object.hasOwnProperty.call(object, key)) return object[key];
247
  // Test `key in object` as an optimization; false means it won't
248
  if(!(key in object)) return;
249
  while( (object = Object.getPrototypeOf(object)) ){
250
    if(Object.propertyIsEnumerable.call(object, key)) return object[key];
251
  }
252
}
253

    
254
/**
255
 * Validates propertyNames
256
 * @param instance
257
 * @param schema
258
 * @param options
259
 * @param ctx
260
 * @return {String|null|ValidatorResult}
261
 */
262
validators.propertyNames = function validatePropertyNames (instance, schema, options, ctx) {
263
  if(!this.types.object(instance)) return;
264
  var result = new ValidatorResult(instance, schema, options, ctx);
265
  var subschema = schema.propertyNames!==undefined ? schema.propertyNames : {};
266
  if(!helpers.isSchema(subschema)) throw new SchemaError('Expected "propertyNames" to be a schema (object or boolean)');
267

    
268
  for (var property in instance) {
269
    if(getEnumerableProperty(instance, property) !== undefined){
270
      var res = this.validateSchema(property, subschema, options, ctx.makeChild(subschema));
271
      result.importErrors(res);
272
    }
273
  }
274

    
275
  return result;
276
};
277

    
278
/**
279
 * Validates properties
280
 * @param instance
281
 * @param schema
282
 * @param options
283
 * @param ctx
284
 * @return {String|null|ValidatorResult}
285
 */
286
validators.properties = function validateProperties (instance, schema, options, ctx) {
287
  if(!this.types.object(instance)) return;
288
  var result = new ValidatorResult(instance, schema, options, ctx);
289
  var properties = schema.properties || {};
290
  for (var property in properties) {
291
    var subschema = properties[property];
292
    if(subschema===undefined){
293
      continue;
294
    }else if(subschema===null){
295
      throw new SchemaError('Unexpected null, expected schema in "properties"');
296
    }
297
    if (typeof options.preValidateProperty == 'function') {
298
      options.preValidateProperty(instance, property, subschema, options, ctx);
299
    }
300
    var prop = getEnumerableProperty(instance, property);
301
    var res = this.validateSchema(prop, subschema, options, ctx.makeChild(subschema, property));
302
    if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
303
    result.importErrors(res);
304
  }
305
  return result;
306
};
307

    
308
/**
309
 * Test a specific property within in instance against the additionalProperties schema attribute
310
 * This ignores properties with definitions in the properties schema attribute, but no other attributes.
311
 * If too many more types of property-existence tests pop up they may need their own class of tests (like `type` has)
312
 * @private
313
 * @return {boolean}
314
 */
315
function testAdditionalProperty (instance, schema, options, ctx, property, result) {
316
  if(!this.types.object(instance)) return;
317
  if (schema.properties && schema.properties[property] !== undefined) {
318
    return;
319
  }
320
  if (schema.additionalProperties === false) {
321
    result.addError({
322
      name: 'additionalProperties',
323
      argument: property,
324
      message: "is not allowed to have the additional property " + JSON.stringify(property),
325
    });
326
  } else {
327
    var additionalProperties = schema.additionalProperties || {};
328

    
329
    if (typeof options.preValidateProperty == 'function') {
330
      options.preValidateProperty(instance, property, additionalProperties, options, ctx);
331
    }
332

    
333
    var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property));
334
    if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
335
    result.importErrors(res);
336
  }
337
}
338

    
339
/**
340
 * Validates patternProperties
341
 * @param instance
342
 * @param schema
343
 * @param options
344
 * @param ctx
345
 * @return {String|null|ValidatorResult}
346
 */
347
validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) {
348
  if(!this.types.object(instance)) return;
349
  var result = new ValidatorResult(instance, schema, options, ctx);
350
  var patternProperties = schema.patternProperties || {};
351

    
352
  for (var property in instance) {
353
    var test = true;
354
    for (var pattern in patternProperties) {
355
      var subschema = patternProperties[pattern];
356
      if(subschema===undefined){
357
        continue;
358
      }else if(subschema===null){
359
        throw new SchemaError('Unexpected null, expected schema in "patternProperties"');
360
      }
361
      try {
362
        var regexp = new RegExp(pattern, 'u');
363
      } catch(_e) {
364
        // In the event the stricter handling causes an error, fall back on the forgiving handling
365
        // DEPRECATED
366
        regexp = new RegExp(pattern);
367
      }
368
      if (!regexp.test(property)) {
369
        continue;
370
      }
371
      test = false;
372

    
373
      if (typeof options.preValidateProperty == 'function') {
374
        options.preValidateProperty(instance, property, subschema, options, ctx);
375
      }
376

    
377
      var res = this.validateSchema(instance[property], subschema, options, ctx.makeChild(subschema, property));
378
      if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
379
      result.importErrors(res);
380
    }
381
    if (test) {
382
      testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
383
    }
384
  }
385

    
386
  return result;
387
};
388

    
389
/**
390
 * Validates additionalProperties
391
 * @param instance
392
 * @param schema
393
 * @param options
394
 * @param ctx
395
 * @return {String|null|ValidatorResult}
396
 */
397
validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) {
398
  if(!this.types.object(instance)) return;
399
  // if patternProperties is defined then we'll test when that one is called instead
400
  if (schema.patternProperties) {
401
    return null;
402
  }
403
  var result = new ValidatorResult(instance, schema, options, ctx);
404
  for (var property in instance) {
405
    testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
406
  }
407
  return result;
408
};
409

    
410
/**
411
 * Validates whether the instance value is at least of a certain length, when the instance value is a string.
412
 * @param instance
413
 * @param schema
414
 * @return {String|null}
415
 */
416
validators.minProperties = function validateMinProperties (instance, schema, options, ctx) {
417
  if (!this.types.object(instance)) return;
418
  var result = new ValidatorResult(instance, schema, options, ctx);
419
  var keys = Object.keys(instance);
420
  if (!(keys.length >= schema.minProperties)) {
421
    result.addError({
422
      name: 'minProperties',
423
      argument: schema.minProperties,
424
      message: "does not meet minimum property length of " + schema.minProperties,
425
    });
426
  }
427
  return result;
428
};
429

    
430
/**
431
 * Validates whether the instance value is at most of a certain length, when the instance value is a string.
432
 * @param instance
433
 * @param schema
434
 * @return {String|null}
435
 */
436
validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) {
437
  if (!this.types.object(instance)) return;
438
  var result = new ValidatorResult(instance, schema, options, ctx);
439
  var keys = Object.keys(instance);
440
  if (!(keys.length <= schema.maxProperties)) {
441
    result.addError({
442
      name: 'maxProperties',
443
      argument: schema.maxProperties,
444
      message: "does not meet maximum property length of " + schema.maxProperties,
445
    });
446
  }
447
  return result;
448
};
449

    
450
/**
451
 * Validates items when instance is an array
452
 * @param instance
453
 * @param schema
454
 * @param options
455
 * @param ctx
456
 * @return {String|null|ValidatorResult}
457
 */
458
validators.items = function validateItems (instance, schema, options, ctx) {
459
  var self = this;
460
  if (!this.types.array(instance)) return;
461
  if (schema.items===undefined) return;
462
  var result = new ValidatorResult(instance, schema, options, ctx);
463
  instance.every(function (value, i) {
464
    if(Array.isArray(schema.items)){
465
      var items =  schema.items[i]===undefined ? schema.additionalItems : schema.items[i];
466
    }else{
467
      var items = schema.items;
468
    }
469
    if (items === undefined) {
470
      return true;
471
    }
472
    if (items === false) {
473
      result.addError({
474
        name: 'items',
475
        message: "additionalItems not permitted",
476
      });
477
      return false;
478
    }
479
    var res = self.validateSchema(value, items, options, ctx.makeChild(items, i));
480
    if(res.instance !== result.instance[i]) result.instance[i] = res.instance;
481
    result.importErrors(res);
482
    return true;
483
  });
484
  return result;
485
};
486

    
487
/**
488
 * Validates the "contains" keyword
489
 * @param instance
490
 * @param schema
491
 * @param options
492
 * @param ctx
493
 * @return {String|null|ValidatorResult}
494
 */
495
validators.contains = function validateContains (instance, schema, options, ctx) {
496
  var self = this;
497
  if (!this.types.array(instance)) return;
498
  if (schema.contains===undefined) return;
499
  if (!helpers.isSchema(schema.contains)) throw new Error('Expected "contains" keyword to be a schema');
500
  var result = new ValidatorResult(instance, schema, options, ctx);
501
  var count = instance.some(function (value, i) {
502
    var res = self.validateSchema(value, schema.contains, options, ctx.makeChild(schema.contains, i));
503
    return res.errors.length===0;
504
  });
505
  if(count===false){
506
    result.addError({
507
      name: 'contains',
508
      argument: schema.contains,
509
      message: "must contain an item matching given schema",
510
    });
511
  }
512
  return result;
513
};
514

    
515
/**
516
 * Validates minimum and exclusiveMinimum when the type of the instance value is a number.
517
 * @param instance
518
 * @param schema
519
 * @return {String|null}
520
 */
521
validators.minimum = function validateMinimum (instance, schema, options, ctx) {
522
  if (!this.types.number(instance)) return;
523
  var result = new ValidatorResult(instance, schema, options, ctx);
524
  if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) {
525
    if(!(instance > schema.minimum)){
526
      result.addError({
527
        name: 'minimum',
528
        argument: schema.minimum,
529
        message: "must be greater than " + schema.minimum,
530
      });
531
    }
532
  } else {
533
    if(!(instance >= schema.minimum)){
534
      result.addError({
535
        name: 'minimum',
536
        argument: schema.minimum,
537
        message: "must be greater than or equal to " + schema.minimum,
538
      });
539
    }
540
  }
541
  return result;
542
};
543

    
544
/**
545
 * Validates maximum and exclusiveMaximum when the type of the instance value is a number.
546
 * @param instance
547
 * @param schema
548
 * @return {String|null}
549
 */
550
validators.maximum = function validateMaximum (instance, schema, options, ctx) {
551
  if (!this.types.number(instance)) return;
552
  var result = new ValidatorResult(instance, schema, options, ctx);
553
  if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) {
554
    if(!(instance < schema.maximum)){
555
      result.addError({
556
        name: 'maximum',
557
        argument: schema.maximum,
558
        message: "must be less than " + schema.maximum,
559
      });
560
    }
561
  } else {
562
    if(!(instance <= schema.maximum)){
563
      result.addError({
564
        name: 'maximum',
565
        argument: schema.maximum,
566
        message: "must be less than or equal to " + schema.maximum,
567
      });
568
    }
569
  }
570
  return result;
571
};
572

    
573
/**
574
 * Validates the number form of exclusiveMinimum when the type of the instance value is a number.
575
 * @param instance
576
 * @param schema
577
 * @return {String|null}
578
 */
579
validators.exclusiveMinimum = function validateExclusiveMinimum (instance, schema, options, ctx) {
580
  // Support the boolean form of exclusiveMinimum, which is handled by the "minimum" keyword.
581
  if(typeof schema.exclusiveMinimum === 'boolean') return;
582
  if (!this.types.number(instance)) return;
583
  var result = new ValidatorResult(instance, schema, options, ctx);
584
  var valid = instance > schema.exclusiveMinimum;
585
  if (!valid) {
586
    result.addError({
587
      name: 'exclusiveMinimum',
588
      argument: schema.exclusiveMinimum,
589
      message: "must be strictly greater than " + schema.exclusiveMinimum,
590
    });
591
  }
592
  return result;
593
};
594

    
595
/**
596
 * Validates the number form of exclusiveMaximum when the type of the instance value is a number.
597
 * @param instance
598
 * @param schema
599
 * @return {String|null}
600
 */
601
validators.exclusiveMaximum = function validateExclusiveMaximum (instance, schema, options, ctx) {
602
  // Support the boolean form of exclusiveMaximum, which is handled by the "maximum" keyword.
603
  if(typeof schema.exclusiveMaximum === 'boolean') return;
604
  if (!this.types.number(instance)) return;
605
  var result = new ValidatorResult(instance, schema, options, ctx);
606
  var valid = instance < schema.exclusiveMaximum;
607
  if (!valid) {
608
    result.addError({
609
      name: 'exclusiveMaximum',
610
      argument: schema.exclusiveMaximum,
611
      message: "must be strictly less than " + schema.exclusiveMaximum,
612
    });
613
  }
614
  return result;
615
};
616

    
617
/**
618
 * Perform validation for multipleOf and divisibleBy, which are essentially the same.
619
 * @param instance
620
 * @param schema
621
 * @param validationType
622
 * @param errorMessage
623
 * @returns {String|null}
624
 */
625
var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) {
626
  if (!this.types.number(instance)) return;
627

    
628
  var validationArgument = schema[validationType];
629
  if (validationArgument == 0) {
630
    throw new SchemaError(validationType + " cannot be zero");
631
  }
632

    
633
  var result = new ValidatorResult(instance, schema, options, ctx);
634

    
635
  var instanceDecimals = helpers.getDecimalPlaces(instance);
636
  var divisorDecimals = helpers.getDecimalPlaces(validationArgument);
637

    
638
  var maxDecimals = Math.max(instanceDecimals , divisorDecimals);
639
  var multiplier = Math.pow(10, maxDecimals);
640

    
641
  if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) {
642
    result.addError({
643
      name: validationType,
644
      argument:  validationArgument,
645
      message: errorMessage + JSON.stringify(validationArgument),
646
    });
647
  }
648

    
649
  return result;
650
};
651

    
652
/**
653
 * Validates divisibleBy when the type of the instance value is a number.
654
 * @param instance
655
 * @param schema
656
 * @return {String|null}
657
 */
658
validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) {
659
  return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) ");
660
};
661

    
662
/**
663
 * Validates multipleOf when the type of the instance value is a number.
664
 * @param instance
665
 * @param schema
666
 * @return {String|null}
667
 */
668
validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) {
669
  return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) ");
670
};
671

    
672
/**
673
 * Validates whether the instance value is present.
674
 * @param instance
675
 * @param schema
676
 * @return {String|null}
677
 */
678
validators.required = function validateRequired (instance, schema, options, ctx) {
679
  var result = new ValidatorResult(instance, schema, options, ctx);
680
  if (instance === undefined && schema.required === true) {
681
    // A boolean form is implemented for reverse-compatibility with schemas written against older drafts
682
    result.addError({
683
      name: 'required',
684
      message: "is required",
685
    });
686
  } else if (this.types.object(instance) && Array.isArray(schema.required)) {
687
    schema.required.forEach(function(n){
688
      if(getEnumerableProperty(instance, n)===undefined){
689
        result.addError({
690
          name: 'required',
691
          argument: n,
692
          message: "requires property " + JSON.stringify(n),
693
        });
694
      }
695
    });
696
  }
697
  return result;
698
};
699

    
700
/**
701
 * Validates whether the instance value matches the regular expression, when the instance value is a string.
702
 * @param instance
703
 * @param schema
704
 * @return {String|null}
705
 */
706
validators.pattern = function validatePattern (instance, schema, options, ctx) {
707
  if (!this.types.string(instance)) return;
708
  var result = new ValidatorResult(instance, schema, options, ctx);
709
  var pattern = schema.pattern;
710
  try {
711
    var regexp = new RegExp(pattern, 'u');
712
  } catch(_e) {
713
    // In the event the stricter handling causes an error, fall back on the forgiving handling
714
    // DEPRECATED
715
    regexp = new RegExp(pattern);
716
  }
717
  if (!instance.match(regexp)) {
718
    result.addError({
719
      name: 'pattern',
720
      argument: schema.pattern,
721
      message: "does not match pattern " + JSON.stringify(schema.pattern.toString()),
722
    });
723
  }
724
  return result;
725
};
726

    
727
/**
728
 * Validates whether the instance value is of a certain defined format or a custom
729
 * format.
730
 * The following formats are supported for string types:
731
 *   - date-time
732
 *   - date
733
 *   - time
734
 *   - ip-address
735
 *   - ipv6
736
 *   - uri
737
 *   - color
738
 *   - host-name
739
 *   - alpha
740
 *   - alpha-numeric
741
 *   - utc-millisec
742
 * @param instance
743
 * @param schema
744
 * @param [options]
745
 * @param [ctx]
746
 * @return {String|null}
747
 */
748
validators.format = function validateFormat (instance, schema, options, ctx) {
749
  if (instance===undefined) return;
750
  var result = new ValidatorResult(instance, schema, options, ctx);
751
  if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) {
752
    result.addError({
753
      name: 'format',
754
      argument: schema.format,
755
      message: "does not conform to the " + JSON.stringify(schema.format) + " format",
756
    });
757
  }
758
  return result;
759
};
760

    
761
/**
762
 * Validates whether the instance value is at least of a certain length, when the instance value is a string.
763
 * @param instance
764
 * @param schema
765
 * @return {String|null}
766
 */
767
validators.minLength = function validateMinLength (instance, schema, options, ctx) {
768
  if (!this.types.string(instance)) return;
769
  var result = new ValidatorResult(instance, schema, options, ctx);
770
  var hsp = instance.match(/[\uDC00-\uDFFF]/g);
771
  var length = instance.length - (hsp ? hsp.length : 0);
772
  if (!(length >= schema.minLength)) {
773
    result.addError({
774
      name: 'minLength',
775
      argument: schema.minLength,
776
      message: "does not meet minimum length of " + schema.minLength,
777
    });
778
  }
779
  return result;
780
};
781

    
782
/**
783
 * Validates whether the instance value is at most of a certain length, when the instance value is a string.
784
 * @param instance
785
 * @param schema
786
 * @return {String|null}
787
 */
788
validators.maxLength = function validateMaxLength (instance, schema, options, ctx) {
789
  if (!this.types.string(instance)) return;
790
  var result = new ValidatorResult(instance, schema, options, ctx);
791
  // TODO if this was already computed in "minLength", use that value instead of re-computing
792
  var hsp = instance.match(/[\uDC00-\uDFFF]/g);
793
  var length = instance.length - (hsp ? hsp.length : 0);
794
  if (!(length <= schema.maxLength)) {
795
    result.addError({
796
      name: 'maxLength',
797
      argument: schema.maxLength,
798
      message: "does not meet maximum length of " + schema.maxLength,
799
    });
800
  }
801
  return result;
802
};
803

    
804
/**
805
 * Validates whether instance contains at least a minimum number of items, when the instance is an Array.
806
 * @param instance
807
 * @param schema
808
 * @return {String|null}
809
 */
810
validators.minItems = function validateMinItems (instance, schema, options, ctx) {
811
  if (!this.types.array(instance)) return;
812
  var result = new ValidatorResult(instance, schema, options, ctx);
813
  if (!(instance.length >= schema.minItems)) {
814
    result.addError({
815
      name: 'minItems',
816
      argument: schema.minItems,
817
      message: "does not meet minimum length of " + schema.minItems,
818
    });
819
  }
820
  return result;
821
};
822

    
823
/**
824
 * Validates whether instance contains no more than a maximum number of items, when the instance is an Array.
825
 * @param instance
826
 * @param schema
827
 * @return {String|null}
828
 */
829
validators.maxItems = function validateMaxItems (instance, schema, options, ctx) {
830
  if (!this.types.array(instance)) return;
831
  var result = new ValidatorResult(instance, schema, options, ctx);
832
  if (!(instance.length <= schema.maxItems)) {
833
    result.addError({
834
      name: 'maxItems',
835
      argument: schema.maxItems,
836
      message: "does not meet maximum length of " + schema.maxItems,
837
    });
838
  }
839
  return result;
840
};
841

    
842
/**
843
 * Deep compares arrays for duplicates
844
 * @param v
845
 * @param i
846
 * @param a
847
 * @private
848
 * @return {boolean}
849
 */
850
function testArrays (v, i, a) {
851
  var j, len = a.length;
852
  for (j = i + 1, len; j < len; j++) {
853
    if (helpers.deepCompareStrict(v, a[j])) {
854
      return false;
855
    }
856
  }
857
  return true;
858
}
859

    
860
/**
861
 * Validates whether there are no duplicates, when the instance is an Array.
862
 * @param instance
863
 * @return {String|null}
864
 */
865
validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
866
  if (schema.uniqueItems!==true) return;
867
  if (!this.types.array(instance)) return;
868
  var result = new ValidatorResult(instance, schema, options, ctx);
869
  if (!instance.every(testArrays)) {
870
    result.addError({
871
      name: 'uniqueItems',
872
      message: "contains duplicate item",
873
    });
874
  }
875
  return result;
876
};
877

    
878
/**
879
 * Validate for the presence of dependency properties, if the instance is an object.
880
 * @param instance
881
 * @param schema
882
 * @param options
883
 * @param ctx
884
 * @return {null|ValidatorResult}
885
 */
886
validators.dependencies = function validateDependencies (instance, schema, options, ctx) {
887
  if (!this.types.object(instance)) return;
888
  var result = new ValidatorResult(instance, schema, options, ctx);
889
  for (var property in schema.dependencies) {
890
    if (instance[property] === undefined) {
891
      continue;
892
    }
893
    var dep = schema.dependencies[property];
894
    var childContext = ctx.makeChild(dep, property);
895
    if (typeof dep == 'string') {
896
      dep = [dep];
897
    }
898
    if (Array.isArray(dep)) {
899
      dep.forEach(function (prop) {
900
        if (instance[prop] === undefined) {
901
          result.addError({
902
            // FIXME there's two different "dependencies" errors here with slightly different outputs
903
            // Can we make these the same? Or should we create different error types?
904
            name: 'dependencies',
905
            argument: childContext.propertyPath,
906
            message: "property " + prop + " not found, required by " + childContext.propertyPath,
907
          });
908
        }
909
      });
910
    } else {
911
      var res = this.validateSchema(instance, dep, options, childContext);
912
      if(result.instance !== res.instance) result.instance = res.instance;
913
      if (res && res.errors.length) {
914
        result.addError({
915
          name: 'dependencies',
916
          argument: childContext.propertyPath,
917
          message: "does not meet dependency required by " + childContext.propertyPath,
918
        });
919
        result.importErrors(res);
920
      }
921
    }
922
  }
923
  return result;
924
};
925

    
926
/**
927
 * Validates whether the instance value is one of the enumerated values.
928
 *
929
 * @param instance
930
 * @param schema
931
 * @return {ValidatorResult|null}
932
 */
933
validators['enum'] = function validateEnum (instance, schema, options, ctx) {
934
  if (instance === undefined) {
935
    return null;
936
  }
937
  if (!Array.isArray(schema['enum'])) {
938
    throw new SchemaError("enum expects an array", schema);
939
  }
940
  var result = new ValidatorResult(instance, schema, options, ctx);
941
  if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) {
942
    result.addError({
943
      name: 'enum',
944
      argument: schema['enum'],
945
      message: "is not one of enum values: " + schema['enum'].map(String).join(','),
946
    });
947
  }
948
  return result;
949
};
950

    
951
/**
952
 * Validates whether the instance exactly matches a given value
953
 *
954
 * @param instance
955
 * @param schema
956
 * @return {ValidatorResult|null}
957
 */
958
validators['const'] = function validateEnum (instance, schema, options, ctx) {
959
  if (instance === undefined) {
960
    return null;
961
  }
962
  var result = new ValidatorResult(instance, schema, options, ctx);
963
  if (!helpers.deepCompareStrict(schema['const'], instance)) {
964
    result.addError({
965
      name: 'const',
966
      argument: schema['const'],
967
      message: "does not exactly match expected constant: " + schema['const'],
968
    });
969
  }
970
  return result;
971
};
972

    
973
/**
974
 * Validates whether the instance if of a prohibited type.
975
 * @param instance
976
 * @param schema
977
 * @param options
978
 * @param ctx
979
 * @return {null|ValidatorResult}
980
 */
981
validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) {
982
  var self = this;
983
  if(instance===undefined) return null;
984
  var result = new ValidatorResult(instance, schema, options, ctx);
985
  var notTypes = schema.not || schema.disallow;
986
  if(!notTypes) return null;
987
  if(!Array.isArray(notTypes)) notTypes=[notTypes];
988
  notTypes.forEach(function (type) {
989
    if (self.testType(instance, schema, options, ctx, type)) {
990
      var id = type && (type.$id || type.id);
991
      var schemaId = id || type;
992
      result.addError({
993
        name: 'not',
994
        argument: schemaId,
995
        message: "is of prohibited type " + schemaId,
996
      });
997
    }
998
  });
999
  return result;
1000
};
1001

    
1002
#EXPORT validators
(1-1/5)