/ cloudformation-templates / node_modules / aws-cdk / node_modules / jsonschema / lib / attribute.js
attribute.js
1 'use strict'; 2 3 var helpers = require('./helpers'); 4 5 /** @type ValidatorResult */ 6 var ValidatorResult = helpers.ValidatorResult; 7 /** @type SchemaError */ 8 var SchemaError = helpers.SchemaError; 9 10 var attribute = {}; 11 12 attribute.ignoreProperties = { 13 // informative properties 14 'id': true, 15 'default': true, 16 'description': true, 17 'title': true, 18 // arguments to other properties 19 'additionalItems': true, 20 'then': true, 21 'else': true, 22 // special-handled properties 23 '$schema': true, 24 '$ref': true, 25 'extends': true, 26 }; 27 28 /** 29 * @name validators 30 */ 31 var validators = attribute.validators = {}; 32 33 /** 34 * Validates whether the instance if of a certain type 35 * @param instance 36 * @param schema 37 * @param options 38 * @param ctx 39 * @return {ValidatorResult|null} 40 */ 41 validators.type = function validateType (instance, schema, options, ctx) { 42 // Ignore undefined instances 43 if (instance === undefined) { 44 return null; 45 } 46 var result = new ValidatorResult(instance, schema, options, ctx); 47 var types = Array.isArray(schema.type) ? schema.type : [schema.type]; 48 if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) { 49 var list = types.map(function (v) { 50 if(!v) return; 51 var id = v.$id || v.id; 52 return id ? ('<' + id + '>') : (v+''); 53 }); 54 result.addError({ 55 name: 'type', 56 argument: list, 57 message: "is not of a type(s) " + list, 58 }); 59 } 60 return result; 61 }; 62 63 function testSchemaNoThrow(instance, options, ctx, callback, schema){ 64 var throwError = options.throwError; 65 var throwAll = options.throwAll; 66 options.throwError = false; 67 options.throwAll = false; 68 var res = this.validateSchema(instance, schema, options, ctx); 69 options.throwError = throwError; 70 options.throwAll = throwAll; 71 72 if (!res.valid && callback instanceof Function) { 73 callback(res); 74 } 75 return res.valid; 76 } 77 78 /** 79 * Validates whether the instance matches some of the given schemas 80 * @param instance 81 * @param schema 82 * @param options 83 * @param ctx 84 * @return {ValidatorResult|null} 85 */ 86 validators.anyOf = function validateAnyOf (instance, schema, options, ctx) { 87 // Ignore undefined instances 88 if (instance === undefined) { 89 return null; 90 } 91 var result = new ValidatorResult(instance, schema, options, ctx); 92 var inner = new ValidatorResult(instance, schema, options, ctx); 93 if (!Array.isArray(schema.anyOf)){ 94 throw new SchemaError("anyOf must be an array"); 95 } 96 if (!schema.anyOf.some( 97 testSchemaNoThrow.bind( 98 this, instance, options, ctx, function(res){inner.importErrors(res);} 99 ))) { 100 var list = schema.anyOf.map(function (v, i) { 101 var id = v.$id || v.id; 102 if(id) return '<' + id + '>'; 103 return(v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; 104 }); 105 if (options.nestedErrors) { 106 result.importErrors(inner); 107 } 108 result.addError({ 109 name: 'anyOf', 110 argument: list, 111 message: "is not any of " + list.join(','), 112 }); 113 } 114 return result; 115 }; 116 117 /** 118 * Validates whether the instance matches every given schema 119 * @param instance 120 * @param schema 121 * @param options 122 * @param ctx 123 * @return {String|null} 124 */ 125 validators.allOf = function validateAllOf (instance, schema, options, ctx) { 126 // Ignore undefined instances 127 if (instance === undefined) { 128 return null; 129 } 130 if (!Array.isArray(schema.allOf)){ 131 throw new SchemaError("allOf must be an array"); 132 } 133 var result = new ValidatorResult(instance, schema, options, ctx); 134 var self = this; 135 schema.allOf.forEach(function(v, i){ 136 var valid = self.validateSchema(instance, v, options, ctx); 137 if(!valid.valid){ 138 var id = v.$id || v.id; 139 var msg = id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; 140 result.addError({ 141 name: 'allOf', 142 argument: { id: msg, length: valid.errors.length, valid: valid }, 143 message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:', 144 }); 145 result.importErrors(valid); 146 } 147 }); 148 return result; 149 }; 150 151 /** 152 * Validates whether the instance matches exactly one of the given schemas 153 * @param instance 154 * @param schema 155 * @param options 156 * @param ctx 157 * @return {String|null} 158 */ 159 validators.oneOf = function validateOneOf (instance, schema, options, ctx) { 160 // Ignore undefined instances 161 if (instance === undefined) { 162 return null; 163 } 164 if (!Array.isArray(schema.oneOf)){ 165 throw new SchemaError("oneOf must be an array"); 166 } 167 var result = new ValidatorResult(instance, schema, options, ctx); 168 var inner = new ValidatorResult(instance, schema, options, ctx); 169 var count = schema.oneOf.filter( 170 testSchemaNoThrow.bind( 171 this, instance, options, ctx, function(res) {inner.importErrors(res);} 172 ) ).length; 173 var list = schema.oneOf.map(function (v, i) { 174 var id = v.$id || v.id; 175 return id || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']'; 176 }); 177 if (count!==1) { 178 if (options.nestedErrors) { 179 result.importErrors(inner); 180 } 181 result.addError({ 182 name: 'oneOf', 183 argument: list, 184 message: "is not exactly one from " + list.join(','), 185 }); 186 } 187 return result; 188 }; 189 190 /** 191 * Validates "then" or "else" depending on the result of validating "if" 192 * @param instance 193 * @param schema 194 * @param options 195 * @param ctx 196 * @return {String|null} 197 */ 198 validators.if = function validateIf (instance, schema, options, ctx) { 199 // Ignore undefined instances 200 if (instance === undefined) return null; 201 if (!helpers.isSchema(schema.if)) throw new Error('Expected "if" keyword to be a schema'); 202 var ifValid = testSchemaNoThrow.call(this, instance, options, ctx, null, schema.if); 203 var result = new ValidatorResult(instance, schema, options, ctx); 204 var res; 205 if(ifValid){ 206 if (schema.then === undefined) return; 207 if (!helpers.isSchema(schema.then)) throw new Error('Expected "then" keyword to be a schema'); 208 res = this.validateSchema(instance, schema.then, options, ctx.makeChild(schema.then)); 209 result.importErrors(res); 210 }else{ 211 if (schema.else === undefined) return; 212 if (!helpers.isSchema(schema.else)) throw new Error('Expected "else" keyword to be a schema'); 213 res = this.validateSchema(instance, schema.else, options, ctx.makeChild(schema.else)); 214 result.importErrors(res); 215 } 216 return result; 217 }; 218 219 function getEnumerableProperty(object, key){ 220 // Determine if `key` shows up in `for(var key in object)` 221 // First test Object.hasOwnProperty.call as an optimization: that guarantees it does 222 if(Object.hasOwnProperty.call(object, key)) return object[key]; 223 // Test `key in object` as an optimization; false means it won't 224 if(!(key in object)) return; 225 while( (object = Object.getPrototypeOf(object)) ){ 226 if(Object.propertyIsEnumerable.call(object, key)) return object[key]; 227 } 228 } 229 230 /** 231 * Validates propertyNames 232 * @param instance 233 * @param schema 234 * @param options 235 * @param ctx 236 * @return {String|null|ValidatorResult} 237 */ 238 validators.propertyNames = function validatePropertyNames (instance, schema, options, ctx) { 239 if(!this.types.object(instance)) return; 240 var result = new ValidatorResult(instance, schema, options, ctx); 241 var subschema = schema.propertyNames!==undefined ? schema.propertyNames : {}; 242 if(!helpers.isSchema(subschema)) throw new SchemaError('Expected "propertyNames" to be a schema (object or boolean)'); 243 244 for (var property in instance) { 245 if(getEnumerableProperty(instance, property) !== undefined){ 246 var res = this.validateSchema(property, subschema, options, ctx.makeChild(subschema)); 247 result.importErrors(res); 248 } 249 } 250 251 return result; 252 }; 253 254 /** 255 * Validates properties 256 * @param instance 257 * @param schema 258 * @param options 259 * @param ctx 260 * @return {String|null|ValidatorResult} 261 */ 262 validators.properties = function validateProperties (instance, schema, options, ctx) { 263 if(!this.types.object(instance)) return; 264 var result = new ValidatorResult(instance, schema, options, ctx); 265 var properties = schema.properties || {}; 266 for (var property in properties) { 267 var subschema = properties[property]; 268 if(subschema===undefined){ 269 continue; 270 }else if(subschema===null){ 271 throw new SchemaError('Unexpected null, expected schema in "properties"'); 272 } 273 if (typeof options.preValidateProperty == 'function') { 274 options.preValidateProperty(instance, property, subschema, options, ctx); 275 } 276 var prop = getEnumerableProperty(instance, property); 277 var res = this.validateSchema(prop, subschema, options, ctx.makeChild(subschema, property)); 278 if(res.instance !== result.instance[property]) result.instance[property] = res.instance; 279 result.importErrors(res); 280 } 281 return result; 282 }; 283 284 /** 285 * Test a specific property within in instance against the additionalProperties schema attribute 286 * This ignores properties with definitions in the properties schema attribute, but no other attributes. 287 * If too many more types of property-existence tests pop up they may need their own class of tests (like `type` has) 288 * @private 289 * @return {boolean} 290 */ 291 function testAdditionalProperty (instance, schema, options, ctx, property, result) { 292 if(!this.types.object(instance)) return; 293 if (schema.properties && schema.properties[property] !== undefined) { 294 return; 295 } 296 if (schema.additionalProperties === false) { 297 result.addError({ 298 name: 'additionalProperties', 299 argument: property, 300 message: "is not allowed to have the additional property " + JSON.stringify(property), 301 }); 302 } else { 303 var additionalProperties = schema.additionalProperties || {}; 304 305 if (typeof options.preValidateProperty == 'function') { 306 options.preValidateProperty(instance, property, additionalProperties, options, ctx); 307 } 308 309 var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property)); 310 if(res.instance !== result.instance[property]) result.instance[property] = res.instance; 311 result.importErrors(res); 312 } 313 } 314 315 /** 316 * Validates patternProperties 317 * @param instance 318 * @param schema 319 * @param options 320 * @param ctx 321 * @return {String|null|ValidatorResult} 322 */ 323 validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) { 324 if(!this.types.object(instance)) return; 325 var result = new ValidatorResult(instance, schema, options, ctx); 326 var patternProperties = schema.patternProperties || {}; 327 328 for (var property in instance) { 329 var test = true; 330 for (var pattern in patternProperties) { 331 var subschema = patternProperties[pattern]; 332 if(subschema===undefined){ 333 continue; 334 }else if(subschema===null){ 335 throw new SchemaError('Unexpected null, expected schema in "patternProperties"'); 336 } 337 try { 338 var regexp = new RegExp(pattern, 'u'); 339 } catch(_e) { 340 // In the event the stricter handling causes an error, fall back on the forgiving handling 341 // DEPRECATED 342 regexp = new RegExp(pattern); 343 } 344 if (!regexp.test(property)) { 345 continue; 346 } 347 test = false; 348 349 if (typeof options.preValidateProperty == 'function') { 350 options.preValidateProperty(instance, property, subschema, options, ctx); 351 } 352 353 var res = this.validateSchema(instance[property], subschema, options, ctx.makeChild(subschema, property)); 354 if(res.instance !== result.instance[property]) result.instance[property] = res.instance; 355 result.importErrors(res); 356 } 357 if (test) { 358 testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); 359 } 360 } 361 362 return result; 363 }; 364 365 /** 366 * Validates additionalProperties 367 * @param instance 368 * @param schema 369 * @param options 370 * @param ctx 371 * @return {String|null|ValidatorResult} 372 */ 373 validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) { 374 if(!this.types.object(instance)) return; 375 // if patternProperties is defined then we'll test when that one is called instead 376 if (schema.patternProperties) { 377 return null; 378 } 379 var result = new ValidatorResult(instance, schema, options, ctx); 380 for (var property in instance) { 381 testAdditionalProperty.call(this, instance, schema, options, ctx, property, result); 382 } 383 return result; 384 }; 385 386 /** 387 * Validates whether the instance value is at least of a certain length, when the instance value is a string. 388 * @param instance 389 * @param schema 390 * @return {String|null} 391 */ 392 validators.minProperties = function validateMinProperties (instance, schema, options, ctx) { 393 if (!this.types.object(instance)) return; 394 var result = new ValidatorResult(instance, schema, options, ctx); 395 var keys = Object.keys(instance); 396 if (!(keys.length >= schema.minProperties)) { 397 result.addError({ 398 name: 'minProperties', 399 argument: schema.minProperties, 400 message: "does not meet minimum property length of " + schema.minProperties, 401 }); 402 } 403 return result; 404 }; 405 406 /** 407 * Validates whether the instance value is at most of a certain length, when the instance value is a string. 408 * @param instance 409 * @param schema 410 * @return {String|null} 411 */ 412 validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) { 413 if (!this.types.object(instance)) return; 414 var result = new ValidatorResult(instance, schema, options, ctx); 415 var keys = Object.keys(instance); 416 if (!(keys.length <= schema.maxProperties)) { 417 result.addError({ 418 name: 'maxProperties', 419 argument: schema.maxProperties, 420 message: "does not meet maximum property length of " + schema.maxProperties, 421 }); 422 } 423 return result; 424 }; 425 426 /** 427 * Validates items when instance is an array 428 * @param instance 429 * @param schema 430 * @param options 431 * @param ctx 432 * @return {String|null|ValidatorResult} 433 */ 434 validators.items = function validateItems (instance, schema, options, ctx) { 435 var self = this; 436 if (!this.types.array(instance)) return; 437 if (!schema.items) return; 438 var result = new ValidatorResult(instance, schema, options, ctx); 439 instance.every(function (value, i) { 440 var items = Array.isArray(schema.items) ? (schema.items[i] || schema.additionalItems) : schema.items; 441 if (items === undefined) { 442 return true; 443 } 444 if (items === false) { 445 result.addError({ 446 name: 'items', 447 message: "additionalItems not permitted", 448 }); 449 return false; 450 } 451 var res = self.validateSchema(value, items, options, ctx.makeChild(items, i)); 452 if(res.instance !== result.instance[i]) result.instance[i] = res.instance; 453 result.importErrors(res); 454 return true; 455 }); 456 return result; 457 }; 458 459 /** 460 * Validates minimum and exclusiveMinimum when the type of the instance value is a number. 461 * @param instance 462 * @param schema 463 * @return {String|null} 464 */ 465 validators.minimum = function validateMinimum (instance, schema, options, ctx) { 466 if (!this.types.number(instance)) return; 467 var result = new ValidatorResult(instance, schema, options, ctx); 468 if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) { 469 if(!(instance > schema.minimum)){ 470 result.addError({ 471 name: 'minimum', 472 argument: schema.minimum, 473 message: "must be greater than " + schema.minimum, 474 }); 475 } 476 } else { 477 if(!(instance >= schema.minimum)){ 478 result.addError({ 479 name: 'minimum', 480 argument: schema.minimum, 481 message: "must be greater than or equal to " + schema.minimum, 482 }); 483 } 484 } 485 return result; 486 }; 487 488 /** 489 * Validates maximum and exclusiveMaximum when the type of the instance value is a number. 490 * @param instance 491 * @param schema 492 * @return {String|null} 493 */ 494 validators.maximum = function validateMaximum (instance, schema, options, ctx) { 495 if (!this.types.number(instance)) return; 496 var result = new ValidatorResult(instance, schema, options, ctx); 497 if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) { 498 if(!(instance < schema.maximum)){ 499 result.addError({ 500 name: 'maximum', 501 argument: schema.maximum, 502 message: "must be less than " + schema.maximum, 503 }); 504 } 505 } else { 506 if(!(instance <= schema.maximum)){ 507 result.addError({ 508 name: 'maximum', 509 argument: schema.maximum, 510 message: "must be less than or equal to " + schema.maximum, 511 }); 512 } 513 } 514 return result; 515 }; 516 517 /** 518 * Validates the number form of exclusiveMinimum when the type of the instance value is a number. 519 * @param instance 520 * @param schema 521 * @return {String|null} 522 */ 523 validators.exclusiveMinimum = function validateExclusiveMinimum (instance, schema, options, ctx) { 524 // Support the boolean form of exclusiveMinimum, which is handled by the "minimum" keyword. 525 if(typeof schema.exclusiveMaximum === 'boolean') return; 526 if (!this.types.number(instance)) return; 527 var result = new ValidatorResult(instance, schema, options, ctx); 528 var valid = instance > schema.exclusiveMinimum; 529 if (!valid) { 530 result.addError({ 531 name: 'exclusiveMinimum', 532 argument: schema.exclusiveMinimum, 533 message: "must be strictly greater than " + schema.exclusiveMinimum, 534 }); 535 } 536 return result; 537 }; 538 539 /** 540 * Validates the number form of exclusiveMaximum when the type of the instance value is a number. 541 * @param instance 542 * @param schema 543 * @return {String|null} 544 */ 545 validators.exclusiveMaximum = function validateExclusiveMaximum (instance, schema, options, ctx) { 546 // Support the boolean form of exclusiveMaximum, which is handled by the "maximum" keyword. 547 if(typeof schema.exclusiveMaximum === 'boolean') return; 548 if (!this.types.number(instance)) return; 549 var result = new ValidatorResult(instance, schema, options, ctx); 550 var valid = instance < schema.exclusiveMaximum; 551 if (!valid) { 552 result.addError({ 553 name: 'exclusiveMaximum', 554 argument: schema.exclusiveMaximum, 555 message: "must be strictly less than " + schema.exclusiveMaximum, 556 }); 557 } 558 return result; 559 }; 560 561 /** 562 * Perform validation for multipleOf and divisibleBy, which are essentially the same. 563 * @param instance 564 * @param schema 565 * @param validationType 566 * @param errorMessage 567 * @returns {String|null} 568 */ 569 var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) { 570 if (!this.types.number(instance)) return; 571 572 var validationArgument = schema[validationType]; 573 if (validationArgument == 0) { 574 throw new SchemaError(validationType + " cannot be zero"); 575 } 576 577 var result = new ValidatorResult(instance, schema, options, ctx); 578 579 var instanceDecimals = helpers.getDecimalPlaces(instance); 580 var divisorDecimals = helpers.getDecimalPlaces(validationArgument); 581 582 var maxDecimals = Math.max(instanceDecimals , divisorDecimals); 583 var multiplier = Math.pow(10, maxDecimals); 584 585 if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) { 586 result.addError({ 587 name: validationType, 588 argument: validationArgument, 589 message: errorMessage + JSON.stringify(validationArgument), 590 }); 591 } 592 593 return result; 594 }; 595 596 /** 597 * Validates divisibleBy when the type of the instance value is a number. 598 * @param instance 599 * @param schema 600 * @return {String|null} 601 */ 602 validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) { 603 return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) "); 604 }; 605 606 /** 607 * Validates multipleOf when the type of the instance value is a number. 608 * @param instance 609 * @param schema 610 * @return {String|null} 611 */ 612 validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) { 613 return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) "); 614 }; 615 616 /** 617 * Validates whether the instance value is present. 618 * @param instance 619 * @param schema 620 * @return {String|null} 621 */ 622 validators.required = function validateRequired (instance, schema, options, ctx) { 623 var result = new ValidatorResult(instance, schema, options, ctx); 624 if (instance === undefined && schema.required === true) { 625 // A boolean form is implemented for reverse-compatibility with schemas written against older drafts 626 result.addError({ 627 name: 'required', 628 message: "is required", 629 }); 630 } else if (this.types.object(instance) && Array.isArray(schema.required)) { 631 schema.required.forEach(function(n){ 632 if(getEnumerableProperty(instance, n)===undefined){ 633 result.addError({ 634 name: 'required', 635 argument: n, 636 message: "requires property " + JSON.stringify(n), 637 }); 638 } 639 }); 640 } 641 return result; 642 }; 643 644 /** 645 * Validates whether the instance value matches the regular expression, when the instance value is a string. 646 * @param instance 647 * @param schema 648 * @return {String|null} 649 */ 650 validators.pattern = function validatePattern (instance, schema, options, ctx) { 651 if (!this.types.string(instance)) return; 652 var result = new ValidatorResult(instance, schema, options, ctx); 653 var pattern = schema.pattern; 654 try { 655 var regexp = new RegExp(pattern, 'u'); 656 } catch(_e) { 657 // In the event the stricter handling causes an error, fall back on the forgiving handling 658 // DEPRECATED 659 regexp = new RegExp(pattern); 660 } 661 if (!instance.match(regexp)) { 662 result.addError({ 663 name: 'pattern', 664 argument: schema.pattern, 665 message: "does not match pattern " + JSON.stringify(schema.pattern.toString()), 666 }); 667 } 668 return result; 669 }; 670 671 /** 672 * Validates whether the instance value is of a certain defined format or a custom 673 * format. 674 * The following formats are supported for string types: 675 * - date-time 676 * - date 677 * - time 678 * - ip-address 679 * - ipv6 680 * - uri 681 * - color 682 * - host-name 683 * - alpha 684 * - alpha-numeric 685 * - utc-millisec 686 * @param instance 687 * @param schema 688 * @param [options] 689 * @param [ctx] 690 * @return {String|null} 691 */ 692 validators.format = function validateFormat (instance, schema, options, ctx) { 693 if (instance===undefined) return; 694 var result = new ValidatorResult(instance, schema, options, ctx); 695 if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) { 696 result.addError({ 697 name: 'format', 698 argument: schema.format, 699 message: "does not conform to the " + JSON.stringify(schema.format) + " format", 700 }); 701 } 702 return result; 703 }; 704 705 /** 706 * Validates whether the instance value is at least of a certain length, when the instance value is a string. 707 * @param instance 708 * @param schema 709 * @return {String|null} 710 */ 711 validators.minLength = function validateMinLength (instance, schema, options, ctx) { 712 if (!this.types.string(instance)) return; 713 var result = new ValidatorResult(instance, schema, options, ctx); 714 var hsp = instance.match(/[\uDC00-\uDFFF]/g); 715 var length = instance.length - (hsp ? hsp.length : 0); 716 if (!(length >= schema.minLength)) { 717 result.addError({ 718 name: 'minLength', 719 argument: schema.minLength, 720 message: "does not meet minimum length of " + schema.minLength, 721 }); 722 } 723 return result; 724 }; 725 726 /** 727 * Validates whether the instance value is at most of a certain length, when the instance value is a string. 728 * @param instance 729 * @param schema 730 * @return {String|null} 731 */ 732 validators.maxLength = function validateMaxLength (instance, schema, options, ctx) { 733 if (!this.types.string(instance)) return; 734 var result = new ValidatorResult(instance, schema, options, ctx); 735 // TODO if this was already computed in "minLength", use that value instead of re-computing 736 var hsp = instance.match(/[\uDC00-\uDFFF]/g); 737 var length = instance.length - (hsp ? hsp.length : 0); 738 if (!(length <= schema.maxLength)) { 739 result.addError({ 740 name: 'maxLength', 741 argument: schema.maxLength, 742 message: "does not meet maximum length of " + schema.maxLength, 743 }); 744 } 745 return result; 746 }; 747 748 /** 749 * Validates whether instance contains at least a minimum number of items, when the instance is an Array. 750 * @param instance 751 * @param schema 752 * @return {String|null} 753 */ 754 validators.minItems = function validateMinItems (instance, schema, options, ctx) { 755 if (!this.types.array(instance)) return; 756 var result = new ValidatorResult(instance, schema, options, ctx); 757 if (!(instance.length >= schema.minItems)) { 758 result.addError({ 759 name: 'minItems', 760 argument: schema.minItems, 761 message: "does not meet minimum length of " + schema.minItems, 762 }); 763 } 764 return result; 765 }; 766 767 /** 768 * Validates whether instance contains no more than a maximum number of items, when the instance is an Array. 769 * @param instance 770 * @param schema 771 * @return {String|null} 772 */ 773 validators.maxItems = function validateMaxItems (instance, schema, options, ctx) { 774 if (!this.types.array(instance)) return; 775 var result = new ValidatorResult(instance, schema, options, ctx); 776 if (!(instance.length <= schema.maxItems)) { 777 result.addError({ 778 name: 'maxItems', 779 argument: schema.maxItems, 780 message: "does not meet maximum length of " + schema.maxItems, 781 }); 782 } 783 return result; 784 }; 785 786 /** 787 * Deep compares arrays for duplicates 788 * @param v 789 * @param i 790 * @param a 791 * @private 792 * @return {boolean} 793 */ 794 function testArrays (v, i, a) { 795 var j, len = a.length; 796 for (j = i + 1, len; j < len; j++) { 797 if (helpers.deepCompareStrict(v, a[j])) { 798 return false; 799 } 800 } 801 return true; 802 } 803 804 /** 805 * Validates whether there are no duplicates, when the instance is an Array. 806 * @param instance 807 * @return {String|null} 808 */ 809 validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) { 810 if (schema.uniqueItems!==true) return; 811 if (!this.types.array(instance)) return; 812 var result = new ValidatorResult(instance, schema, options, ctx); 813 if (!instance.every(testArrays)) { 814 result.addError({ 815 name: 'uniqueItems', 816 message: "contains duplicate item", 817 }); 818 } 819 return result; 820 }; 821 822 /** 823 * Validate for the presence of dependency properties, if the instance is an object. 824 * @param instance 825 * @param schema 826 * @param options 827 * @param ctx 828 * @return {null|ValidatorResult} 829 */ 830 validators.dependencies = function validateDependencies (instance, schema, options, ctx) { 831 if (!this.types.object(instance)) return; 832 var result = new ValidatorResult(instance, schema, options, ctx); 833 for (var property in schema.dependencies) { 834 if (instance[property] === undefined) { 835 continue; 836 } 837 var dep = schema.dependencies[property]; 838 var childContext = ctx.makeChild(dep, property); 839 if (typeof dep == 'string') { 840 dep = [dep]; 841 } 842 if (Array.isArray(dep)) { 843 dep.forEach(function (prop) { 844 if (instance[prop] === undefined) { 845 result.addError({ 846 // FIXME there's two different "dependencies" errors here with slightly different outputs 847 // Can we make these the same? Or should we create different error types? 848 name: 'dependencies', 849 argument: childContext.propertyPath, 850 message: "property " + prop + " not found, required by " + childContext.propertyPath, 851 }); 852 } 853 }); 854 } else { 855 var res = this.validateSchema(instance, dep, options, childContext); 856 if(result.instance !== res.instance) result.instance = res.instance; 857 if (res && res.errors.length) { 858 result.addError({ 859 name: 'dependencies', 860 argument: childContext.propertyPath, 861 message: "does not meet dependency required by " + childContext.propertyPath, 862 }); 863 result.importErrors(res); 864 } 865 } 866 } 867 return result; 868 }; 869 870 /** 871 * Validates whether the instance value is one of the enumerated values. 872 * 873 * @param instance 874 * @param schema 875 * @return {ValidatorResult|null} 876 */ 877 validators['enum'] = function validateEnum (instance, schema, options, ctx) { 878 if (instance === undefined) { 879 return null; 880 } 881 if (!Array.isArray(schema['enum'])) { 882 throw new SchemaError("enum expects an array", schema); 883 } 884 var result = new ValidatorResult(instance, schema, options, ctx); 885 if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) { 886 result.addError({ 887 name: 'enum', 888 argument: schema['enum'], 889 message: "is not one of enum values: " + schema['enum'].map(String).join(','), 890 }); 891 } 892 return result; 893 }; 894 895 /** 896 * Validates whether the instance exactly matches a given value 897 * 898 * @param instance 899 * @param schema 900 * @return {ValidatorResult|null} 901 */ 902 validators['const'] = function validateEnum (instance, schema, options, ctx) { 903 if (instance === undefined) { 904 return null; 905 } 906 var result = new ValidatorResult(instance, schema, options, ctx); 907 if (!helpers.deepCompareStrict(schema['const'], instance)) { 908 result.addError({ 909 name: 'const', 910 argument: schema['const'], 911 message: "does not exactly match expected constant: " + schema['const'], 912 }); 913 } 914 return result; 915 }; 916 917 /** 918 * Validates whether the instance if of a prohibited type. 919 * @param instance 920 * @param schema 921 * @param options 922 * @param ctx 923 * @return {null|ValidatorResult} 924 */ 925 validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) { 926 var self = this; 927 if(instance===undefined) return null; 928 var result = new ValidatorResult(instance, schema, options, ctx); 929 var notTypes = schema.not || schema.disallow; 930 if(!notTypes) return null; 931 if(!Array.isArray(notTypes)) notTypes=[notTypes]; 932 notTypes.forEach(function (type) { 933 if (self.testType(instance, schema, options, ctx, type)) { 934 var id = type && (type.$id || type.id); 935 var schemaId = id || type; 936 result.addError({ 937 name: 'not', 938 argument: schemaId, 939 message: "is of prohibited type " + schemaId, 940 }); 941 } 942 }); 943 return result; 944 }; 945 946 module.exports = attribute;