select-fields.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. const _ = require('lodash');
  2. const formatParamOutput = require('./format-param-output');
  3. const persistValues = require('./persist-values');
  4. module.exports = (req, context, options = {}) => {
  5. let allFields = [];
  6. const optionalityFilter = options.filterOptionals == null || options.filterOptionals
  7. ? createOptionalityFilter(context)
  8. : Boolean;
  9. const sanitizerMapper = createSanitizerMapper(req, context, options);
  10. context.fields.map(field => field == null ? '' : field).forEach(field => {
  11. let instances = _(context.locations)
  12. .flatMap(createFieldExpander(req, field))
  13. .map(sanitizerMapper)
  14. .filter(optionalityFilter)
  15. .value();
  16. // #331 - When multiple locations are involved, all of them must pass the validation.
  17. // If none of the locations contain the field, we at least include one for error reporting.
  18. // #458, #531 - Wildcards are an exception though: they may yield 0..* instances with different
  19. // paths, so we may want to skip this filtering.
  20. if (instances.length > 1 && context.locations.length > 1 && !field.includes('*')) {
  21. const withValue = instances.filter(field => field.value !== undefined);
  22. instances = withValue.length ? withValue : [instances[0]];
  23. }
  24. allFields = allFields.concat(instances);
  25. });
  26. persistValues(req, allFields);
  27. return _.uniqWith(allFields, _.isEqual);
  28. };
  29. function createFieldExpander(req, field) {
  30. return location => {
  31. const fieldPath = location === 'headers' ? field.toLowerCase() : field;
  32. return expand(req[location], fieldPath, []).map(path => ({
  33. location,
  34. path: path,
  35. value: path === '' ? req[location] : _.get(req[location], path)
  36. })).map(field => Object.assign(field, {
  37. originalValue: field.value
  38. }));
  39. };
  40. }
  41. function expand(object, path, paths) {
  42. const segments = _.toPath(path);
  43. const wildcard = segments.indexOf('*');
  44. if (wildcard > -1) {
  45. const subObject = wildcard ? _.get(object, segments.slice(0, wildcard)) : object;
  46. if (!subObject) {
  47. return paths;
  48. }
  49. Object.keys(subObject)
  50. .map(key => segments
  51. .slice(0, wildcard)
  52. .concat(key)
  53. .concat(segments.slice(wildcard + 1)))
  54. .forEach(path => expand(object, path, paths));
  55. } else {
  56. paths.push(formatParamOutput(segments));
  57. }
  58. return paths;
  59. }
  60. function createSanitizerMapper(req, { sanitizers = [] }, { sanitize = true }) {
  61. return !sanitize ? field => field : field => sanitizers.reduce((prev, sanitizer) => {
  62. const value = typeof prev.value === 'string' ?
  63. callSanitizer(sanitizer, prev) :
  64. prev.value;
  65. return Object.assign({}, prev, { value });
  66. }, field);
  67. function callSanitizer(config, field) {
  68. return !config.custom ?
  69. config.sanitizer(field.value, ...config.options) :
  70. config.sanitizer(field.value, {
  71. req,
  72. location: field.location,
  73. path: field.path
  74. });
  75. }
  76. }
  77. function createOptionalityFilter({ optional }) {
  78. const checks = [
  79. value => value !== undefined,
  80. value => optional.nullable ? value != null : true,
  81. value => optional.checkFalsy ? value : true
  82. ];
  83. return field => {
  84. if (!optional) {
  85. return true;
  86. }
  87. return checks.every(check => check(field.value));
  88. };
  89. }