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