attribute.js 30 KB

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