966030099de2a20a0dbf5c864a96dd74eb0336be901b1a3482dccfb4463a72e438a5bfadea7bfd934d2243553e95abb1bdff6bcbb5d568b0d68286f4454e55-exec 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. 'use strict';
  2. const Bourne = require('@hapi/bourne');
  3. const Hoek = require('@hapi/hoek');
  4. const Any = require('../any');
  5. const Cast = require('../../cast');
  6. const Ref = require('../../ref');
  7. const State = require('../state');
  8. const internals = {};
  9. internals.fastSplice = function (arr, i) {
  10. let pos = i;
  11. while (pos < arr.length) {
  12. arr[pos++] = arr[pos];
  13. }
  14. --arr.length;
  15. };
  16. internals.Array = class extends Any {
  17. constructor() {
  18. super();
  19. this._type = 'array';
  20. this._inner.items = [];
  21. this._inner.ordereds = [];
  22. this._inner.inclusions = [];
  23. this._inner.exclusions = [];
  24. this._inner.requireds = [];
  25. this._flags.sparse = false;
  26. }
  27. _base(value, state, options) {
  28. const result = {
  29. value
  30. };
  31. if (typeof value === 'string' &&
  32. options.convert) {
  33. if (value.length > 1 &&
  34. (value[0] === '[' || /^\s*\[/.test(value))) {
  35. try {
  36. result.value = Bourne.parse(value);
  37. }
  38. catch (e) { }
  39. }
  40. }
  41. let isArray = Array.isArray(result.value);
  42. const wasArray = isArray;
  43. if (options.convert && this._flags.single && !isArray) {
  44. result.value = [result.value];
  45. isArray = true;
  46. }
  47. if (!isArray) {
  48. result.errors = this.createError('array.base', null, state, options);
  49. return result;
  50. }
  51. if (this._inner.inclusions.length ||
  52. this._inner.exclusions.length ||
  53. this._inner.requireds.length ||
  54. this._inner.ordereds.length ||
  55. !this._flags.sparse) {
  56. // Clone the array so that we don't modify the original
  57. if (wasArray) {
  58. result.value = result.value.slice(0);
  59. }
  60. result.errors = this._checkItems(result.value, wasArray, state, options);
  61. if (result.errors && wasArray && options.convert && this._flags.single) {
  62. // Attempt a 2nd pass by putting the array inside one.
  63. const previousErrors = result.errors;
  64. result.value = [result.value];
  65. result.errors = this._checkItems(result.value, wasArray, state, options);
  66. if (result.errors) {
  67. // Restore previous errors and value since this didn't validate either.
  68. result.errors = previousErrors;
  69. result.value = result.value[0];
  70. }
  71. }
  72. }
  73. return result;
  74. }
  75. _checkItems(items, wasArray, state, options) {
  76. const errors = [];
  77. let errored;
  78. const requireds = this._inner.requireds.slice();
  79. const ordereds = this._inner.ordereds.slice();
  80. const inclusions = [...this._inner.inclusions, ...requireds];
  81. let il = items.length;
  82. for (let i = 0; i < il; ++i) {
  83. errored = false;
  84. const item = items[i];
  85. let isValid = false;
  86. const key = wasArray ? i : state.key;
  87. const path = wasArray ? [...state.path, i] : state.path;
  88. const localState = new State(key, path, state.parent, state.reference);
  89. let res;
  90. // Sparse
  91. if (!this._flags.sparse && item === undefined) {
  92. errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
  93. if (options.abortEarly) {
  94. return errors;
  95. }
  96. ordereds.shift();
  97. continue;
  98. }
  99. // Exclusions
  100. for (let j = 0; j < this._inner.exclusions.length; ++j) {
  101. res = this._inner.exclusions[j]._validate(item, localState, {}); // Not passing options to use defaults
  102. if (!res.errors) {
  103. errors.push(this.createError(wasArray ? 'array.excludes' : 'array.excludesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options));
  104. errored = true;
  105. if (options.abortEarly) {
  106. return errors;
  107. }
  108. ordereds.shift();
  109. break;
  110. }
  111. }
  112. if (errored) {
  113. continue;
  114. }
  115. // Ordered
  116. if (this._inner.ordereds.length) {
  117. if (ordereds.length > 0) {
  118. const ordered = ordereds.shift();
  119. res = ordered._validate(item, localState, options);
  120. if (!res.errors) {
  121. if (ordered._flags.strip) {
  122. internals.fastSplice(items, i);
  123. --i;
  124. --il;
  125. }
  126. else if (!this._flags.sparse && res.value === undefined) {
  127. errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
  128. if (options.abortEarly) {
  129. return errors;
  130. }
  131. continue;
  132. }
  133. else {
  134. items[i] = res.value;
  135. }
  136. }
  137. else {
  138. errors.push(this.createError('array.ordered', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options));
  139. if (options.abortEarly) {
  140. return errors;
  141. }
  142. }
  143. continue;
  144. }
  145. else if (!this._inner.items.length) {
  146. errors.push(this.createError('array.orderedLength', { pos: i, limit: this._inner.ordereds.length }, { key: state.key, path: localState.path }, options));
  147. if (options.abortEarly) {
  148. return errors;
  149. }
  150. continue;
  151. }
  152. }
  153. // Requireds
  154. const requiredChecks = [];
  155. let jl = requireds.length;
  156. for (let j = 0; j < jl; ++j) {
  157. res = requiredChecks[j] = requireds[j]._validate(item, localState, options);
  158. if (!res.errors) {
  159. items[i] = res.value;
  160. isValid = true;
  161. internals.fastSplice(requireds, j);
  162. --j;
  163. --jl;
  164. if (!this._flags.sparse && res.value === undefined) {
  165. errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
  166. if (options.abortEarly) {
  167. return errors;
  168. }
  169. }
  170. break;
  171. }
  172. }
  173. if (isValid) {
  174. continue;
  175. }
  176. // Inclusions
  177. const stripUnknown = options.stripUnknown && !!options.stripUnknown.arrays || false;
  178. jl = inclusions.length;
  179. for (let j = 0; j < jl; ++j) {
  180. const inclusion = inclusions[j];
  181. // Avoid re-running requireds that already didn't match in the previous loop
  182. const previousCheck = requireds.indexOf(inclusion);
  183. if (previousCheck !== -1) {
  184. res = requiredChecks[previousCheck];
  185. }
  186. else {
  187. res = inclusion._validate(item, localState, options);
  188. if (!res.errors) {
  189. if (inclusion._flags.strip) {
  190. internals.fastSplice(items, i);
  191. --i;
  192. --il;
  193. }
  194. else if (!this._flags.sparse && res.value === undefined) {
  195. errors.push(this.createError('array.sparse', null, { key: state.key, path: localState.path, pos: i }, options));
  196. errored = true;
  197. }
  198. else {
  199. items[i] = res.value;
  200. }
  201. isValid = true;
  202. break;
  203. }
  204. }
  205. // Return the actual error if only one inclusion defined
  206. if (jl === 1) {
  207. if (stripUnknown) {
  208. internals.fastSplice(items, i);
  209. --i;
  210. --il;
  211. isValid = true;
  212. break;
  213. }
  214. errors.push(this.createError(wasArray ? 'array.includesOne' : 'array.includesOneSingle', { pos: i, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options));
  215. errored = true;
  216. if (options.abortEarly) {
  217. return errors;
  218. }
  219. break;
  220. }
  221. }
  222. if (errored) {
  223. continue;
  224. }
  225. if (this._inner.inclusions.length && !isValid) {
  226. if (stripUnknown) {
  227. internals.fastSplice(items, i);
  228. --i;
  229. --il;
  230. continue;
  231. }
  232. errors.push(this.createError(wasArray ? 'array.includes' : 'array.includesSingle', { pos: i, value: item }, { key: state.key, path: localState.path }, options));
  233. if (options.abortEarly) {
  234. return errors;
  235. }
  236. }
  237. }
  238. if (requireds.length) {
  239. this._fillMissedErrors(errors, requireds, state, options);
  240. }
  241. if (ordereds.length) {
  242. this._fillOrderedErrors(errors, ordereds, state, options);
  243. }
  244. return errors.length ? errors : null;
  245. }
  246. describe() {
  247. const description = super.describe();
  248. if (this._inner.ordereds.length) {
  249. description.orderedItems = [];
  250. for (let i = 0; i < this._inner.ordereds.length; ++i) {
  251. description.orderedItems.push(this._inner.ordereds[i].describe());
  252. }
  253. }
  254. if (this._inner.items.length) {
  255. description.items = [];
  256. for (let i = 0; i < this._inner.items.length; ++i) {
  257. description.items.push(this._inner.items[i].describe());
  258. }
  259. }
  260. if (description.rules) {
  261. for (let i = 0; i < description.rules.length; ++i) {
  262. const rule = description.rules[i];
  263. if (rule.name === 'has') {
  264. rule.arg = rule.arg.describe();
  265. }
  266. }
  267. }
  268. return description;
  269. }
  270. items(...schemas) {
  271. const obj = this.clone();
  272. Hoek.flatten(schemas).forEach((type, index) => {
  273. try {
  274. type = Cast.schema(this._currentJoi, type);
  275. }
  276. catch (castErr) {
  277. if (castErr.hasOwnProperty('path')) {
  278. castErr.path = index + '.' + castErr.path;
  279. }
  280. else {
  281. castErr.path = index;
  282. }
  283. castErr.message = `${castErr.message}(${castErr.path})`;
  284. throw castErr;
  285. }
  286. obj._inner.items.push(type);
  287. if (type._flags.presence === 'required') {
  288. obj._inner.requireds.push(type);
  289. }
  290. else if (type._flags.presence === 'forbidden') {
  291. obj._inner.exclusions.push(type.optional());
  292. }
  293. else {
  294. obj._inner.inclusions.push(type);
  295. }
  296. });
  297. return obj;
  298. }
  299. ordered(...schemas) {
  300. const obj = this.clone();
  301. Hoek.flatten(schemas).forEach((type, index) => {
  302. try {
  303. type = Cast.schema(this._currentJoi, type);
  304. }
  305. catch (castErr) {
  306. if (castErr.hasOwnProperty('path')) {
  307. castErr.path = index + '.' + castErr.path;
  308. }
  309. else {
  310. castErr.path = index;
  311. }
  312. castErr.message = `${castErr.message}(${castErr.path})`;
  313. throw castErr;
  314. }
  315. obj._inner.ordereds.push(type);
  316. });
  317. return obj;
  318. }
  319. min(limit) {
  320. const isRef = Ref.isRef(limit);
  321. Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
  322. return this._testUnique('min', limit, function (value, state, options) {
  323. let compareTo;
  324. if (isRef) {
  325. compareTo = limit(state.reference || state.parent, options);
  326. if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
  327. return this.createError('array.ref', { ref: limit, value: compareTo }, state, options);
  328. }
  329. }
  330. else {
  331. compareTo = limit;
  332. }
  333. if (value.length >= compareTo) {
  334. return value;
  335. }
  336. return this.createError('array.min', { limit, value }, state, options);
  337. });
  338. }
  339. max(limit) {
  340. const isRef = Ref.isRef(limit);
  341. Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
  342. return this._testUnique('max', limit, function (value, state, options) {
  343. let compareTo;
  344. if (isRef) {
  345. compareTo = limit(state.reference || state.parent, options);
  346. if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
  347. return this.createError('array.ref', { ref: limit.key }, state, options);
  348. }
  349. }
  350. else {
  351. compareTo = limit;
  352. }
  353. if (value.length <= compareTo) {
  354. return value;
  355. }
  356. return this.createError('array.max', { limit, value }, state, options);
  357. });
  358. }
  359. length(limit) {
  360. const isRef = Ref.isRef(limit);
  361. Hoek.assert((Number.isSafeInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference');
  362. return this._testUnique('length', limit, function (value, state, options) {
  363. let compareTo;
  364. if (isRef) {
  365. compareTo = limit(state.reference || state.parent, options);
  366. if (!(Number.isSafeInteger(compareTo) && compareTo >= 0)) {
  367. return this.createError('array.ref', { ref: limit.key }, state, options);
  368. }
  369. }
  370. else {
  371. compareTo = limit;
  372. }
  373. if (value.length === compareTo) {
  374. return value;
  375. }
  376. return this.createError('array.length', { limit, value }, state, options);
  377. });
  378. }
  379. has(schema) {
  380. try {
  381. schema = Cast.schema(this._currentJoi, schema);
  382. }
  383. catch (castErr) {
  384. if (castErr.hasOwnProperty('path')) {
  385. castErr.message = `${castErr.message}(${castErr.path})`;
  386. }
  387. throw castErr;
  388. }
  389. return this._test('has', schema, function (value, state, options) {
  390. const isValid = value.some((item, idx) => {
  391. const localState = new State(idx, [...state.path, idx], state.key, state.reference);
  392. return !schema._validate(item, localState, options).errors;
  393. });
  394. if (isValid) {
  395. return value;
  396. }
  397. const patternLabel = schema._getLabel();
  398. if (patternLabel) {
  399. return this.createError('array.hasKnown', { patternLabel }, state, options);
  400. }
  401. return this.createError('array.hasUnknown', null, state, options);
  402. });
  403. }
  404. unique(comparator, configs) {
  405. Hoek.assert(comparator === undefined ||
  406. typeof comparator === 'function' ||
  407. typeof comparator === 'string', 'comparator must be a function or a string');
  408. Hoek.assert(configs === undefined ||
  409. typeof configs === 'object', 'configs must be an object');
  410. const settings = {
  411. ignoreUndefined: (configs && configs.ignoreUndefined) || false
  412. };
  413. if (typeof comparator === 'string') {
  414. settings.path = comparator;
  415. }
  416. else if (typeof comparator === 'function') {
  417. settings.comparator = comparator;
  418. }
  419. return this._test('unique', settings, function (value, state, options) {
  420. const found = {
  421. string: Object.create(null),
  422. number: Object.create(null),
  423. undefined: Object.create(null),
  424. boolean: Object.create(null),
  425. object: new Map(),
  426. function: new Map(),
  427. custom: new Map()
  428. };
  429. const compare = settings.comparator || Hoek.deepEqual;
  430. const ignoreUndefined = settings.ignoreUndefined;
  431. for (let i = 0; i < value.length; ++i) {
  432. const item = settings.path ? Hoek.reach(value[i], settings.path) : value[i];
  433. const records = settings.comparator ? found.custom : found[typeof item];
  434. // All available types are supported, so it's not possible to reach 100% coverage without ignoring this line.
  435. // I still want to keep the test for future js versions with new types (eg. Symbol).
  436. if (/* $lab:coverage:off$ */ records /* $lab:coverage:on$ */) {
  437. if (records instanceof Map) {
  438. const entries = records.entries();
  439. let current;
  440. while (!(current = entries.next()).done) {
  441. if (compare(current.value[0], item)) {
  442. const localState = new State(state.key, [...state.path, i], state.parent, state.reference);
  443. const context = {
  444. pos: i,
  445. value: value[i],
  446. dupePos: current.value[1],
  447. dupeValue: value[current.value[1]]
  448. };
  449. if (settings.path) {
  450. context.path = settings.path;
  451. }
  452. return this.createError('array.unique', context, localState, options);
  453. }
  454. }
  455. records.set(item, i);
  456. }
  457. else {
  458. if ((!ignoreUndefined || item !== undefined) && records[item] !== undefined) {
  459. const localState = new State(state.key, [...state.path, i], state.parent, state.reference);
  460. const context = {
  461. pos: i,
  462. value: value[i],
  463. dupePos: records[item],
  464. dupeValue: value[records[item]]
  465. };
  466. if (settings.path) {
  467. context.path = settings.path;
  468. }
  469. return this.createError('array.unique', context, localState, options);
  470. }
  471. records[item] = i;
  472. }
  473. }
  474. }
  475. return value;
  476. });
  477. }
  478. sparse(enabled) {
  479. const value = enabled === undefined ? true : !!enabled;
  480. if (this._flags.sparse === value) {
  481. return this;
  482. }
  483. const obj = this.clone();
  484. obj._flags.sparse = value;
  485. return obj;
  486. }
  487. single(enabled) {
  488. const value = enabled === undefined ? true : !!enabled;
  489. if (this._flags.single === value) {
  490. return this;
  491. }
  492. const obj = this.clone();
  493. obj._flags.single = value;
  494. return obj;
  495. }
  496. _fillMissedErrors(errors, requireds, state, options) {
  497. const knownMisses = [];
  498. let unknownMisses = 0;
  499. for (let i = 0; i < requireds.length; ++i) {
  500. const label = requireds[i]._getLabel();
  501. if (label) {
  502. knownMisses.push(label);
  503. }
  504. else {
  505. ++unknownMisses;
  506. }
  507. }
  508. if (knownMisses.length) {
  509. if (unknownMisses) {
  510. errors.push(this.createError('array.includesRequiredBoth', { knownMisses, unknownMisses }, { key: state.key, path: state.path }, options));
  511. }
  512. else {
  513. errors.push(this.createError('array.includesRequiredKnowns', { knownMisses }, { key: state.key, path: state.path }, options));
  514. }
  515. }
  516. else {
  517. errors.push(this.createError('array.includesRequiredUnknowns', { unknownMisses }, { key: state.key, path: state.path }, options));
  518. }
  519. }
  520. _fillOrderedErrors(errors, ordereds, state, options) {
  521. const requiredOrdereds = [];
  522. for (let i = 0; i < ordereds.length; ++i) {
  523. const presence = Hoek.reach(ordereds[i], '_flags.presence');
  524. if (presence === 'required') {
  525. requiredOrdereds.push(ordereds[i]);
  526. }
  527. }
  528. if (requiredOrdereds.length) {
  529. this._fillMissedErrors(errors, requiredOrdereds, state, options);
  530. }
  531. }
  532. };
  533. module.exports = new internals.Array();