one-of.js 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.oneOf = oneOf;
  4. const _ = require("lodash");
  5. const chain_1 = require("../chain");
  6. const context_builder_1 = require("../context-builder");
  7. const utils_1 = require("../utils");
  8. // A dummy context item that gets added to surrogate contexts just to make them run
  9. const dummyItem = { async run() { } };
  10. /**
  11. * Creates a middleware that will ensure that at least one of the given validation chains
  12. * or validation chain groups are valid.
  13. *
  14. * If none are, a single `AlternativeValidationError` or `GroupedAlternativeValidationError`
  15. * is added to the request, with the errors of each chain made available under the `nestedErrors` property.
  16. *
  17. * @param chains an array of validation chains to check if are valid.
  18. * If any of the items of `chains` is an array of validation chains, then all of them
  19. * must be valid together for the request to be considered valid.
  20. */
  21. function oneOf(chains, options = {}) {
  22. const run = async (req, opts) => {
  23. const surrogateContext = new context_builder_1.ContextBuilder().addItem(dummyItem).build();
  24. // Run each group of chains in parallel
  25. const promises = chains.map(async (chain) => {
  26. const group = Array.isArray(chain) ? chain : [chain];
  27. const results = await (0, utils_1.runAllChains)(req, group, { dryRun: true });
  28. const { contexts, groupErrors } = results.reduce(({ contexts, groupErrors }, result) => {
  29. const { context } = result;
  30. contexts.push(context);
  31. const fieldErrors = context.errors.filter((error) => error.type === 'field');
  32. groupErrors.push(...fieldErrors);
  33. return { contexts, groupErrors };
  34. }, {
  35. contexts: [],
  36. groupErrors: [],
  37. });
  38. // #536: The data from a chain within oneOf() can only be made available to e.g. matchedData()
  39. // if its entire group is valid.
  40. if (!groupErrors.length) {
  41. contexts.forEach(context => {
  42. surrogateContext.addFieldInstances(context.getData());
  43. });
  44. }
  45. return groupErrors;
  46. });
  47. const allErrors = await Promise.all(promises);
  48. const success = allErrors.some(groupErrors => groupErrors.length === 0);
  49. if (!success) {
  50. const message = options.message || 'Invalid value(s)';
  51. switch (options.errorType) {
  52. case 'flat':
  53. surrogateContext.addError({
  54. type: 'alternative',
  55. req,
  56. message,
  57. nestedErrors: _.flatMap(allErrors),
  58. });
  59. break;
  60. case 'least_errored':
  61. let leastErroredIndex = 0;
  62. for (let i = 1; i < allErrors.length; i++) {
  63. if (allErrors[i].length < allErrors[leastErroredIndex].length) {
  64. leastErroredIndex = i;
  65. }
  66. }
  67. surrogateContext.addError({
  68. type: 'alternative',
  69. req,
  70. message,
  71. nestedErrors: allErrors[leastErroredIndex],
  72. });
  73. break;
  74. case 'grouped':
  75. default:
  76. // grouped
  77. surrogateContext.addError({
  78. type: 'alternative_grouped',
  79. req,
  80. message,
  81. nestedErrors: allErrors,
  82. });
  83. break;
  84. }
  85. }
  86. // Final context running pass to ensure contexts are added and values are modified properly
  87. return await new chain_1.ContextRunnerImpl(surrogateContext).run(req, opts);
  88. };
  89. const middleware = (req, _res, next) => run(req).then(() => next(), next);
  90. return Object.assign(middleware, { run });
  91. }