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
|