選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

attribute.js 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  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. 'exclusiveMinimum': true,
  16. 'exclusiveMaximum': true,
  17. 'additionalItems': 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. return v.id && ('<' + v.id + '>') || (v+'');
  45. });
  46. result.addError({
  47. name: 'type',
  48. argument: list,
  49. message: "is not of a type(s) " + list,
  50. });
  51. }
  52. return result;
  53. };
  54. function testSchema(instance, options, ctx, callback, schema){
  55. var res = this.validateSchema(instance, schema, options, ctx);
  56. if (! res.valid && callback instanceof Function) {
  57. callback(res);
  58. }
  59. return res.valid;
  60. }
  61. /**
  62. * Validates whether the instance matches some of the given schemas
  63. * @param instance
  64. * @param schema
  65. * @param options
  66. * @param ctx
  67. * @return {ValidatorResult|null}
  68. */
  69. validators.anyOf = function validateAnyOf (instance, schema, options, ctx) {
  70. // Ignore undefined instances
  71. if (instance === undefined) {
  72. return null;
  73. }
  74. var result = new ValidatorResult(instance, schema, options, ctx);
  75. var inner = new ValidatorResult(instance, schema, options, ctx);
  76. if (!Array.isArray(schema.anyOf)){
  77. throw new SchemaError("anyOf must be an array");
  78. }
  79. if (!schema.anyOf.some(
  80. testSchema.bind(
  81. this, instance, options, ctx, function(res){inner.importErrors(res);}
  82. ))) {
  83. var list = schema.anyOf.map(function (v, i) {
  84. return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
  85. });
  86. if (options.nestedErrors) {
  87. result.importErrors(inner);
  88. }
  89. result.addError({
  90. name: 'anyOf',
  91. argument: list,
  92. message: "is not any of " + list.join(','),
  93. });
  94. }
  95. return result;
  96. };
  97. /**
  98. * Validates whether the instance matches every given schema
  99. * @param instance
  100. * @param schema
  101. * @param options
  102. * @param ctx
  103. * @return {String|null}
  104. */
  105. validators.allOf = function validateAllOf (instance, schema, options, ctx) {
  106. // Ignore undefined instances
  107. if (instance === undefined) {
  108. return null;
  109. }
  110. if (!Array.isArray(schema.allOf)){
  111. throw new SchemaError("allOf must be an array");
  112. }
  113. var result = new ValidatorResult(instance, schema, options, ctx);
  114. var self = this;
  115. schema.allOf.forEach(function(v, i){
  116. var valid = self.validateSchema(instance, v, options, ctx);
  117. if(!valid.valid){
  118. var msg = (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
  119. result.addError({
  120. name: 'allOf',
  121. argument: { id: msg, length: valid.errors.length, valid: valid },
  122. message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:',
  123. });
  124. result.importErrors(valid);
  125. }
  126. });
  127. return result;
  128. };
  129. /**
  130. * Validates whether the instance matches exactly one of the given schemas
  131. * @param instance
  132. * @param schema
  133. * @param options
  134. * @param ctx
  135. * @return {String|null}
  136. */
  137. validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
  138. // Ignore undefined instances
  139. if (instance === undefined) {
  140. return null;
  141. }
  142. if (!Array.isArray(schema.oneOf)){
  143. throw new SchemaError("oneOf must be an array");
  144. }
  145. var result = new ValidatorResult(instance, schema, options, ctx);
  146. var inner = new ValidatorResult(instance, schema, options, ctx);
  147. var count = schema.oneOf.filter(
  148. testSchema.bind(
  149. this, instance, options, ctx, function(res) {inner.importErrors(res);}
  150. ) ).length;
  151. var list = schema.oneOf.map(function (v, i) {
  152. return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
  153. });
  154. if (count!==1) {
  155. if (options.nestedErrors) {
  156. result.importErrors(inner);
  157. }
  158. result.addError({
  159. name: 'oneOf',
  160. argument: list,
  161. message: "is not exactly one from " + list.join(','),
  162. });
  163. }
  164. return result;
  165. };
  166. /**
  167. * Validates properties
  168. * @param instance
  169. * @param schema
  170. * @param options
  171. * @param ctx
  172. * @return {String|null|ValidatorResult}
  173. */
  174. validators.properties = function validateProperties (instance, schema, options, ctx) {
  175. if(instance === undefined || !(instance instanceof Object)) return;
  176. var result = new ValidatorResult(instance, schema, options, ctx);
  177. var properties = schema.properties || {};
  178. for (var property in properties) {
  179. if (typeof options.preValidateProperty == 'function') {
  180. options.preValidateProperty(instance, property, properties[property], options, ctx);
  181. }
  182. var prop = (instance || undefined) && instance[property];
  183. var res = this.validateSchema(prop, properties[property], options, ctx.makeChild(properties[property], property));
  184. if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
  185. result.importErrors(res);
  186. }
  187. return result;
  188. };
  189. /**
  190. * Test a specific property within in instance against the additionalProperties schema attribute
  191. * This ignores properties with definitions in the properties schema attribute, but no other attributes.
  192. * If too many more types of property-existance tests pop up they may need their own class of tests (like `type` has)
  193. * @private
  194. * @return {boolean}
  195. */
  196. function testAdditionalProperty (instance, schema, options, ctx, property, result) {
  197. if (schema.properties && schema.properties[property] !== undefined) {
  198. return;
  199. }
  200. if (schema.additionalProperties === false) {
  201. result.addError({
  202. name: 'additionalProperties',
  203. argument: property,
  204. message: "additionalProperty " + JSON.stringify(property) + " exists in instance when not allowed",
  205. });
  206. } else {
  207. var additionalProperties = schema.additionalProperties || {};
  208. if (typeof options.preValidateProperty == 'function') {
  209. options.preValidateProperty(instance, property, additionalProperties, options, ctx);
  210. }
  211. var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property));
  212. if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
  213. result.importErrors(res);
  214. }
  215. }
  216. /**
  217. * Validates patternProperties
  218. * @param instance
  219. * @param schema
  220. * @param options
  221. * @param ctx
  222. * @return {String|null|ValidatorResult}
  223. */
  224. validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) {
  225. if(instance === undefined) return;
  226. if(!this.types.object(instance)) return;
  227. var result = new ValidatorResult(instance, schema, options, ctx);
  228. var patternProperties = schema.patternProperties || {};
  229. for (var property in instance) {
  230. var test = true;
  231. for (var pattern in patternProperties) {
  232. var expr = new RegExp(pattern);
  233. if (!expr.test(property)) {
  234. continue;
  235. }
  236. test = false;
  237. if (typeof options.preValidateProperty == 'function') {
  238. options.preValidateProperty(instance, property, patternProperties[pattern], options, ctx);
  239. }
  240. var res = this.validateSchema(instance[property], patternProperties[pattern], options, ctx.makeChild(patternProperties[pattern], property));
  241. if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
  242. result.importErrors(res);
  243. }
  244. if (test) {
  245. testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
  246. }
  247. }
  248. return result;
  249. };
  250. /**
  251. * Validates additionalProperties
  252. * @param instance
  253. * @param schema
  254. * @param options
  255. * @param ctx
  256. * @return {String|null|ValidatorResult}
  257. */
  258. validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) {
  259. if(instance === undefined) return;
  260. if(!this.types.object(instance)) return;
  261. // if patternProperties is defined then we'll test when that one is called instead
  262. if (schema.patternProperties) {
  263. return null;
  264. }
  265. var result = new ValidatorResult(instance, schema, options, ctx);
  266. for (var property in instance) {
  267. testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
  268. }
  269. return result;
  270. };
  271. /**
  272. * Validates whether the instance value is at least of a certain length, when the instance value is a string.
  273. * @param instance
  274. * @param schema
  275. * @return {String|null}
  276. */
  277. validators.minProperties = function validateMinProperties (instance, schema, options, ctx) {
  278. if (!instance || typeof instance !== 'object') {
  279. return null;
  280. }
  281. var result = new ValidatorResult(instance, schema, options, ctx);
  282. var keys = Object.keys(instance);
  283. if (!(keys.length >= schema.minProperties)) {
  284. result.addError({
  285. name: 'minProperties',
  286. argument: schema.minProperties,
  287. message: "does not meet minimum property length of " + schema.minProperties,
  288. })
  289. }
  290. return result;
  291. };
  292. /**
  293. * Validates whether the instance value is at most of a certain length, when the instance value is a string.
  294. * @param instance
  295. * @param schema
  296. * @return {String|null}
  297. */
  298. validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) {
  299. if (!instance || typeof instance !== 'object') {
  300. return null;
  301. }
  302. var result = new ValidatorResult(instance, schema, options, ctx);
  303. var keys = Object.keys(instance);
  304. if (!(keys.length <= schema.maxProperties)) {
  305. result.addError({
  306. name: 'maxProperties',
  307. argument: schema.maxProperties,
  308. message: "does not meet maximum property length of " + schema.maxProperties,
  309. });
  310. }
  311. return result;
  312. };
  313. /**
  314. * Validates items when instance is an array
  315. * @param instance
  316. * @param schema
  317. * @param options
  318. * @param ctx
  319. * @return {String|null|ValidatorResult}
  320. */
  321. validators.items = function validateItems (instance, schema, options, ctx) {
  322. if (!Array.isArray(instance)) {
  323. return null;
  324. }
  325. var self = this;
  326. var result = new ValidatorResult(instance, schema, options, ctx);
  327. if (instance === undefined || !schema.items) {
  328. return result;
  329. }
  330. instance.every(function (value, i) {
  331. var items = Array.isArray(schema.items) ? (schema.items[i] || schema.additionalItems) : schema.items;
  332. if (items === undefined) {
  333. return true;
  334. }
  335. if (items === false) {
  336. result.addError({
  337. name: 'items',
  338. message: "additionalItems not permitted",
  339. });
  340. return false;
  341. }
  342. var res = self.validateSchema(value, items, options, ctx.makeChild(items, i));
  343. if(res.instance !== result.instance[i]) result.instance[i] = res.instance;
  344. result.importErrors(res);
  345. return true;
  346. });
  347. return result;
  348. };
  349. /**
  350. * Validates minimum and exclusiveMinimum when the type of the instance value is a number.
  351. * @param instance
  352. * @param schema
  353. * @return {String|null}
  354. */
  355. validators.minimum = function validateMinimum (instance, schema, options, ctx) {
  356. if (typeof instance !== 'number') {
  357. return null;
  358. }
  359. var result = new ValidatorResult(instance, schema, options, ctx);
  360. var valid = true;
  361. if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) {
  362. valid = instance > schema.minimum;
  363. } else {
  364. valid = instance >= schema.minimum;
  365. }
  366. if (!valid) {
  367. result.addError({
  368. name: 'minimum',
  369. argument: schema.minimum,
  370. message: "must have a minimum value of " + schema.minimum,
  371. });
  372. }
  373. return result;
  374. };
  375. /**
  376. * Validates maximum and exclusiveMaximum when the type of the instance value is a number.
  377. * @param instance
  378. * @param schema
  379. * @return {String|null}
  380. */
  381. validators.maximum = function validateMaximum (instance, schema, options, ctx) {
  382. if (typeof instance !== 'number') {
  383. return null;
  384. }
  385. var result = new ValidatorResult(instance, schema, options, ctx);
  386. var valid;
  387. if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) {
  388. valid = instance < schema.maximum;
  389. } else {
  390. valid = instance <= schema.maximum;
  391. }
  392. if (!valid) {
  393. result.addError({
  394. name: 'maximum',
  395. argument: schema.maximum,
  396. message: "must have a maximum value of " + schema.maximum,
  397. });
  398. }
  399. return result;
  400. };
  401. /**
  402. * Perform validation for multipleOf and divisibleBy, which are essentially the same.
  403. * @param instance
  404. * @param schema
  405. * @param validationType
  406. * @param errorMessage
  407. * @returns {String|null}
  408. */
  409. var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) {
  410. if (typeof instance !== 'number') {
  411. return null;
  412. }
  413. var validationArgument = schema[validationType];
  414. if (validationArgument == 0) {
  415. throw new SchemaError(validationType + " cannot be zero");
  416. }
  417. var result = new ValidatorResult(instance, schema, options, ctx);
  418. var instanceDecimals = helpers.getDecimalPlaces(instance);
  419. var divisorDecimals = helpers.getDecimalPlaces(validationArgument);
  420. var maxDecimals = Math.max(instanceDecimals , divisorDecimals);
  421. var multiplier = Math.pow(10, maxDecimals);
  422. if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) {
  423. result.addError({
  424. name: validationType,
  425. argument: validationArgument,
  426. message: errorMessage + JSON.stringify(validationArgument)
  427. });
  428. }
  429. return result;
  430. };
  431. /**
  432. * Validates divisibleBy when the type of the instance value is a number.
  433. * @param instance
  434. * @param schema
  435. * @return {String|null}
  436. */
  437. validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) {
  438. return validateMultipleOfOrDivisbleBy(instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) ");
  439. };
  440. /**
  441. * Validates multipleOf when the type of the instance value is a number.
  442. * @param instance
  443. * @param schema
  444. * @return {String|null}
  445. */
  446. validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) {
  447. return validateMultipleOfOrDivisbleBy(instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) ");
  448. };
  449. /**
  450. * Validates whether the instance value is present.
  451. * @param instance
  452. * @param schema
  453. * @return {String|null}
  454. */
  455. validators.required = function validateRequired (instance, schema, options, ctx) {
  456. var result = new ValidatorResult(instance, schema, options, ctx);
  457. if (instance === undefined && schema.required === true) {
  458. // A boolean form is implemented for reverse-compatability with schemas written against older drafts
  459. result.addError({
  460. name: 'required',
  461. message: "is required"
  462. });
  463. } else if (instance && typeof instance==='object' && Array.isArray(schema.required)) {
  464. schema.required.forEach(function(n){
  465. if(instance[n]===undefined){
  466. result.addError({
  467. name: 'required',
  468. argument: n,
  469. message: "requires property " + JSON.stringify(n),
  470. });
  471. }
  472. });
  473. }
  474. return result;
  475. };
  476. /**
  477. * Validates whether the instance value matches the regular expression, when the instance value is a string.
  478. * @param instance
  479. * @param schema
  480. * @return {String|null}
  481. */
  482. validators.pattern = function validatePattern (instance, schema, options, ctx) {
  483. if (typeof instance !== 'string') {
  484. return null;
  485. }
  486. var result = new ValidatorResult(instance, schema, options, ctx);
  487. if (!instance.match(schema.pattern)) {
  488. result.addError({
  489. name: 'pattern',
  490. argument: schema.pattern,
  491. message: "does not match pattern " + JSON.stringify(schema.pattern),
  492. });
  493. }
  494. return result;
  495. };
  496. /**
  497. * Validates whether the instance value is of a certain defined format or a custom
  498. * format.
  499. * The following formats are supported for string types:
  500. * - date-time
  501. * - date
  502. * - time
  503. * - ip-address
  504. * - ipv6
  505. * - uri
  506. * - color
  507. * - host-name
  508. * - alpha
  509. * - alpha-numeric
  510. * - utc-millisec
  511. * @param instance
  512. * @param schema
  513. * @param [options]
  514. * @param [ctx]
  515. * @return {String|null}
  516. */
  517. validators.format = function validateFormat (instance, schema, options, ctx) {
  518. var result = new ValidatorResult(instance, schema, options, ctx);
  519. if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) {
  520. result.addError({
  521. name: 'format',
  522. argument: schema.format,
  523. message: "does not conform to the " + JSON.stringify(schema.format) + " format",
  524. });
  525. }
  526. return result;
  527. };
  528. /**
  529. * Validates whether the instance value is at least of a certain length, when the instance value is a string.
  530. * @param instance
  531. * @param schema
  532. * @return {String|null}
  533. */
  534. validators.minLength = function validateMinLength (instance, schema, options, ctx) {
  535. if (!(typeof instance === 'string')) {
  536. return null;
  537. }
  538. var result = new ValidatorResult(instance, schema, options, ctx);
  539. if (!(instance.length >= schema.minLength)) {
  540. result.addError({
  541. name: 'minLength',
  542. argument: schema.minLength,
  543. message: "does not meet minimum length of " + schema.minLength,
  544. });
  545. }
  546. return result;
  547. };
  548. /**
  549. * Validates whether the instance value is at most of a certain length, when the instance value is a string.
  550. * @param instance
  551. * @param schema
  552. * @return {String|null}
  553. */
  554. validators.maxLength = function validateMaxLength (instance, schema, options, ctx) {
  555. if (!(typeof instance === 'string')) {
  556. return null;
  557. }
  558. var result = new ValidatorResult(instance, schema, options, ctx);
  559. if (!(instance.length <= schema.maxLength)) {
  560. result.addError({
  561. name: 'maxLength',
  562. argument: schema.maxLength,
  563. message: "does not meet maximum length of " + schema.maxLength,
  564. });
  565. }
  566. return result;
  567. };
  568. /**
  569. * Validates whether instance contains at least a minimum number of items, when the instance is an Array.
  570. * @param instance
  571. * @param schema
  572. * @return {String|null}
  573. */
  574. validators.minItems = function validateMinItems (instance, schema, options, ctx) {
  575. if (!Array.isArray(instance)) {
  576. return null;
  577. }
  578. var result = new ValidatorResult(instance, schema, options, ctx);
  579. if (!(instance.length >= schema.minItems)) {
  580. result.addError({
  581. name: 'minItems',
  582. argument: schema.minItems,
  583. message: "does not meet minimum length of " + schema.minItems,
  584. });
  585. }
  586. return result;
  587. };
  588. /**
  589. * Validates whether instance contains no more than a maximum number of items, when the instance is an Array.
  590. * @param instance
  591. * @param schema
  592. * @return {String|null}
  593. */
  594. validators.maxItems = function validateMaxItems (instance, schema, options, ctx) {
  595. if (!Array.isArray(instance)) {
  596. return null;
  597. }
  598. var result = new ValidatorResult(instance, schema, options, ctx);
  599. if (!(instance.length <= schema.maxItems)) {
  600. result.addError({
  601. name: 'maxItems',
  602. argument: schema.maxItems,
  603. message: "does not meet maximum length of " + schema.maxItems,
  604. });
  605. }
  606. return result;
  607. };
  608. /**
  609. * Validates that every item in an instance array is unique, when instance is an array
  610. * @param instance
  611. * @param schema
  612. * @param options
  613. * @param ctx
  614. * @return {String|null|ValidatorResult}
  615. */
  616. validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
  617. var result = new ValidatorResult(instance, schema, options, ctx);
  618. if (!Array.isArray(instance)) {
  619. return result;
  620. }
  621. function testArrays (v, i, a) {
  622. for (var j = i + 1; j < a.length; j++) if (helpers.deepCompareStrict(v, a[j])) {
  623. return false;
  624. }
  625. return true;
  626. }
  627. if (!instance.every(testArrays)) {
  628. result.addError({
  629. name: 'uniqueItems',
  630. message: "contains duplicate item",
  631. });
  632. }
  633. return result;
  634. };
  635. /**
  636. * Deep compares arrays for duplicates
  637. * @param v
  638. * @param i
  639. * @param a
  640. * @private
  641. * @return {boolean}
  642. */
  643. function testArrays (v, i, a) {
  644. var j, len = a.length;
  645. for (j = i + 1, len; j < len; j++) {
  646. if (helpers.deepCompareStrict(v, a[j])) {
  647. return false;
  648. }
  649. }
  650. return true;
  651. }
  652. /**
  653. * Validates whether there are no duplicates, when the instance is an Array.
  654. * @param instance
  655. * @return {String|null}
  656. */
  657. validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
  658. if (!Array.isArray(instance)) {
  659. return null;
  660. }
  661. var result = new ValidatorResult(instance, schema, options, ctx);
  662. if (!instance.every(testArrays)) {
  663. result.addError({
  664. name: 'uniqueItems',
  665. message: "contains duplicate item",
  666. });
  667. }
  668. return result;
  669. };
  670. /**
  671. * Validate for the presence of dependency properties, if the instance is an object.
  672. * @param instance
  673. * @param schema
  674. * @param options
  675. * @param ctx
  676. * @return {null|ValidatorResult}
  677. */
  678. validators.dependencies = function validateDependencies (instance, schema, options, ctx) {
  679. if (!instance || typeof instance != 'object') {
  680. return null;
  681. }
  682. var result = new ValidatorResult(instance, schema, options, ctx);
  683. for (var property in schema.dependencies) {
  684. if (instance[property] === undefined) {
  685. continue;
  686. }
  687. var dep = schema.dependencies[property];
  688. var childContext = ctx.makeChild(dep, property);
  689. if (typeof dep == 'string') {
  690. dep = [dep];
  691. }
  692. if (Array.isArray(dep)) {
  693. dep.forEach(function (prop) {
  694. if (instance[prop] === undefined) {
  695. result.addError({
  696. // FIXME there's two different "dependencies" errors here with slightly different outputs
  697. // Can we make these the same? Or should we create different error types?
  698. name: 'dependencies',
  699. argument: childContext.propertyPath,
  700. message: "property " + prop + " not found, required by " + childContext.propertyPath,
  701. });
  702. }
  703. });
  704. } else {
  705. var res = this.validateSchema(instance, dep, options, childContext);
  706. if(result.instance !== res.instance) result.instance = res.instance;
  707. if (res && res.errors.length) {
  708. result.addError({
  709. name: 'dependencies',
  710. argument: childContext.propertyPath,
  711. message: "does not meet dependency required by " + childContext.propertyPath,
  712. });
  713. result.importErrors(res);
  714. }
  715. }
  716. }
  717. return result;
  718. };
  719. /**
  720. * Validates whether the instance value is one of the enumerated values.
  721. *
  722. * @param instance
  723. * @param schema
  724. * @return {ValidatorResult|null}
  725. */
  726. validators['enum'] = function validateEnum (instance, schema, options, ctx) {
  727. if (!Array.isArray(schema['enum'])) {
  728. throw new SchemaError("enum expects an array", schema);
  729. }
  730. if (instance === undefined) {
  731. return null;
  732. }
  733. var result = new ValidatorResult(instance, schema, options, ctx);
  734. if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) {
  735. result.addError({
  736. name: 'enum',
  737. argument: schema['enum'],
  738. message: "is not one of enum values: " + schema['enum'].join(','),
  739. });
  740. }
  741. return result;
  742. };
  743. /**
  744. * Validates whether the instance exactly matches a given value
  745. *
  746. * @param instance
  747. * @param schema
  748. * @return {ValidatorResult|null}
  749. */
  750. validators['const'] = function validateEnum (instance, schema, options, ctx) {
  751. var result = new ValidatorResult(instance, schema, options, ctx);
  752. if (!helpers.deepCompareStrict(schema['const'], instance)) {
  753. result.addError({
  754. name: 'const',
  755. argument: schema['const'],
  756. message: "does not exactly match expected constant: " + schema['const'],
  757. });
  758. }
  759. return result;
  760. };
  761. /**
  762. * Validates whether the instance if of a prohibited type.
  763. * @param instance
  764. * @param schema
  765. * @param options
  766. * @param ctx
  767. * @return {null|ValidatorResult}
  768. */
  769. validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) {
  770. var self = this;
  771. if(instance===undefined) return null;
  772. var result = new ValidatorResult(instance, schema, options, ctx);
  773. var notTypes = schema.not || schema.disallow;
  774. if(!notTypes) return null;
  775. if(!Array.isArray(notTypes)) notTypes=[notTypes];
  776. notTypes.forEach(function (type) {
  777. if (self.testType(instance, schema, options, ctx, type)) {
  778. var schemaId = type && type.id && ('<' + type.id + '>') || type;
  779. result.addError({
  780. name: 'not',
  781. argument: schemaId,
  782. message: "is of prohibited type " + schemaId,
  783. });
  784. }
  785. });
  786. return result;
  787. };
  788. module.exports = attribute;