context.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Context = void 0;
  4. const _ = require("lodash");
  5. function getDataMapKey(path, location) {
  6. return `${location}:${path}`;
  7. }
  8. class Context {
  9. get errors() {
  10. return this._errors;
  11. }
  12. constructor(fields, locations, stack, optional, bail, visibility = { type: 'visible' }, message) {
  13. this.fields = fields;
  14. this.locations = locations;
  15. this.stack = stack;
  16. this.optional = optional;
  17. this.bail = bail;
  18. this.visibility = visibility;
  19. this.message = message;
  20. this._errors = [];
  21. this.dataMap = new Map();
  22. }
  23. getData(options = { requiredOnly: false }) {
  24. const { optional } = this;
  25. const checks = options.requiredOnly && optional
  26. ? [
  27. (value) => value !== undefined,
  28. (value) => (optional === 'null' ? value != null : true),
  29. (value) => (optional === 'falsy' ? value : true),
  30. ]
  31. : [];
  32. return _([...this.dataMap.values()])
  33. .groupBy('originalPath')
  34. .flatMap((instances, group) => {
  35. const locations = _.uniqBy(instances, 'location');
  36. // #331 - When multiple locations are involved, all of them must pass the validation.
  37. // If none of the locations contain the field, we at least include one for error reporting.
  38. // #458, #531 - Wildcards are an exception though: they may yield 0..* instances with different
  39. // paths, so we may want to skip this filtering.
  40. if (instances.length > 1 && locations.length > 1 && !group.includes('*')) {
  41. const withValue = instances.filter(instance => instance.value !== undefined);
  42. return withValue.length ? withValue : [instances[0]];
  43. }
  44. return instances;
  45. })
  46. .filter(instance => checks.every(check => check(instance.value)))
  47. .valueOf();
  48. }
  49. addFieldInstances(instances) {
  50. instances.forEach(instance => {
  51. this.dataMap.set(getDataMapKey(instance.path, instance.location), { ...instance });
  52. });
  53. }
  54. setData(path, value, location) {
  55. const instance = this.dataMap.get(getDataMapKey(path, location));
  56. if (!instance) {
  57. throw new Error('Attempt to write data that did not pre-exist in context');
  58. }
  59. instance.value = value;
  60. }
  61. addError(opts) {
  62. const msg = opts.message || this.message || 'Invalid value';
  63. let error;
  64. switch (opts.type) {
  65. case 'field':
  66. error = this.updateVisibility({
  67. type: 'field',
  68. value: opts.value,
  69. msg: typeof msg === 'function' ? msg(opts.value, opts.meta) : msg,
  70. path: opts.meta?.path,
  71. location: opts.meta?.location,
  72. });
  73. break;
  74. case 'unknown_fields':
  75. error = {
  76. type: 'unknown_fields',
  77. msg: typeof msg === 'function' ? msg(opts.fields, { req: opts.req }) : msg,
  78. fields: opts.fields,
  79. };
  80. break;
  81. case 'alternative':
  82. error = {
  83. type: 'alternative',
  84. msg: typeof msg === 'function' ? msg(opts.nestedErrors, { req: opts.req }) : msg,
  85. nestedErrors: opts.nestedErrors.map(error => this.updateVisibility(error)),
  86. };
  87. break;
  88. case 'alternative_grouped':
  89. error = {
  90. type: 'alternative_grouped',
  91. msg: typeof msg === 'function' ? msg(opts.nestedErrors, { req: opts.req }) : msg,
  92. nestedErrors: opts.nestedErrors.map(errors => errors.map(error => this.updateVisibility(error))),
  93. };
  94. break;
  95. default:
  96. throw new Error(`Unhandled addError case`);
  97. }
  98. this._errors.push(error);
  99. }
  100. updateVisibility(error) {
  101. switch (this.visibility.type) {
  102. case 'hidden':
  103. error = { ...error };
  104. delete error.value;
  105. return error;
  106. case 'redacted':
  107. return {
  108. ...error,
  109. value: this.visibility.value,
  110. };
  111. case 'visible':
  112. default:
  113. return error;
  114. }
  115. }
  116. }
  117. exports.Context = Context;