@depup/express-validator 7.3.1-depup.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/changes.json +14 -0
  4. package/lib/base.d.ts +187 -0
  5. package/lib/base.js +8 -0
  6. package/lib/chain/context-handler-impl.d.ts +13 -0
  7. package/lib/chain/context-handler-impl.js +52 -0
  8. package/lib/chain/context-handler.d.ts +110 -0
  9. package/lib/chain/context-handler.js +2 -0
  10. package/lib/chain/context-runner-impl.d.ts +18 -0
  11. package/lib/chain/context-runner-impl.js +72 -0
  12. package/lib/chain/context-runner.d.ts +22 -0
  13. package/lib/chain/context-runner.js +2 -0
  14. package/lib/chain/index.d.ts +9 -0
  15. package/lib/chain/index.js +25 -0
  16. package/lib/chain/sanitizers-impl.d.ts +29 -0
  17. package/lib/chain/sanitizers-impl.js +80 -0
  18. package/lib/chain/sanitizers.d.ts +42 -0
  19. package/lib/chain/sanitizers.js +2 -0
  20. package/lib/chain/validation-chain.d.ts +18 -0
  21. package/lib/chain/validation-chain.js +2 -0
  22. package/lib/chain/validators-impl.d.ts +121 -0
  23. package/lib/chain/validators-impl.js +353 -0
  24. package/lib/chain/validators.d.ts +181 -0
  25. package/lib/chain/validators.js +2 -0
  26. package/lib/context-builder.d.ts +20 -0
  27. package/lib/context-builder.js +52 -0
  28. package/lib/context-items/bail.d.ts +5 -0
  29. package/lib/context-items/bail.js +13 -0
  30. package/lib/context-items/chain-condition.d.ts +9 -0
  31. package/lib/context-items/chain-condition.js +16 -0
  32. package/lib/context-items/context-item.d.ts +5 -0
  33. package/lib/context-items/context-item.js +2 -0
  34. package/lib/context-items/custom-condition.d.ts +8 -0
  35. package/lib/context-items/custom-condition.js +24 -0
  36. package/lib/context-items/custom-validation.d.ts +10 -0
  37. package/lib/context-items/custom-validation.js +34 -0
  38. package/lib/context-items/index.d.ts +5 -0
  39. package/lib/context-items/index.js +21 -0
  40. package/lib/context-items/sanitization.d.ts +12 -0
  41. package/lib/context-items/sanitization.js +34 -0
  42. package/lib/context-items/standard-validation.d.ts +13 -0
  43. package/lib/context-items/standard-validation.js +25 -0
  44. package/lib/context.d.ts +61 -0
  45. package/lib/context.js +117 -0
  46. package/lib/express-validator.d.ts +153 -0
  47. package/lib/express-validator.js +125 -0
  48. package/lib/field-selection.d.ts +22 -0
  49. package/lib/field-selection.js +221 -0
  50. package/lib/index.d.ts +9 -0
  51. package/lib/index.js +25 -0
  52. package/lib/matched-data.d.ts +26 -0
  53. package/lib/matched-data.js +45 -0
  54. package/lib/middlewares/check.d.ts +3 -0
  55. package/lib/middlewares/check.js +23 -0
  56. package/lib/middlewares/exact.d.ts +29 -0
  57. package/lib/middlewares/exact.js +68 -0
  58. package/lib/middlewares/one-of.d.ts +28 -0
  59. package/lib/middlewares/one-of.js +91 -0
  60. package/lib/middlewares/schema.d.ts +108 -0
  61. package/lib/middlewares/schema.js +112 -0
  62. package/lib/middlewares/validation-chain-builders.d.ts +43 -0
  63. package/lib/middlewares/validation-chain-builders.js +49 -0
  64. package/lib/options.d.ts +377 -0
  65. package/lib/options.js +2 -0
  66. package/lib/utils.d.ts +12 -0
  67. package/lib/utils.js +56 -0
  68. package/lib/validation-result.d.ts +67 -0
  69. package/lib/validation-result.js +80 -0
  70. package/package.json +97 -0
package/lib/context.js ADDED
@@ -0,0 +1,117 @@
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;
@@ -0,0 +1,153 @@
1
+ import { CustomSanitizer, CustomValidator, ErrorMessage, FieldMessageFactory, Location, Middleware, Request, ValidationError } from './base';
2
+ import { ContextRunner, ValidationChain } from './chain';
3
+ import { MatchedDataOptions } from './matched-data';
4
+ import { checkExact } from './middlewares/exact';
5
+ import { OneOfOptions } from './middlewares/one-of';
6
+ import { DefaultSchemaKeys, ExtensionSanitizerSchemaOptions, ExtensionValidatorSchemaOptions, ParamSchema, RunnableValidationChains } from './middlewares/schema';
7
+ import { ErrorFormatter, Result } from './validation-result';
8
+ type CustomValidatorsMap = Record<string, CustomValidator>;
9
+ type CustomSanitizersMap = Record<string, CustomSanitizer>;
10
+ type CustomOptions<E = ValidationError> = {
11
+ errorFormatter?: ErrorFormatter<E>;
12
+ };
13
+ /**
14
+ * A validation chain that contains some extension validators/sanitizers.
15
+ *
16
+ * Built-in methods return the same chain type so that chaining using more of the extensions is
17
+ * possible.
18
+ *
19
+ * @example
20
+ * ```
21
+ * function createChain(chain: ValidationChainWithExtensions<'isAllowedDomain' | 'removeEmailAttribute'>) {
22
+ * return chain
23
+ * .isEmail()
24
+ * .isAllowedDomain()
25
+ * .trim()
26
+ * .removeEmailAttribute();
27
+ * }
28
+ * ```
29
+ */
30
+ export type ValidationChainWithExtensions<T extends string> = Middleware & {
31
+ [K in keyof ValidationChain]: ValidationChain[K] extends (...args: infer A) => ValidationChain ? (...params: A) => ValidationChainWithExtensions<T> : ValidationChain[K];
32
+ } & {
33
+ [K in T]: () => ValidationChainWithExtensions<T>;
34
+ };
35
+ /**
36
+ * Schema of validations/sanitizations for a field, including extension validators/sanitizers
37
+ */
38
+ export type ParamSchemaWithExtensions<V extends string, S extends string, T extends string = DefaultSchemaKeys> = {
39
+ [K in keyof ParamSchema<T> | V | S]?: K extends V ? ExtensionValidatorSchemaOptions : K extends S ? ExtensionSanitizerSchemaOptions : K extends keyof ParamSchema<T> ? ParamSchema<T>[K] : never;
40
+ };
41
+ /**
42
+ * Type of a validation chain created by a custom ExpressValidator instance.
43
+ *
44
+ * @example
45
+ * ```
46
+ * const myExpressValidator = new ExpressValidator({
47
+ * isAllowedDomain: value => value.endsWith('@gmail.com')
48
+ * });
49
+ *
50
+ * type MyCustomValidationChain = CustomValidationChain<typeof myExpressValidator>
51
+ * function createMyCustomChain(): MyCustomValidationChain {
52
+ * return myExpressValidator.body('email').isAllowedDomain();
53
+ * }
54
+ * ```
55
+ */
56
+ export type CustomValidationChain<T extends ExpressValidator<any, any, any>> = T extends ExpressValidator<infer V, infer S, any> ? ValidationChainWithExtensions<Extract<keyof V | keyof S, string>> : never;
57
+ /**
58
+ * Mapping from field name to a validations/sanitizations schema, including extensions from an
59
+ * ExpressValidator instance.
60
+ */
61
+ export type CustomSchema<T extends ExpressValidator<any, any, any>, K extends string = DefaultSchemaKeys> = T extends ExpressValidator<infer V, infer S, any> ? Record<string, ParamSchemaWithExtensions<Extract<keyof V, string>, Extract<keyof S, string>, K>> : never;
62
+ export declare class ExpressValidator<V extends CustomValidatorsMap = {}, S extends CustomSanitizersMap = {}, E = ValidationError> {
63
+ private readonly validators?;
64
+ private readonly sanitizers?;
65
+ private readonly options?;
66
+ private readonly validatorEntries;
67
+ private readonly sanitizerEntries;
68
+ constructor(validators?: V | undefined, sanitizers?: S | undefined, options?: CustomOptions<E> | undefined);
69
+ private createChain;
70
+ buildCheckFunction(locations: Location[]): (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
71
+ /**
72
+ * Creates a middleware/validation chain for one or more fields that may be located in
73
+ * any of the following:
74
+ *
75
+ * - `req.body`
76
+ * - `req.cookies`
77
+ * - `req.headers`
78
+ * - `req.params`
79
+ * - `req.query`
80
+ *
81
+ * @param fields a string or array of field names to validate/sanitize
82
+ * @param message an error message to use when failed validations don't specify a custom message.
83
+ * Defaults to `Invalid Value`.
84
+ */
85
+ readonly check: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
86
+ /**
87
+ * Same as {@link ExpressValidator.check}, but only validates in `req.body`.
88
+ */
89
+ readonly body: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
90
+ /**
91
+ * Same as {@link ExpressValidator.check}, but only validates in `req.cookies`.
92
+ */
93
+ readonly cookie: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
94
+ /**
95
+ * Same as {@link ExpressValidator.check}, but only validates in `req.headers`.
96
+ */
97
+ readonly header: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
98
+ /**
99
+ * Same as {@link ExpressValidator.check}, but only validates in `req.params`.
100
+ */
101
+ readonly param: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
102
+ /**
103
+ * Same as {@link ExpressValidator.check}, but only validates in `req.query`.
104
+ */
105
+ readonly query: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
106
+ /**
107
+ * Checks whether the request contains exactly only those fields that have been validated.
108
+ *
109
+ * This method is here for convenience; it does exactly the same as `checkExact`.
110
+ *
111
+ * @see {@link checkExact}
112
+ */
113
+ readonly checkExact: typeof checkExact;
114
+ /**
115
+ * Creates an express middleware with validations for multiple fields at once in the form of
116
+ * a schema object.
117
+ *
118
+ * @param schema the schema to validate.
119
+ * @param defaultLocations which locations to validate in each field. Defaults to every location.
120
+ */
121
+ readonly checkSchema: <T extends string = DefaultSchemaKeys>(schema: CustomSchema<this, T>, locations?: Location[]) => RunnableValidationChains<CustomValidationChain<this>>;
122
+ /**
123
+ * Creates a middleware that will ensure that at least one of the given validation chains
124
+ * or validation chain groups are valid.
125
+ *
126
+ * If none are, a single error of type `alternative` is added to the request,
127
+ * with the errors of each chain made available under the `nestedErrors` property.
128
+ *
129
+ * @param chains an array of validation chains to check if are valid.
130
+ * If any of the items of `chains` is an array of validation chains, then all of them
131
+ * must be valid together for the request to be considered valid.
132
+ */
133
+ oneOf(chains: (CustomValidationChain<this> | CustomValidationChain<this>[])[], options?: OneOfOptions): Middleware & ContextRunner;
134
+ /**
135
+ * Extracts the validation errors of an express request using the default error formatter of this
136
+ * instance.
137
+ *
138
+ * @see {@link validationResult()}
139
+ * @param req the express request object
140
+ * @returns a `Result` which will by default use the error formatter passed when
141
+ * instantiating `ExpressValidator`.
142
+ */
143
+ readonly validationResult: (req: Request) => Result<E>;
144
+ /**
145
+ * Extracts data validated or sanitized from the request, and builds an object with them.
146
+ *
147
+ * This method is a shortcut for `matchedData`; it does nothing different than it.
148
+ *
149
+ * @see {@link matchedData}
150
+ */
151
+ matchedData(req: Request, options?: Partial<MatchedDataOptions>): Record<string, any>;
152
+ }
153
+ export {};
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExpressValidator = void 0;
4
+ const matched_data_1 = require("./matched-data");
5
+ const check_1 = require("./middlewares/check");
6
+ const exact_1 = require("./middlewares/exact");
7
+ const one_of_1 = require("./middlewares/one-of");
8
+ const schema_1 = require("./middlewares/schema");
9
+ const validation_result_1 = require("./validation-result");
10
+ /* eslint-enable no-use-before-define */
11
+ class ExpressValidator {
12
+ constructor(validators, sanitizers, options) {
13
+ this.validators = validators;
14
+ this.sanitizers = sanitizers;
15
+ this.options = options;
16
+ /**
17
+ * Creates a middleware/validation chain for one or more fields that may be located in
18
+ * any of the following:
19
+ *
20
+ * - `req.body`
21
+ * - `req.cookies`
22
+ * - `req.headers`
23
+ * - `req.params`
24
+ * - `req.query`
25
+ *
26
+ * @param fields a string or array of field names to validate/sanitize
27
+ * @param message an error message to use when failed validations don't specify a custom message.
28
+ * Defaults to `Invalid Value`.
29
+ */
30
+ this.check = this.buildCheckFunction(['body', 'cookies', 'headers', 'params', 'query']);
31
+ /**
32
+ * Same as {@link ExpressValidator.check}, but only validates in `req.body`.
33
+ */
34
+ this.body = this.buildCheckFunction(['body']);
35
+ /**
36
+ * Same as {@link ExpressValidator.check}, but only validates in `req.cookies`.
37
+ */
38
+ this.cookie = this.buildCheckFunction(['cookies']);
39
+ /**
40
+ * Same as {@link ExpressValidator.check}, but only validates in `req.headers`.
41
+ */
42
+ this.header = this.buildCheckFunction(['headers']);
43
+ /**
44
+ * Same as {@link ExpressValidator.check}, but only validates in `req.params`.
45
+ */
46
+ this.param = this.buildCheckFunction(['params']);
47
+ /**
48
+ * Same as {@link ExpressValidator.check}, but only validates in `req.query`.
49
+ */
50
+ this.query = this.buildCheckFunction(['query']);
51
+ /**
52
+ * Checks whether the request contains exactly only those fields that have been validated.
53
+ *
54
+ * This method is here for convenience; it does exactly the same as `checkExact`.
55
+ *
56
+ * @see {@link checkExact}
57
+ */
58
+ this.checkExact = exact_1.checkExact;
59
+ /**
60
+ * Creates an express middleware with validations for multiple fields at once in the form of
61
+ * a schema object.
62
+ *
63
+ * @param schema the schema to validate.
64
+ * @param defaultLocations which locations to validate in each field. Defaults to every location.
65
+ */
66
+ // NOTE: This method references its own type, so the type cast is necessary.
67
+ this.checkSchema = (0, schema_1.createCheckSchema)((...args) => this.createChain(...args), Object.keys(this.validators || {}), Object.keys(this.sanitizers || {}));
68
+ /**
69
+ * Extracts the validation errors of an express request using the default error formatter of this
70
+ * instance.
71
+ *
72
+ * @see {@link validationResult()}
73
+ * @param req the express request object
74
+ * @returns a `Result` which will by default use the error formatter passed when
75
+ * instantiating `ExpressValidator`.
76
+ */
77
+ this.validationResult = (req) => {
78
+ const formatter = this.options?.errorFormatter;
79
+ const result = (0, validation_result_1.validationResult)(req);
80
+ return formatter ? result.formatWith(formatter) : result;
81
+ };
82
+ this.validatorEntries = Object.entries(validators || {});
83
+ this.sanitizerEntries = Object.entries(sanitizers || {});
84
+ // Can't use arrow function in the declaration of `buildCheckFunction` due to the following
85
+ // error which only happens when tests are run without Jest cache (so CI only):
86
+ //
87
+ // 'buildCheckFunction' implicitly has type 'any' because it does not have a type annotation
88
+ // and is referenced directly or indirectly in its own initializer
89
+ this.buildCheckFunction = this.buildCheckFunction.bind(this);
90
+ }
91
+ createChain(fields = '', locations = [], message) {
92
+ const middleware = (0, check_1.check)(fields, locations, message);
93
+ const boundValidators = Object.fromEntries(this.validatorEntries.map(([name, fn]) => [name, () => middleware.custom(fn)]));
94
+ const boundSanitizers = Object.fromEntries(this.sanitizerEntries.map(([name, fn]) => [name, () => middleware.customSanitizer(fn)]));
95
+ return Object.assign(middleware, boundValidators, boundSanitizers);
96
+ }
97
+ buildCheckFunction(locations) {
98
+ return (fields, message) => this.createChain(fields, locations, message);
99
+ }
100
+ /**
101
+ * Creates a middleware that will ensure that at least one of the given validation chains
102
+ * or validation chain groups are valid.
103
+ *
104
+ * If none are, a single error of type `alternative` is added to the request,
105
+ * with the errors of each chain made available under the `nestedErrors` property.
106
+ *
107
+ * @param chains an array of validation chains to check if are valid.
108
+ * If any of the items of `chains` is an array of validation chains, then all of them
109
+ * must be valid together for the request to be considered valid.
110
+ */
111
+ oneOf(chains, options) {
112
+ return (0, one_of_1.oneOf)(chains, options);
113
+ }
114
+ /**
115
+ * Extracts data validated or sanitized from the request, and builds an object with them.
116
+ *
117
+ * This method is a shortcut for `matchedData`; it does nothing different than it.
118
+ *
119
+ * @see {@link matchedData}
120
+ */
121
+ matchedData(req, options) {
122
+ return (0, matched_data_1.matchedData)(req, options);
123
+ }
124
+ }
125
+ exports.ExpressValidator = ExpressValidator;
@@ -0,0 +1,22 @@
1
+ import { FieldInstance, Location, Request, UnknownFieldInstance } from './base';
2
+ export type SelectFields = (req: Request, fields: string[], locations: Location[]) => FieldInstance[];
3
+ export declare const selectFields: SelectFields;
4
+ export declare const selectUnknownFields: (req: Request, knownFields: string[], locations: Location[]) => UnknownFieldInstance[];
5
+ /**
6
+ * Reconstructs a field path from a list of path segments.
7
+ *
8
+ * Most segments will be concatenated by a dot, for example `['foo', 'bar']` becomes `foo.bar`.
9
+ * However, a numeric segment will be wrapped in brackets to match regular JS array syntax:
10
+ *
11
+ * ```
12
+ * reconstructFieldPath(['foo', 0, 'bar']) // foo[0].bar
13
+ * ```
14
+ *
15
+ * Segments which have a special character such as `.` will be wrapped in brackets and quotes,
16
+ * which also matches JS syntax for objects with such keys.
17
+ *
18
+ * ```
19
+ * reconstructFieldPath(['foo', 'bar.baz', 'qux']) // foo["bar.baz"].qux
20
+ * ```
21
+ */
22
+ export declare function reconstructFieldPath(segments: readonly string[]): string;
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectUnknownFields = exports.selectFields = void 0;
4
+ exports.reconstructFieldPath = reconstructFieldPath;
5
+ const _ = require("lodash");
6
+ const selectFields = (req, fields, locations) => _(fields)
7
+ .flatMap(field => _.flatMap(locations, location => {
8
+ return expandField(req, field, location);
9
+ }))
10
+ // Avoid duplicates if multiple field selections would return the same field twice.
11
+ // E.g. with fields = ['*.foo', 'bar.foo'] and req.body = { bar: { foo: 1 }, baz: { foo: 2 } },
12
+ // the instance bla.foo would appear twice, and baz.foo once.
13
+ .uniqWith(isSameFieldInstance)
14
+ .value();
15
+ exports.selectFields = selectFields;
16
+ function isSameFieldInstance(a, b) {
17
+ return a.path === b.path && a.location === b.location;
18
+ }
19
+ function expandField(req, field, location) {
20
+ const originalPath = field;
21
+ const pathToExpand = location === 'headers' ? field.toLowerCase() : field;
22
+ const paths = expandPath(req[location], pathToExpand, []);
23
+ return paths.map(({ path, values }) => {
24
+ const value = path === '' ? req[location] : _.get(req[location], path);
25
+ return {
26
+ location,
27
+ path,
28
+ originalPath,
29
+ pathValues: values,
30
+ value,
31
+ };
32
+ });
33
+ }
34
+ function expandPath(object, path, currPath, currValues = []) {
35
+ const segments = _.toPath(path);
36
+ if (!segments.length) {
37
+ // no more paths to traverse
38
+ return [
39
+ {
40
+ path: reconstructFieldPath(currPath),
41
+ values: currValues,
42
+ },
43
+ ];
44
+ }
45
+ const key = segments[0];
46
+ const rest = segments.slice(1);
47
+ if (object != null && !_.isObjectLike(object)) {
48
+ if (key === '**') {
49
+ if (!rest.length) {
50
+ // globstar leaves are always selected
51
+ return [
52
+ {
53
+ path: reconstructFieldPath(currPath),
54
+ values: currValues,
55
+ },
56
+ ];
57
+ }
58
+ return [];
59
+ }
60
+ if (key === '*') {
61
+ // wildcard position does not exist
62
+ return [];
63
+ }
64
+ // value is a primitive, paths being traversed from here might be in their prototype, return the entire path
65
+ return [
66
+ {
67
+ path: reconstructFieldPath([...currPath, ...segments]),
68
+ values: currValues,
69
+ },
70
+ ];
71
+ }
72
+ // Use a non-null value so that inexistent fields are still selected
73
+ object = object || {};
74
+ if (key === '*') {
75
+ return Object.keys(object).flatMap(key => expandPath(object[key], rest, currPath.concat(key), currValues.concat(key)));
76
+ }
77
+ if (key === '**') {
78
+ return Object.keys(object).flatMap(key => {
79
+ const nextPath = currPath.concat(key);
80
+ const value = object[key];
81
+ // recursively find matching subpaths
82
+ const selectedPaths = expandPath(value, segments, nextPath, [key]).concat(
83
+ // skip the first remaining segment, if it matches the current key
84
+ rest[0] === key ? expandPath(value, rest.slice(1), nextPath, []) : []);
85
+ return _.uniqBy(selectedPaths, ({ path }) => path).map(({ path, values }) => ({
86
+ path,
87
+ values: values.length ? [...currValues, values.flat()] : currValues,
88
+ }));
89
+ });
90
+ }
91
+ return expandPath(object[key], rest, currPath.concat(key), currValues);
92
+ }
93
+ const selectUnknownFields = (req, knownFields, locations) => {
94
+ const tree = {};
95
+ knownFields.forEach(field => {
96
+ const segments = field === '' ? [''] : _.toPath(field);
97
+ pathToTree(segments, tree);
98
+ });
99
+ const instances = [];
100
+ for (const location of locations) {
101
+ if (req[location] != null) {
102
+ instances.push(...findUnknownFields(location, req[location], tree));
103
+ }
104
+ }
105
+ return instances;
106
+ };
107
+ exports.selectUnknownFields = selectUnknownFields;
108
+ function pathToTree(segments, tree) {
109
+ // Will either create or merge into existing branch for the current path segment
110
+ const branch = tree[segments[0]] || (tree[segments[0]] = {});
111
+ if (segments.length > 1) {
112
+ pathToTree(segments.slice(1), branch);
113
+ }
114
+ else {
115
+ // Leaf value.
116
+ branch[''] = {};
117
+ }
118
+ }
119
+ /**
120
+ * Performs a depth-first search for unknown fields in `value`.
121
+ * The path to the unknown fields will be pushed to the `unknownFields` argument.
122
+ *
123
+ * Known fields must be passed via `tree`. A field won't be considered unknown if:
124
+ * - its branch is validated as a whole; that is, it contains an empty string key (e.g `{ ['']: {} }`); OR
125
+ * - its path is individually validated; OR
126
+ * - it's covered by a wildcard (`*`).
127
+ *
128
+ * @returns the list of unknown fields
129
+ */
130
+ function findUnknownFields(location, value, tree, treePath = [], unknownFields = []) {
131
+ const globstarBranch = tree['**'];
132
+ if (tree[''] || globstarBranch?.['']) {
133
+ // The rest of the tree from here is covered by some validation chain
134
+ // For example, when the current treePath is `['foo', 'bar']` but `foo` is known
135
+ return unknownFields;
136
+ }
137
+ if (typeof value !== 'object') {
138
+ if (!treePath.length || globstarBranch) {
139
+ // This is either
140
+ // a. a req.body that isn't an object (e.g. `req.body = 'bla'`), and wasn't validated either
141
+ // b. a leaf value which wasn't the target of a globstar path, e.g. `foo.**.bar`
142
+ unknownFields.push({
143
+ path: reconstructFieldPath(treePath),
144
+ value,
145
+ location,
146
+ });
147
+ }
148
+ return unknownFields;
149
+ }
150
+ const wildcardBranch = tree['*'];
151
+ for (const key of Object.keys(value)) {
152
+ const keyBranch = tree[key];
153
+ const path = treePath.concat([key]);
154
+ if (!keyBranch && !wildcardBranch && !globstarBranch) {
155
+ // No trees cover this path, so it's an unknown one.
156
+ unknownFields.push({
157
+ path: reconstructFieldPath(path),
158
+ value: value[key],
159
+ location,
160
+ });
161
+ continue;
162
+ }
163
+ const keyUnknowns = keyBranch ? findUnknownFields(location, value[key], keyBranch, path) : [];
164
+ const wildcardUnknowns = wildcardBranch
165
+ ? findUnknownFields(location, value[key], wildcardBranch, path)
166
+ : [];
167
+ const globstarUnknowns = globstarBranch
168
+ ? findUnknownFields(location, value[key], { ['**']: globstarBranch, ...globstarBranch }, path)
169
+ : [];
170
+ // If any of the tested branches contain only known fields, then don't mark the fields not covered
171
+ // by the other branches to the list of unknown ones.
172
+ // For example, `foo` is more comprehensive than `foo.*.bar`.
173
+ if ((!keyBranch || keyUnknowns.length) &&
174
+ (!wildcardBranch || wildcardUnknowns.length) &&
175
+ (!globstarBranch || globstarUnknowns.length)) {
176
+ unknownFields.push(...keyUnknowns, ...wildcardUnknowns, ...globstarUnknowns);
177
+ }
178
+ }
179
+ return unknownFields;
180
+ }
181
+ /**
182
+ * Reconstructs a field path from a list of path segments.
183
+ *
184
+ * Most segments will be concatenated by a dot, for example `['foo', 'bar']` becomes `foo.bar`.
185
+ * However, a numeric segment will be wrapped in brackets to match regular JS array syntax:
186
+ *
187
+ * ```
188
+ * reconstructFieldPath(['foo', 0, 'bar']) // foo[0].bar
189
+ * ```
190
+ *
191
+ * Segments which have a special character such as `.` will be wrapped in brackets and quotes,
192
+ * which also matches JS syntax for objects with such keys.
193
+ *
194
+ * ```
195
+ * reconstructFieldPath(['foo', 'bar.baz', 'qux']) // foo["bar.baz"].qux
196
+ * ```
197
+ */
198
+ function reconstructFieldPath(segments) {
199
+ return segments.reduce((prev, segment) => {
200
+ let part = '';
201
+ segment = segment === '\\*' ? '*' : segment;
202
+ // TODO: Handle brackets?
203
+ if (segment.includes('.')) {
204
+ // Special char key access
205
+ part = `["${segment}"]`;
206
+ }
207
+ else if (/^\d+$/.test(segment)) {
208
+ // Index access
209
+ part = `[${segment}]`;
210
+ }
211
+ else if (prev) {
212
+ // Object key access
213
+ part = `.${segment}`;
214
+ }
215
+ else {
216
+ // Top level key
217
+ part = segment;
218
+ }
219
+ return prev + part;
220
+ }, '');
221
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { Location, Meta, CustomValidator, CustomSanitizer, AlternativeMessageFactory, FieldMessageFactory, GroupedAlternativeMessageFactory, UnknownFieldMessageFactory, FieldValidationError, AlternativeValidationError, GroupedAlternativeValidationError, UnknownFieldsError, ValidationError, } from './base';
2
+ export { ContextRunner, ValidationChain } from './chain';
3
+ export * from './middlewares/exact';
4
+ export * from './middlewares/one-of';
5
+ export * from './middlewares/validation-chain-builders';
6
+ export { checkSchema, Schema, ParamSchema } from './middlewares/schema';
7
+ export * from './matched-data';
8
+ export * from './validation-result';
9
+ export * from './express-validator';
package/lib/index.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.checkSchema = void 0;
18
+ __exportStar(require("./middlewares/exact"), exports);
19
+ __exportStar(require("./middlewares/one-of"), exports);
20
+ __exportStar(require("./middlewares/validation-chain-builders"), exports);
21
+ var schema_1 = require("./middlewares/schema");
22
+ Object.defineProperty(exports, "checkSchema", { enumerable: true, get: function () { return schema_1.checkSchema; } });
23
+ __exportStar(require("./matched-data"), exports);
24
+ __exportStar(require("./validation-result"), exports);
25
+ __exportStar(require("./express-validator"), exports);