@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.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/changes.json +14 -0
- package/lib/base.d.ts +187 -0
- package/lib/base.js +8 -0
- package/lib/chain/context-handler-impl.d.ts +13 -0
- package/lib/chain/context-handler-impl.js +52 -0
- package/lib/chain/context-handler.d.ts +110 -0
- package/lib/chain/context-handler.js +2 -0
- package/lib/chain/context-runner-impl.d.ts +18 -0
- package/lib/chain/context-runner-impl.js +72 -0
- package/lib/chain/context-runner.d.ts +22 -0
- package/lib/chain/context-runner.js +2 -0
- package/lib/chain/index.d.ts +9 -0
- package/lib/chain/index.js +25 -0
- package/lib/chain/sanitizers-impl.d.ts +29 -0
- package/lib/chain/sanitizers-impl.js +80 -0
- package/lib/chain/sanitizers.d.ts +42 -0
- package/lib/chain/sanitizers.js +2 -0
- package/lib/chain/validation-chain.d.ts +18 -0
- package/lib/chain/validation-chain.js +2 -0
- package/lib/chain/validators-impl.d.ts +121 -0
- package/lib/chain/validators-impl.js +353 -0
- package/lib/chain/validators.d.ts +181 -0
- package/lib/chain/validators.js +2 -0
- package/lib/context-builder.d.ts +20 -0
- package/lib/context-builder.js +52 -0
- package/lib/context-items/bail.d.ts +5 -0
- package/lib/context-items/bail.js +13 -0
- package/lib/context-items/chain-condition.d.ts +9 -0
- package/lib/context-items/chain-condition.js +16 -0
- package/lib/context-items/context-item.d.ts +5 -0
- package/lib/context-items/context-item.js +2 -0
- package/lib/context-items/custom-condition.d.ts +8 -0
- package/lib/context-items/custom-condition.js +24 -0
- package/lib/context-items/custom-validation.d.ts +10 -0
- package/lib/context-items/custom-validation.js +34 -0
- package/lib/context-items/index.d.ts +5 -0
- package/lib/context-items/index.js +21 -0
- package/lib/context-items/sanitization.d.ts +12 -0
- package/lib/context-items/sanitization.js +34 -0
- package/lib/context-items/standard-validation.d.ts +13 -0
- package/lib/context-items/standard-validation.js +25 -0
- package/lib/context.d.ts +61 -0
- package/lib/context.js +117 -0
- package/lib/express-validator.d.ts +153 -0
- package/lib/express-validator.js +125 -0
- package/lib/field-selection.d.ts +22 -0
- package/lib/field-selection.js +221 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +25 -0
- package/lib/matched-data.d.ts +26 -0
- package/lib/matched-data.js +45 -0
- package/lib/middlewares/check.d.ts +3 -0
- package/lib/middlewares/check.js +23 -0
- package/lib/middlewares/exact.d.ts +29 -0
- package/lib/middlewares/exact.js +68 -0
- package/lib/middlewares/one-of.d.ts +28 -0
- package/lib/middlewares/one-of.js +91 -0
- package/lib/middlewares/schema.d.ts +108 -0
- package/lib/middlewares/schema.js +112 -0
- package/lib/middlewares/validation-chain-builders.d.ts +43 -0
- package/lib/middlewares/validation-chain-builders.js +49 -0
- package/lib/options.d.ts +377 -0
- package/lib/options.js +2 -0
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +56 -0
- package/lib/validation-result.d.ts +67 -0
- package/lib/validation-result.js +80 -0
- 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);
|