validate.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. import { deepCompareStrict } from './deep-compare-strict.js';
  2. import { dereference } from './dereference.js';
  3. import { format } from './format.js';
  4. import { encodePointer } from './pointer.js';
  5. import { ucs2length } from './ucs2-length.js';
  6. export function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = '#', schemaLocation = '#', evaluated = Object.create(null)) {
  7. if (schema === true) {
  8. return { valid: true, errors: [] };
  9. }
  10. if (schema === false) {
  11. return {
  12. valid: false,
  13. errors: [
  14. {
  15. instanceLocation,
  16. keyword: 'false',
  17. keywordLocation: instanceLocation,
  18. error: 'False boolean schema.'
  19. }
  20. ]
  21. };
  22. }
  23. const rawInstanceType = typeof instance;
  24. let instanceType;
  25. switch (rawInstanceType) {
  26. case 'boolean':
  27. case 'number':
  28. case 'string':
  29. instanceType = rawInstanceType;
  30. break;
  31. case 'object':
  32. if (instance === null) {
  33. instanceType = 'null';
  34. }
  35. else if (Array.isArray(instance)) {
  36. instanceType = 'array';
  37. }
  38. else {
  39. instanceType = 'object';
  40. }
  41. break;
  42. default:
  43. throw new Error(`Instances of "${rawInstanceType}" type are not supported.`);
  44. }
  45. const { $ref, $recursiveRef, $recursiveAnchor, type: $type, const: $const, enum: $enum, required: $required, not: $not, anyOf: $anyOf, allOf: $allOf, oneOf: $oneOf, if: $if, then: $then, else: $else, format: $format, properties: $properties, patternProperties: $patternProperties, additionalProperties: $additionalProperties, unevaluatedProperties: $unevaluatedProperties, minProperties: $minProperties, maxProperties: $maxProperties, propertyNames: $propertyNames, dependentRequired: $dependentRequired, dependentSchemas: $dependentSchemas, dependencies: $dependencies, prefixItems: $prefixItems, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minContains: $minContains, maxContains: $maxContains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, __absolute_ref__, __absolute_recursive_ref__ } = schema;
  46. const errors = [];
  47. if ($recursiveAnchor === true && recursiveAnchor === null) {
  48. recursiveAnchor = schema;
  49. }
  50. if ($recursiveRef === '#') {
  51. const refSchema = recursiveAnchor === null
  52. ? lookup[__absolute_recursive_ref__]
  53. : recursiveAnchor;
  54. const keywordLocation = `${schemaLocation}/$recursiveRef`;
  55. const result = validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, shortCircuit, refSchema, instanceLocation, keywordLocation, evaluated);
  56. if (!result.valid) {
  57. errors.push({
  58. instanceLocation,
  59. keyword: '$recursiveRef',
  60. keywordLocation,
  61. error: 'A subschema had errors.'
  62. }, ...result.errors);
  63. }
  64. }
  65. if ($ref !== undefined) {
  66. const uri = __absolute_ref__ || $ref;
  67. const refSchema = lookup[uri];
  68. if (refSchema === undefined) {
  69. let message = `Unresolved $ref "${$ref}".`;
  70. if (__absolute_ref__ && __absolute_ref__ !== $ref) {
  71. message += ` Absolute URI "${__absolute_ref__}".`;
  72. }
  73. message += `\nKnown schemas:\n- ${Object.keys(lookup).join('\n- ')}`;
  74. throw new Error(message);
  75. }
  76. const keywordLocation = `${schemaLocation}/$ref`;
  77. const result = validate(instance, refSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated);
  78. if (!result.valid) {
  79. errors.push({
  80. instanceLocation,
  81. keyword: '$ref',
  82. keywordLocation,
  83. error: 'A subschema had errors.'
  84. }, ...result.errors);
  85. }
  86. if (draft === '4' || draft === '7') {
  87. return { valid: errors.length === 0, errors };
  88. }
  89. }
  90. if (Array.isArray($type)) {
  91. let length = $type.length;
  92. let valid = false;
  93. for (let i = 0; i < length; i++) {
  94. if (instanceType === $type[i] ||
  95. ($type[i] === 'integer' &&
  96. instanceType === 'number' &&
  97. instance % 1 === 0 &&
  98. instance === instance)) {
  99. valid = true;
  100. break;
  101. }
  102. }
  103. if (!valid) {
  104. errors.push({
  105. instanceLocation,
  106. keyword: 'type',
  107. keywordLocation: `${schemaLocation}/type`,
  108. error: `Instance type "${instanceType}" is invalid. Expected "${$type.join('", "')}".`
  109. });
  110. }
  111. }
  112. else if ($type === 'integer') {
  113. if (instanceType !== 'number' || instance % 1 || instance !== instance) {
  114. errors.push({
  115. instanceLocation,
  116. keyword: 'type',
  117. keywordLocation: `${schemaLocation}/type`,
  118. error: `Instance type "${instanceType}" is invalid. Expected "${$type}".`
  119. });
  120. }
  121. }
  122. else if ($type !== undefined && instanceType !== $type) {
  123. errors.push({
  124. instanceLocation,
  125. keyword: 'type',
  126. keywordLocation: `${schemaLocation}/type`,
  127. error: `Instance type "${instanceType}" is invalid. Expected "${$type}".`
  128. });
  129. }
  130. if ($const !== undefined) {
  131. if (instanceType === 'object' || instanceType === 'array') {
  132. if (!deepCompareStrict(instance, $const)) {
  133. errors.push({
  134. instanceLocation,
  135. keyword: 'const',
  136. keywordLocation: `${schemaLocation}/const`,
  137. error: `Instance does not match ${JSON.stringify($const)}.`
  138. });
  139. }
  140. }
  141. else if (instance !== $const) {
  142. errors.push({
  143. instanceLocation,
  144. keyword: 'const',
  145. keywordLocation: `${schemaLocation}/const`,
  146. error: `Instance does not match ${JSON.stringify($const)}.`
  147. });
  148. }
  149. }
  150. if ($enum !== undefined) {
  151. if (instanceType === 'object' || instanceType === 'array') {
  152. if (!$enum.some(value => deepCompareStrict(instance, value))) {
  153. errors.push({
  154. instanceLocation,
  155. keyword: 'enum',
  156. keywordLocation: `${schemaLocation}/enum`,
  157. error: `Instance does not match any of ${JSON.stringify($enum)}.`
  158. });
  159. }
  160. }
  161. else if (!$enum.some(value => instance === value)) {
  162. errors.push({
  163. instanceLocation,
  164. keyword: 'enum',
  165. keywordLocation: `${schemaLocation}/enum`,
  166. error: `Instance does not match any of ${JSON.stringify($enum)}.`
  167. });
  168. }
  169. }
  170. if ($not !== undefined) {
  171. const keywordLocation = `${schemaLocation}/not`;
  172. const result = validate(instance, $not, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation);
  173. if (result.valid) {
  174. errors.push({
  175. instanceLocation,
  176. keyword: 'not',
  177. keywordLocation,
  178. error: 'Instance matched "not" schema.'
  179. });
  180. }
  181. }
  182. let subEvaluateds = [];
  183. if ($anyOf !== undefined) {
  184. const keywordLocation = `${schemaLocation}/anyOf`;
  185. const errorsLength = errors.length;
  186. let anyValid = false;
  187. for (let i = 0; i < $anyOf.length; i++) {
  188. const subSchema = $anyOf[i];
  189. const subEvaluated = Object.create(evaluated);
  190. const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
  191. errors.push(...result.errors);
  192. anyValid = anyValid || result.valid;
  193. if (result.valid) {
  194. subEvaluateds.push(subEvaluated);
  195. }
  196. }
  197. if (anyValid) {
  198. errors.length = errorsLength;
  199. }
  200. else {
  201. errors.splice(errorsLength, 0, {
  202. instanceLocation,
  203. keyword: 'anyOf',
  204. keywordLocation,
  205. error: 'Instance does not match any subschemas.'
  206. });
  207. }
  208. }
  209. if ($allOf !== undefined) {
  210. const keywordLocation = `${schemaLocation}/allOf`;
  211. const errorsLength = errors.length;
  212. let allValid = true;
  213. for (let i = 0; i < $allOf.length; i++) {
  214. const subSchema = $allOf[i];
  215. const subEvaluated = Object.create(evaluated);
  216. const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
  217. errors.push(...result.errors);
  218. allValid = allValid && result.valid;
  219. if (result.valid) {
  220. subEvaluateds.push(subEvaluated);
  221. }
  222. }
  223. if (allValid) {
  224. errors.length = errorsLength;
  225. }
  226. else {
  227. errors.splice(errorsLength, 0, {
  228. instanceLocation,
  229. keyword: 'allOf',
  230. keywordLocation,
  231. error: `Instance does not match every subschema.`
  232. });
  233. }
  234. }
  235. if ($oneOf !== undefined) {
  236. const keywordLocation = `${schemaLocation}/oneOf`;
  237. const errorsLength = errors.length;
  238. const matches = $oneOf.filter((subSchema, i) => {
  239. const subEvaluated = Object.create(evaluated);
  240. const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
  241. errors.push(...result.errors);
  242. if (result.valid) {
  243. subEvaluateds.push(subEvaluated);
  244. }
  245. return result.valid;
  246. }).length;
  247. if (matches === 1) {
  248. errors.length = errorsLength;
  249. }
  250. else {
  251. errors.splice(errorsLength, 0, {
  252. instanceLocation,
  253. keyword: 'oneOf',
  254. keywordLocation,
  255. error: `Instance does not match exactly one subschema (${matches} matches).`
  256. });
  257. }
  258. }
  259. if (instanceType === 'object' || instanceType === 'array') {
  260. Object.assign(evaluated, ...subEvaluateds);
  261. }
  262. if ($if !== undefined) {
  263. const keywordLocation = `${schemaLocation}/if`;
  264. const conditionResult = validate(instance, $if, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated).valid;
  265. if (conditionResult) {
  266. if ($then !== undefined) {
  267. const thenResult = validate(instance, $then, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated);
  268. if (!thenResult.valid) {
  269. errors.push({
  270. instanceLocation,
  271. keyword: 'if',
  272. keywordLocation,
  273. error: `Instance does not match "then" schema.`
  274. }, ...thenResult.errors);
  275. }
  276. }
  277. }
  278. else if ($else !== undefined) {
  279. const elseResult = validate(instance, $else, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated);
  280. if (!elseResult.valid) {
  281. errors.push({
  282. instanceLocation,
  283. keyword: 'if',
  284. keywordLocation,
  285. error: `Instance does not match "else" schema.`
  286. }, ...elseResult.errors);
  287. }
  288. }
  289. }
  290. if (instanceType === 'object') {
  291. if ($required !== undefined) {
  292. for (const key of $required) {
  293. if (!(key in instance)) {
  294. errors.push({
  295. instanceLocation,
  296. keyword: 'required',
  297. keywordLocation: `${schemaLocation}/required`,
  298. error: `Instance does not have required property "${key}".`
  299. });
  300. }
  301. }
  302. }
  303. const keys = Object.keys(instance);
  304. if ($minProperties !== undefined && keys.length < $minProperties) {
  305. errors.push({
  306. instanceLocation,
  307. keyword: 'minProperties',
  308. keywordLocation: `${schemaLocation}/minProperties`,
  309. error: `Instance does not have at least ${$minProperties} properties.`
  310. });
  311. }
  312. if ($maxProperties !== undefined && keys.length > $maxProperties) {
  313. errors.push({
  314. instanceLocation,
  315. keyword: 'maxProperties',
  316. keywordLocation: `${schemaLocation}/maxProperties`,
  317. error: `Instance does not have at least ${$maxProperties} properties.`
  318. });
  319. }
  320. if ($propertyNames !== undefined) {
  321. const keywordLocation = `${schemaLocation}/propertyNames`;
  322. for (const key in instance) {
  323. const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
  324. const result = validate(key, $propertyNames, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
  325. if (!result.valid) {
  326. errors.push({
  327. instanceLocation,
  328. keyword: 'propertyNames',
  329. keywordLocation,
  330. error: `Property name "${key}" does not match schema.`
  331. }, ...result.errors);
  332. }
  333. }
  334. }
  335. if ($dependentRequired !== undefined) {
  336. const keywordLocation = `${schemaLocation}/dependantRequired`;
  337. for (const key in $dependentRequired) {
  338. if (key in instance) {
  339. const required = $dependentRequired[key];
  340. for (const dependantKey of required) {
  341. if (!(dependantKey in instance)) {
  342. errors.push({
  343. instanceLocation,
  344. keyword: 'dependentRequired',
  345. keywordLocation,
  346. error: `Instance has "${key}" but does not have "${dependantKey}".`
  347. });
  348. }
  349. }
  350. }
  351. }
  352. }
  353. if ($dependentSchemas !== undefined) {
  354. for (const key in $dependentSchemas) {
  355. const keywordLocation = `${schemaLocation}/dependentSchemas`;
  356. if (key in instance) {
  357. const result = validate(instance, $dependentSchemas[key], draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`, evaluated);
  358. if (!result.valid) {
  359. errors.push({
  360. instanceLocation,
  361. keyword: 'dependentSchemas',
  362. keywordLocation,
  363. error: `Instance has "${key}" but does not match dependant schema.`
  364. }, ...result.errors);
  365. }
  366. }
  367. }
  368. }
  369. if ($dependencies !== undefined) {
  370. const keywordLocation = `${schemaLocation}/dependencies`;
  371. for (const key in $dependencies) {
  372. if (key in instance) {
  373. const propsOrSchema = $dependencies[key];
  374. if (Array.isArray(propsOrSchema)) {
  375. for (const dependantKey of propsOrSchema) {
  376. if (!(dependantKey in instance)) {
  377. errors.push({
  378. instanceLocation,
  379. keyword: 'dependencies',
  380. keywordLocation,
  381. error: `Instance has "${key}" but does not have "${dependantKey}".`
  382. });
  383. }
  384. }
  385. }
  386. else {
  387. const result = validate(instance, propsOrSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`);
  388. if (!result.valid) {
  389. errors.push({
  390. instanceLocation,
  391. keyword: 'dependencies',
  392. keywordLocation,
  393. error: `Instance has "${key}" but does not match dependant schema.`
  394. }, ...result.errors);
  395. }
  396. }
  397. }
  398. }
  399. }
  400. const thisEvaluated = Object.create(null);
  401. let stop = false;
  402. if ($properties !== undefined) {
  403. const keywordLocation = `${schemaLocation}/properties`;
  404. for (const key in $properties) {
  405. if (!(key in instance)) {
  406. continue;
  407. }
  408. const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
  409. const result = validate(instance[key], $properties[key], draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}`);
  410. if (result.valid) {
  411. evaluated[key] = thisEvaluated[key] = true;
  412. }
  413. else {
  414. stop = shortCircuit;
  415. errors.push({
  416. instanceLocation,
  417. keyword: 'properties',
  418. keywordLocation,
  419. error: `Property "${key}" does not match schema.`
  420. }, ...result.errors);
  421. if (stop)
  422. break;
  423. }
  424. }
  425. }
  426. if (!stop && $patternProperties !== undefined) {
  427. const keywordLocation = `${schemaLocation}/patternProperties`;
  428. for (const pattern in $patternProperties) {
  429. const regex = new RegExp(pattern, 'u');
  430. const subSchema = $patternProperties[pattern];
  431. for (const key in instance) {
  432. if (!regex.test(key)) {
  433. continue;
  434. }
  435. const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
  436. const result = validate(instance[key], subSchema, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}`);
  437. if (result.valid) {
  438. evaluated[key] = thisEvaluated[key] = true;
  439. }
  440. else {
  441. stop = shortCircuit;
  442. errors.push({
  443. instanceLocation,
  444. keyword: 'patternProperties',
  445. keywordLocation,
  446. error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.`
  447. }, ...result.errors);
  448. }
  449. }
  450. }
  451. }
  452. if (!stop && $additionalProperties !== undefined) {
  453. const keywordLocation = `${schemaLocation}/additionalProperties`;
  454. for (const key in instance) {
  455. if (thisEvaluated[key]) {
  456. continue;
  457. }
  458. const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
  459. const result = validate(instance[key], $additionalProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
  460. if (result.valid) {
  461. evaluated[key] = true;
  462. }
  463. else {
  464. stop = shortCircuit;
  465. errors.push({
  466. instanceLocation,
  467. keyword: 'additionalProperties',
  468. keywordLocation,
  469. error: `Property "${key}" does not match additional properties schema.`
  470. }, ...result.errors);
  471. }
  472. }
  473. }
  474. else if (!stop && $unevaluatedProperties !== undefined) {
  475. const keywordLocation = `${schemaLocation}/unevaluatedProperties`;
  476. for (const key in instance) {
  477. if (!evaluated[key]) {
  478. const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
  479. const result = validate(instance[key], $unevaluatedProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
  480. if (result.valid) {
  481. evaluated[key] = true;
  482. }
  483. else {
  484. errors.push({
  485. instanceLocation,
  486. keyword: 'unevaluatedProperties',
  487. keywordLocation,
  488. error: `Property "${key}" does not match unevaluated properties schema.`
  489. }, ...result.errors);
  490. }
  491. }
  492. }
  493. }
  494. }
  495. else if (instanceType === 'array') {
  496. if ($maxItems !== undefined && instance.length > $maxItems) {
  497. errors.push({
  498. instanceLocation,
  499. keyword: 'maxItems',
  500. keywordLocation: `${schemaLocation}/maxItems`,
  501. error: `Array has too many items (${instance.length} > ${$maxItems}).`
  502. });
  503. }
  504. if ($minItems !== undefined && instance.length < $minItems) {
  505. errors.push({
  506. instanceLocation,
  507. keyword: 'minItems',
  508. keywordLocation: `${schemaLocation}/minItems`,
  509. error: `Array has too few items (${instance.length} < ${$minItems}).`
  510. });
  511. }
  512. const length = instance.length;
  513. let i = 0;
  514. let stop = false;
  515. if ($prefixItems !== undefined) {
  516. const keywordLocation = `${schemaLocation}/prefixItems`;
  517. const length2 = Math.min($prefixItems.length, length);
  518. for (; i < length2; i++) {
  519. const result = validate(instance[i], $prefixItems[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`);
  520. evaluated[i] = true;
  521. if (!result.valid) {
  522. stop = shortCircuit;
  523. errors.push({
  524. instanceLocation,
  525. keyword: 'prefixItems',
  526. keywordLocation,
  527. error: `Items did not match schema.`
  528. }, ...result.errors);
  529. if (stop)
  530. break;
  531. }
  532. }
  533. }
  534. if ($items !== undefined) {
  535. const keywordLocation = `${schemaLocation}/items`;
  536. if (Array.isArray($items)) {
  537. const length2 = Math.min($items.length, length);
  538. for (; i < length2; i++) {
  539. const result = validate(instance[i], $items[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`);
  540. evaluated[i] = true;
  541. if (!result.valid) {
  542. stop = shortCircuit;
  543. errors.push({
  544. instanceLocation,
  545. keyword: 'items',
  546. keywordLocation,
  547. error: `Items did not match schema.`
  548. }, ...result.errors);
  549. if (stop)
  550. break;
  551. }
  552. }
  553. }
  554. else {
  555. for (; i < length; i++) {
  556. const result = validate(instance[i], $items, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
  557. evaluated[i] = true;
  558. if (!result.valid) {
  559. stop = shortCircuit;
  560. errors.push({
  561. instanceLocation,
  562. keyword: 'items',
  563. keywordLocation,
  564. error: `Items did not match schema.`
  565. }, ...result.errors);
  566. if (stop)
  567. break;
  568. }
  569. }
  570. }
  571. if (!stop && $additionalItems !== undefined) {
  572. const keywordLocation = `${schemaLocation}/additionalItems`;
  573. for (; i < length; i++) {
  574. const result = validate(instance[i], $additionalItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
  575. evaluated[i] = true;
  576. if (!result.valid) {
  577. stop = shortCircuit;
  578. errors.push({
  579. instanceLocation,
  580. keyword: 'additionalItems',
  581. keywordLocation,
  582. error: `Items did not match additional items schema.`
  583. }, ...result.errors);
  584. }
  585. }
  586. }
  587. }
  588. if ($contains !== undefined) {
  589. if (length === 0 && $minContains === undefined) {
  590. errors.push({
  591. instanceLocation,
  592. keyword: 'contains',
  593. keywordLocation: `${schemaLocation}/contains`,
  594. error: `Array is empty. It must contain at least one item matching the schema.`
  595. });
  596. }
  597. else if ($minContains !== undefined && length < $minContains) {
  598. errors.push({
  599. instanceLocation,
  600. keyword: 'minContains',
  601. keywordLocation: `${schemaLocation}/minContains`,
  602. error: `Array has less items (${length}) than minContains (${$minContains}).`
  603. });
  604. }
  605. else {
  606. const keywordLocation = `${schemaLocation}/contains`;
  607. const errorsLength = errors.length;
  608. let contained = 0;
  609. for (let j = 0; j < length; j++) {
  610. const result = validate(instance[j], $contains, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${j}`, keywordLocation);
  611. if (result.valid) {
  612. evaluated[j] = true;
  613. contained++;
  614. }
  615. else {
  616. errors.push(...result.errors);
  617. }
  618. }
  619. if (contained >= ($minContains || 0)) {
  620. errors.length = errorsLength;
  621. }
  622. if ($minContains === undefined &&
  623. $maxContains === undefined &&
  624. contained === 0) {
  625. errors.splice(errorsLength, 0, {
  626. instanceLocation,
  627. keyword: 'contains',
  628. keywordLocation,
  629. error: `Array does not contain item matching schema.`
  630. });
  631. }
  632. else if ($minContains !== undefined && contained < $minContains) {
  633. errors.push({
  634. instanceLocation,
  635. keyword: 'minContains',
  636. keywordLocation: `${schemaLocation}/minContains`,
  637. error: `Array must contain at least ${$minContains} items matching schema. Only ${contained} items were found.`
  638. });
  639. }
  640. else if ($maxContains !== undefined && contained > $maxContains) {
  641. errors.push({
  642. instanceLocation,
  643. keyword: 'maxContains',
  644. keywordLocation: `${schemaLocation}/maxContains`,
  645. error: `Array may contain at most ${$maxContains} items matching schema. ${contained} items were found.`
  646. });
  647. }
  648. }
  649. }
  650. if (!stop && $unevaluatedItems !== undefined) {
  651. const keywordLocation = `${schemaLocation}/unevaluatedItems`;
  652. for (i; i < length; i++) {
  653. if (evaluated[i]) {
  654. continue;
  655. }
  656. const result = validate(instance[i], $unevaluatedItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
  657. evaluated[i] = true;
  658. if (!result.valid) {
  659. errors.push({
  660. instanceLocation,
  661. keyword: 'unevaluatedItems',
  662. keywordLocation,
  663. error: `Items did not match unevaluated items schema.`
  664. }, ...result.errors);
  665. }
  666. }
  667. }
  668. if ($uniqueItems) {
  669. for (let j = 0; j < length; j++) {
  670. const a = instance[j];
  671. const ao = typeof a === 'object' && a !== null;
  672. for (let k = 0; k < length; k++) {
  673. if (j === k) {
  674. continue;
  675. }
  676. const b = instance[k];
  677. const bo = typeof b === 'object' && b !== null;
  678. if (a === b || (ao && bo && deepCompareStrict(a, b))) {
  679. errors.push({
  680. instanceLocation,
  681. keyword: 'uniqueItems',
  682. keywordLocation: `${schemaLocation}/uniqueItems`,
  683. error: `Duplicate items at indexes ${j} and ${k}.`
  684. });
  685. j = Number.MAX_SAFE_INTEGER;
  686. k = Number.MAX_SAFE_INTEGER;
  687. }
  688. }
  689. }
  690. }
  691. }
  692. else if (instanceType === 'number') {
  693. if (draft === '4') {
  694. if ($minimum !== undefined &&
  695. (($exclusiveMinimum === true && instance <= $minimum) ||
  696. instance < $minimum)) {
  697. errors.push({
  698. instanceLocation,
  699. keyword: 'minimum',
  700. keywordLocation: `${schemaLocation}/minimum`,
  701. error: `${instance} is less than ${$exclusiveMinimum ? 'or equal to ' : ''} ${$minimum}.`
  702. });
  703. }
  704. if ($maximum !== undefined &&
  705. (($exclusiveMaximum === true && instance >= $maximum) ||
  706. instance > $maximum)) {
  707. errors.push({
  708. instanceLocation,
  709. keyword: 'maximum',
  710. keywordLocation: `${schemaLocation}/maximum`,
  711. error: `${instance} is greater than ${$exclusiveMaximum ? 'or equal to ' : ''} ${$maximum}.`
  712. });
  713. }
  714. }
  715. else {
  716. if ($minimum !== undefined && instance < $minimum) {
  717. errors.push({
  718. instanceLocation,
  719. keyword: 'minimum',
  720. keywordLocation: `${schemaLocation}/minimum`,
  721. error: `${instance} is less than ${$minimum}.`
  722. });
  723. }
  724. if ($maximum !== undefined && instance > $maximum) {
  725. errors.push({
  726. instanceLocation,
  727. keyword: 'maximum',
  728. keywordLocation: `${schemaLocation}/maximum`,
  729. error: `${instance} is greater than ${$maximum}.`
  730. });
  731. }
  732. if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) {
  733. errors.push({
  734. instanceLocation,
  735. keyword: 'exclusiveMinimum',
  736. keywordLocation: `${schemaLocation}/exclusiveMinimum`,
  737. error: `${instance} is less than ${$exclusiveMinimum}.`
  738. });
  739. }
  740. if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) {
  741. errors.push({
  742. instanceLocation,
  743. keyword: 'exclusiveMaximum',
  744. keywordLocation: `${schemaLocation}/exclusiveMaximum`,
  745. error: `${instance} is greater than or equal to ${$exclusiveMaximum}.`
  746. });
  747. }
  748. }
  749. if ($multipleOf !== undefined) {
  750. const remainder = instance % $multipleOf;
  751. if (Math.abs(0 - remainder) >= 1.1920929e-7 &&
  752. Math.abs($multipleOf - remainder) >= 1.1920929e-7) {
  753. errors.push({
  754. instanceLocation,
  755. keyword: 'multipleOf',
  756. keywordLocation: `${schemaLocation}/multipleOf`,
  757. error: `${instance} is not a multiple of ${$multipleOf}.`
  758. });
  759. }
  760. }
  761. }
  762. else if (instanceType === 'string') {
  763. const length = $minLength === undefined && $maxLength === undefined
  764. ? 0
  765. : ucs2length(instance);
  766. if ($minLength !== undefined && length < $minLength) {
  767. errors.push({
  768. instanceLocation,
  769. keyword: 'minLength',
  770. keywordLocation: `${schemaLocation}/minLength`,
  771. error: `String is too short (${length} < ${$minLength}).`
  772. });
  773. }
  774. if ($maxLength !== undefined && length > $maxLength) {
  775. errors.push({
  776. instanceLocation,
  777. keyword: 'maxLength',
  778. keywordLocation: `${schemaLocation}/maxLength`,
  779. error: `String is too long (${length} > ${$maxLength}).`
  780. });
  781. }
  782. if ($pattern !== undefined && !new RegExp($pattern, 'u').test(instance)) {
  783. errors.push({
  784. instanceLocation,
  785. keyword: 'pattern',
  786. keywordLocation: `${schemaLocation}/pattern`,
  787. error: `String does not match pattern.`
  788. });
  789. }
  790. if ($format !== undefined &&
  791. format[$format] &&
  792. !format[$format](instance)) {
  793. errors.push({
  794. instanceLocation,
  795. keyword: 'format',
  796. keywordLocation: `${schemaLocation}/format`,
  797. error: `String does not match format "${$format}".`
  798. });
  799. }
  800. }
  801. return { valid: errors.length === 0, errors };
  802. }