Change endpoint from persons to people

This commit is contained in:
xfarrow
2025-03-23 21:00:08 +01:00
parent 4ae263662c
commit d005193f63
7158 changed files with 700476 additions and 735 deletions

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010 Chris O'Hara <cohara87@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,33 @@
# express-validator
[![npm version](https://img.shields.io/npm/v/express-validator.svg)](https://www.npmjs.com/package/express-validator)
[![Build status](https://github.com/express-validator/express-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/express-validator/express-validator/actions/workflows/ci.yml)
[![Coverage Status](https://img.shields.io/coveralls/express-validator/express-validator.svg)](https://coveralls.io/github/express-validator/express-validator?branch=master)
An [express.js](https://github.com/visionmedia/express) middleware for
[validator](https://github.com/validatorjs/validator.js).
- [Installation](#installation)
- [Documentation](#documentation)
- [Changelog](#changelog)
- [License](#license)
## Installation
```
npm install express-validator
```
Also make sure that you have Node.js 14 or newer in order to use it.
## Documentation
Please refer to the documentation website on https://express-validator.github.io.
## Changelog
Check the [GitHub Releases page](https://github.com/express-validator/express-validator/releases).
## License
MIT License

View File

@ -0,0 +1,187 @@
import { ReadonlyContext } from './context';
export interface Request {
[k: string]: any;
body?: any;
cookies?: Record<string, any>;
headers?: Record<string, any>;
params?: Record<string, any>;
query?: Record<string, any>;
}
export type Middleware = (req: Request, res: any, next: (err?: any) => void) => void;
export type Location = 'body' | 'cookies' | 'headers' | 'params' | 'query';
/**
* Metadata about a validated field.
*/
export type Meta = {
/**
* The express request from which the field was validated
*/
req: Request;
/**
* Which of the request objects the field was picked from
*/
location: Location;
/**
* The full path of the field within the request object.
*
* @example
* const meta = { req, location: 'body', path: 'foo.bar' }; // req.body.foo.bar
*/
path: string;
/**
* Values from wildcards/globstars used when selecting fields for validation.
*
* @example
* body('products.*.price').custom((value, { req, pathValues }) => {
* const index = pathValues[0];
* const productName = req.body.products[index].name;
* });
*/
pathValues: readonly (string | string[])[];
};
/**
* A function which may
* - return falsy values, a promise that rejects or throw to indicate that a field is invalid;
* - return truthy values or a promise that resolves to indicate that a field is valid.
*
* @param input the field value
* @param meta metadata about the field being validated
*/
export type CustomValidator = (input: any, meta: Meta) => any;
export type StandardValidator = (input: string, ...options: any[]) => boolean;
export type CustomSanitizer = (input: any, meta: Meta) => any;
export type StandardSanitizer = (input: string, ...options: any[]) => any;
export interface FieldInstance {
path: string;
originalPath: string;
pathValues: readonly (string | string[])[];
location: Location;
value: any;
}
export type UnknownFieldInstance = Omit<FieldInstance, 'originalPath' | 'pathValues'>;
export type FieldValidationError = {
/**
* Indicates that the error occurred because a field had an invalid value
*/
type: 'field';
/**
* The location within the request where this field is
*/
location: Location;
/**
* The path to the field which has a validation error
*/
path: string;
/**
* The value of the field. It might be unset if the value is hidden.
*/
value?: any;
/**
* The error message
*/
msg: any;
};
export type UnknownFieldsError = {
/**
* Indicates that the error occurred because one or more fields are unknown in the request
*/
type: 'unknown_fields';
/**
* The error message
*/
msg: any;
/**
* The list of fields that are unknown
*/
fields: UnknownFieldInstance[];
};
export type AlternativeValidationError = {
/**
* Indicates that the error occurred because all alternatives (e.g. in `oneOf()`) were invalid
*/
type: 'alternative';
/**
* The error message
*/
msg: any;
/**
* The list of underlying validation errors returned by validation chains in `oneOf()`
*/
nestedErrors: FieldValidationError[];
};
export type GroupedAlternativeValidationError = {
/**
* Indicates that the error occurred because all alternatives (e.g. in `oneOf()`) were invalid,
* and the nested errors are grouped per alternative.
*/
type: 'alternative_grouped';
/**
* The error message
*/
msg: any;
/**
* The list of underlying validation errors returned by validation chains in `oneOf()`
*/
nestedErrors: FieldValidationError[][];
};
/**
* A validation error as reported by a middleware.
* The properties available in the error object vary according to the type.
*
* @example
* if (error.type === 'alternative') {
* console.log(`There are ${error.nestedErrors.length} errors under this alternative list`);
* } else if (error.type === 'field') {
* console.log(`There's an error with field ${error.path} in the request ${error.location}`);
* }
*
*/
export type ValidationError = AlternativeValidationError | GroupedAlternativeValidationError | UnknownFieldsError | FieldValidationError;
/**
* An error message that's not a function, as these are treated as message factories
* by all validation middlewares.
*/
export type ErrorMessage = string | number | symbol | boolean | object | any[];
/**
* A function which creates an error message based on a field's value.
*
* @param input the field value
* @param meta metadata about the field that was validated
*/
export type FieldMessageFactory = (value: any, meta: Meta) => any;
/**
* A function which creates an error message based on an alternative's nested errors.
*
* @see `oneOf()`
* @param nestedErrors The errors from the invalid alternative(s).
* @param opts
*/
export type AlternativeMessageFactory = (nestedErrors: FieldValidationError[], opts: {
req: Request;
}) => any;
/**
* A function which creates an error message based on a group of alternatives nested errors.
*
* @see `oneOf()`
* @param nestedErrors The errors from the invalid alternative groups.
* @param opts
*/
export type GroupedAlternativeMessageFactory = (nestedErrors: FieldValidationError[][], opts: {
req: Request;
}) => any;
/**
* A function which creates an error message based on unknown fields.
*
* @see `checkExact()`
* @param unknownFields The unknown fields found in the request
* @param opts
*/
export type UnknownFieldMessageFactory = (unknownFields: UnknownFieldInstance[], opts: {
req: Request;
}) => any;
export declare const contextsKey = "express-validator#contexts";
export interface InternalRequest extends Request {
[contextsKey]?: ReadonlyContext[];
}
export declare class ValidationHalt extends Error {
}

View File

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationHalt = exports.contextsKey = void 0;
// Not using Symbol because of #813
exports.contextsKey = 'express-validator#contexts';
class ValidationHalt extends Error {
}
exports.ValidationHalt = ValidationHalt;

View File

@ -0,0 +1,13 @@
import { ContextBuilder } from '../context-builder';
import { CustomValidator } from '../base';
import { BailOptions, ContextHandler, OptionalOptions } from './context-handler';
import { ContextRunner } from './context-runner';
export declare class ContextHandlerImpl<Chain> implements ContextHandler<Chain> {
private readonly builder;
private readonly chain;
constructor(builder: ContextBuilder, chain: Chain);
bail(opts?: BailOptions): Chain;
if(condition: CustomValidator | ContextRunner): Chain;
optional(options?: OptionalOptions | boolean): Chain;
hide(hiddenValue?: string): Chain;
}

View File

@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextHandlerImpl = void 0;
const context_items_1 = require("../context-items");
const bail_1 = require("../context-items/bail");
class ContextHandlerImpl {
constructor(builder, chain) {
this.builder = builder;
this.chain = chain;
}
bail(opts) {
if (opts?.level === 'request') {
this.builder.setRequestBail();
}
this.builder.addItem(new bail_1.Bail());
return this.chain;
}
if(condition) {
if ('run' in condition) {
this.builder.addItem(new context_items_1.ChainCondition(condition));
}
else if (typeof condition === 'function') {
this.builder.addItem(new context_items_1.CustomCondition(condition));
}
else {
throw new Error('express-validator: condition is not a validation chain nor a function');
}
return this.chain;
}
optional(options = true) {
let value;
if (typeof options === 'boolean') {
value = options ? 'undefined' : false;
}
else {
value = options.values
? options.values
: options.checkFalsy
? 'falsy'
: options.nullable
? 'null'
: 'undefined';
}
this.builder.setOptional(value);
return this.chain;
}
hide(hiddenValue) {
this.builder.setHidden(true, hiddenValue);
return this.chain;
}
}
exports.ContextHandlerImpl = ContextHandlerImpl;

View File

@ -0,0 +1,110 @@
import { CustomValidator } from '../base';
import { Optional } from '../context';
import { ContextRunner } from './context-runner';
export interface BailOptions {
/**
* Defines the level at which to stop running further validations:
* - When set to `chain`, further validations won't be run for this validation chain if there
* are any errors.
* - When set to `request`, no further validations on the same request will be run either if
* there are any errors.
*
* @default 'chain'
*/
level?: 'chain' | 'request';
}
export interface OptionalOptions {
/**
* Defines which kind of value makes a field optional.
*
* - `undefined`: only `undefined` values; equivalent to `value === undefined`
* - `null`: only `undefined` and `null` values; equivalent to `value == null`
* - `falsy`: all falsy values; equivalent to `!value`
*
* @default 'undefined'
*/
values?: Exclude<Optional, false>;
/**
* Whether a field whose value is `null` or `undefined` is to be considered optional.
* @default false
* @deprecated Use `values` instead.
*/
nullable?: boolean;
/**
* Whether a field whose value is falsy (that is, `0`, `false`, `null`, `undefined` or an empty
* string) is to be considered optional.
* @default false
* @deprecated Use `values` instead.
*/
checkFalsy?: boolean;
}
export interface ContextHandler<Chain> {
/**
* Stops running validations if any of the previous ones have failed.
*
* Useful to prevent a custom validator that touches a database or external API from running when
* you know it will fail.
*
* May be used multiple times in the same validation chain if desired.
*
* @example
* check('username')
* .isEmail()
* // If not an email, stop here
* .bail()
* .custom(checkDenylistDomain)
* // If domain is not allowed, don't go check if it already exists
* .bail()
* .custom(checkEmailExists)
*
* @returns the current validation chain
*/
bail(opts?: BailOptions): Chain;
/**
* Adds a condition on whether the validation should continue on a field or not.
* @param condition may be either
* - a custom validator-like function, which must truthy or a promise that resolves to continue
* validation. If the return value is falsy, a promise that rejects, or if it throws, validation
* will stop.
* - a validation chain which if it would produce errors, the validation chain stops.
* @example
* check('newPassword')
* // Only validate if the old password has been provided
* .if((value, { req }) => req.body.oldPassword)
* // Or, use it with a a validation chain
* .if(body('oldPassword').notEmpty())
* @returns the current validation chain
*/
if(condition: CustomValidator | ContextRunner): Chain;
/**
* Marks the field(s) of the validation chain as optional.
* By default, only fields with an `undefined` value are considered optional and will be ignored
* when validating.
*
* @param options an object of options to customize the behavior of optional.
* @returns the current validation chain
*/
optional(options?: {
values?: Optional;
/**
* @deprecated use `values` instead
*/
checkFalsy?: boolean;
/**
* @deprecated use `values` instead
*/
nullable?: boolean;
} | boolean): Chain;
/**
* Hide the field's value in errors returned by `validationResult()`.
*
* If the value is confidential information (such as api key),
* you might want to call this method to prevent exposing it.
*
* @param hiddenValue? String to replace the field's value with.
* If it's not set, the field value is removed from errors.
*
* @returns the current validation chain
*/
hide(hiddenValue?: string): Chain;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,18 @@
import { Request } from '../base';
import { Context, ReadonlyContext } from '../context';
import { ContextBuilder } from '../context-builder';
import { SelectFields } from '../field-selection';
import { Result } from '../validation-result';
import { ContextRunner, ResultWithContext } from './context-runner';
export declare class ResultWithContextImpl extends Result implements ResultWithContext {
readonly context: ReadonlyContext;
constructor(context: ReadonlyContext);
}
export declare class ContextRunnerImpl implements ContextRunner {
private readonly builderOrContext;
private readonly selectFields;
constructor(builderOrContext: ContextBuilder | Context, selectFields?: SelectFields);
run(req: Request, options?: {
dryRun?: boolean;
}): Promise<ResultWithContextImpl>;
}

View File

@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextRunnerImpl = exports.ResultWithContextImpl = void 0;
const _ = require("lodash");
const base_1 = require("../base");
const context_1 = require("../context");
const field_selection_1 = require("../field-selection");
const validation_result_1 = require("../validation-result");
class ResultWithContextImpl extends validation_result_1.Result {
constructor(context) {
super(error => error, context.errors);
this.context = context;
}
}
exports.ResultWithContextImpl = ResultWithContextImpl;
class ContextRunnerImpl {
constructor(builderOrContext, selectFields = field_selection_1.selectFields) {
this.builderOrContext = builderOrContext;
this.selectFields = selectFields;
}
async run(req, options = {}) {
const context = this.builderOrContext instanceof context_1.Context
? this.builderOrContext
: this.builderOrContext.build();
const internalReq = req;
const bail = internalReq[base_1.contextsKey]?.some(context => context.bail && context.errors.length > 0);
if (bail) {
return new ResultWithContextImpl(context);
}
const instances = this.selectFields(req, context.fields, context.locations);
context.addFieldInstances(instances);
const haltedInstances = new Set();
for (const contextItem of context.stack) {
const promises = context.getData({ requiredOnly: true }).map(async (instance) => {
const { location, path } = instance;
const instanceKey = `${location}:${path}`;
if (haltedInstances.has(instanceKey)) {
return;
}
try {
await contextItem.run(context, instance.value, {
req,
location,
path,
pathValues: instance.pathValues,
});
// An instance is mutable, so if an item changed its value, there's no need to call getData again
const newValue = instance.value;
// Checks whether the value changed.
// Avoids e.g. undefined values being set on the request if it didn't have the key initially.
const reqValue = path !== '' ? _.get(req[location], path) : req[location];
if (!options.dryRun && reqValue !== instance.value) {
path !== '' ? _.set(req[location], path, newValue) : _.set(req, location, newValue);
}
}
catch (e) {
if (e instanceof base_1.ValidationHalt) {
haltedInstances.add(instanceKey);
return;
}
throw e;
}
});
await Promise.all(promises);
}
if (!options.dryRun) {
internalReq[base_1.contextsKey] = (internalReq[base_1.contextsKey] || []).concat(context);
}
return new ResultWithContextImpl(context);
}
}
exports.ContextRunnerImpl = ContextRunnerImpl;

View File

@ -0,0 +1,22 @@
import { Request, ValidationError } from '../base';
import { ReadonlyContext } from '../context';
import { Result } from '../validation-result';
export type ContextRunningOptions = {
/**
* Defines whether errors and sanitization should be persisted to `req`.
* @default false
*/
dryRun?: boolean;
};
export interface ResultWithContext extends Result<ValidationError> {
readonly context: ReadonlyContext;
}
export interface ContextRunner {
/**
* Runs the current validation chain.
* @param req the express request to validate
* @param options an object of options to customize how the chain will be run
* @returns a promise for a {@link Result} that resolves when the validation chain has finished
*/
run(req: Request, options?: ContextRunningOptions): Promise<ResultWithContext>;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,9 @@
export * from './sanitizers';
export * from './sanitizers-impl';
export * from './context-handler';
export * from './context-handler-impl';
export * from './context-runner';
export * from './context-runner-impl';
export * from './validators';
export * from './validators-impl';
export * from './validation-chain';

View File

@ -0,0 +1,25 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./sanitizers"), exports);
__exportStar(require("./sanitizers-impl"), exports);
__exportStar(require("./context-handler"), exports);
__exportStar(require("./context-handler-impl"), exports);
__exportStar(require("./context-runner"), exports);
__exportStar(require("./context-runner-impl"), exports);
__exportStar(require("./validators"), exports);
__exportStar(require("./validators-impl"), exports);
__exportStar(require("./validation-chain"), exports);

View File

@ -0,0 +1,29 @@
import { CustomSanitizer } from '../base';
import { ContextBuilder } from '../context-builder';
import * as Options from '../options';
import { Sanitizers } from './sanitizers';
export declare class SanitizersImpl<Chain> implements Sanitizers<Chain> {
private readonly builder;
private readonly chain;
constructor(builder: ContextBuilder, chain: Chain);
customSanitizer(sanitizer: CustomSanitizer): Chain;
default(default_value: any): Chain;
replace(values_to_replace: any, new_value: any): Chain;
private addStandardSanitization;
blacklist(chars: string): Chain;
escape(): Chain;
unescape(): Chain;
ltrim(chars?: string): Chain;
normalizeEmail(options?: Options.NormalizeEmailOptions): Chain;
rtrim(chars?: string): Chain;
stripLow(keep_new_lines?: boolean): Chain;
toArray(): Chain;
toBoolean(strict?: boolean): Chain;
toDate(): Chain;
toFloat(): Chain;
toInt(radix?: number): Chain;
toLowerCase(): Chain;
toUpperCase(): Chain;
trim(chars?: string): Chain;
whitelist(chars: string): Chain;
}

View File

@ -0,0 +1,80 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SanitizersImpl = void 0;
const _ = require("lodash");
const validator = require("validator");
const sanitization_1 = require("../context-items/sanitization");
class SanitizersImpl {
constructor(builder, chain) {
this.builder = builder;
this.chain = chain;
}
// custom sanitizers
customSanitizer(sanitizer) {
this.builder.addItem(new sanitization_1.Sanitization(sanitizer, true));
return this.chain;
}
default(default_value) {
return this.customSanitizer(value => [undefined, null, NaN, ''].includes(value) ? _.cloneDeep(default_value) : value);
}
replace(values_to_replace, new_value) {
if (!Array.isArray(values_to_replace)) {
values_to_replace = [values_to_replace];
}
return this.customSanitizer(value => values_to_replace.includes(value) ? _.cloneDeep(new_value) : value);
}
// Standard sanitizers
addStandardSanitization(sanitizer, ...options) {
this.builder.addItem(new sanitization_1.Sanitization(sanitizer, false, options));
return this.chain;
}
blacklist(chars) {
return this.addStandardSanitization(validator.blacklist, chars);
}
escape() {
return this.addStandardSanitization(validator.escape);
}
unescape() {
return this.addStandardSanitization(validator.unescape);
}
ltrim(chars) {
return this.addStandardSanitization(validator.ltrim, chars);
}
normalizeEmail(options) {
return this.addStandardSanitization(validator.normalizeEmail, options);
}
rtrim(chars) {
return this.addStandardSanitization(validator.rtrim, chars);
}
stripLow(keep_new_lines) {
return this.addStandardSanitization(validator.stripLow, keep_new_lines);
}
toArray() {
return this.customSanitizer(value => (value !== undefined && ((Array.isArray(value) && value) || [value])) || []);
}
toBoolean(strict) {
return this.addStandardSanitization(validator.toBoolean, strict);
}
toDate() {
return this.addStandardSanitization(validator.toDate);
}
toFloat() {
return this.addStandardSanitization(validator.toFloat);
}
toInt(radix) {
return this.addStandardSanitization(validator.toInt, radix);
}
toLowerCase() {
return this.customSanitizer(value => (typeof value === 'string' ? value.toLowerCase() : value));
}
toUpperCase() {
return this.customSanitizer(value => (typeof value === 'string' ? value.toUpperCase() : value));
}
trim(chars) {
return this.addStandardSanitization(validator.trim, chars);
}
whitelist(chars) {
return this.addStandardSanitization(validator.whitelist, chars);
}
}
exports.SanitizersImpl = SanitizersImpl;

View File

@ -0,0 +1,42 @@
import { CustomSanitizer } from '../base';
import * as Options from '../options';
export interface Sanitizers<Return> {
/**
* Adds a custom sanitizer to the validation chain.
*
* @param sanitizer the custom sanitizer
* @returns the current validation chain
*/
customSanitizer(sanitizer: CustomSanitizer): Return;
/**
* Replaces the value of the field if it's one of `''`, `null`, `undefined` or `NaN`.
*
* @param default_value the value to replace with
* @returns the current validation chain
*/
default(default_value: any): Return;
/**
* Replaces a field's value with another value.
*
* @param values_to_replace one or more values that should be replaced
* @param new_value the value to replace with
* @returns the current validation chain
*/
replace(values_to_replace: any, new_value: any): Return;
blacklist(chars: string): Return;
escape(): Return;
unescape(): Return;
ltrim(chars?: string): Return;
normalizeEmail(options?: Options.NormalizeEmailOptions): Return;
rtrim(chars?: string): Return;
stripLow(keep_new_lines?: boolean): Return;
toArray(): Return;
toBoolean(strict?: boolean): Return;
toDate(): Return;
toFloat(): Return;
toInt(radix?: number): Return;
toLowerCase(): Return;
toUpperCase(): Return;
trim(chars?: string): Return;
whitelist(chars: string): Return;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,18 @@
import { Request } from '../base';
import { ContextBuilder } from '../context-builder';
import { Sanitizers } from './sanitizers';
import { Validators } from './validators';
import { ContextHandler } from './context-handler';
import { ContextRunner } from './context-runner';
export interface ValidationChain extends Validators<ValidationChain>, Sanitizers<ValidationChain>, ContextHandler<ValidationChain>, ContextRunner {
(req: Request, res: any, next: (error?: any) => void): void;
builder: ContextBuilder;
}
/**
* A copy of `ValidationChain` where methods that would return the chain itself can return any other
* value.
* Useful for typing functions which accept either standard or custom validation chains.
*/
export type ValidationChainLike = {
[K in keyof ValidationChain]: ValidationChain[K] extends (...args: infer A) => ValidationChain ? (...args: A) => any : ValidationChain[K];
};

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,119 @@
import { CustomValidator, ErrorMessage, FieldMessageFactory } from '../base';
import { ContextBuilder } from '../context-builder';
import * as Options from '../options';
import { ExistsOptions, Validators } from './validators';
export declare class ValidatorsImpl<Chain> implements Validators<Chain> {
private readonly builder;
private readonly chain;
private lastValidator;
private negateNext;
constructor(builder: ContextBuilder, chain: Chain);
private addItem;
not(): Chain;
withMessage(message: FieldMessageFactory | ErrorMessage): Chain;
custom(validator: CustomValidator): Chain;
exists(options?: ExistsOptions): Chain;
isArray(options?: {
min?: number;
max?: number;
}): Chain;
isObject(options?: {
strict?: boolean;
}): Chain;
isString(): Chain;
isULID(): any;
notEmpty(options?: Options.IsEmptyOptions): Chain;
private addStandardValidation;
contains(elem: any, options?: Options.ContainsOptions): Chain;
equals(comparison: string): Chain;
isAbaRouting(): Chain;
isAfter(dateOrOptions?: string | Options.IsAfterOptions): Chain;
isAlpha(locale?: Options.AlphaLocale, options?: Options.IsAlphaOptions): Chain;
isAlphanumeric(locale?: Options.AlphanumericLocale, options?: Options.IsAlphanumericOptions): Chain;
isAscii(): Chain;
isBase32(options?: Options.IsBase32Options): Chain;
isBase58(): Chain;
isBase64(options?: Options.IsBase64Options): Chain;
isBefore(date?: string): Chain;
isBIC(): Chain;
/**
* There are basically three levels of strictness for this validator.
* Passing `{ strict: true }` as option only passes the validation if the value is a JS bool. (It also overrides the loose property of the options).
* Passing `{ loose: true|false }` along with no `strict` prop of with `strict` falsy follows the behaviour specified in validator.js docs.
*/
isBoolean(options?: Options.IsBooleanOptions): Chain;
isBtcAddress(): Chain;
isByteLength(options: Options.MinMaxOptions): Chain;
isCreditCard(): Chain;
isCurrency(options?: Options.IsCurrencyOptions): Chain;
isDataURI(): Chain;
isDate(options?: Options.IsDateOptions): Chain;
isDecimal(options?: Options.IsDecimalOptions): Chain;
isDivisibleBy(number: number): Chain;
isEAN(): Chain;
isEmail(options?: Options.IsEmailOptions): Chain;
isEmpty(options?: Options.IsEmptyOptions): Chain;
isEthereumAddress(): Chain;
isFQDN(options?: Options.IsFQDNOptions): Chain;
isFloat(options?: Options.IsFloatOptions): Chain;
isFreightContainerID(): Chain;
isFullWidth(): Chain;
isHalfWidth(): Chain;
isHash(algorithm: Options.HashAlgorithm): Chain;
isHexColor(): Chain;
isHexadecimal(): Chain;
isHSL(): Chain;
isIBAN(options?: Options.IsIBANOptions): Chain;
isIdentityCard(locale: Options.IdentityCardLocale): Chain;
isIMEI(options?: Options.IsIMEIOptions): Chain;
isIP(version?: Options.IPVersion): Chain;
isIPRange(version?: Options.IPVersion): Chain;
isISBN(versionOrOptions?: number | Options.IsISBNOptions): Chain;
isISSN(options?: Options.IsISSNOptions): Chain;
isISIN(): Chain;
isISO6346(): Chain;
isISO6391(): Chain;
isISO8601(options?: Options.IsISO8601Options): Chain;
isISO31661Alpha2(): Chain;
isISO31661Alpha3(): Chain;
isISO4217(): Chain;
isISRC(): Chain;
isIn(values: readonly any[]): Chain;
isInt(options?: Options.IsIntOptions): Chain;
isJSON(options?: Options.IsJSONOptions): Chain;
isJWT(): Chain;
isLatLong(options?: Options.IsLatLongOptions): Chain;
isLength(options: Options.MinMaxOptions): Chain;
isLicensePlate(locale: Options.IsLicensePlateLocale): Chain;
isLocale(): Chain;
isLowercase(): Chain;
isLuhnNumber(): Chain;
isMagnetURI(): Chain;
isMailtoURI(options?: Options.IsEmailOptions): Chain;
isMACAddress(options?: Options.IsMACAddressOptions): Chain;
isMD5(): Chain;
isMimeType(): Chain;
isMobilePhone(locale: Options.MobilePhoneLocale | readonly Options.MobilePhoneLocale[], options?: Options.IsMobilePhoneOptions): Chain;
isMongoId(): Chain;
isMultibyte(): Chain;
isNumeric(options?: Options.IsNumericOptions): Chain;
isOctal(): Chain;
isPassportNumber(countryCode?: Options.PassportCountryCode): Chain;
isPort(): Chain;
isPostalCode(locale: Options.PostalCodeLocale): Chain;
isRFC3339(): Chain;
isRgbColor(includePercentValues?: boolean): Chain;
isSemVer(): Chain;
isSlug(): Chain;
isStrongPassword(options?: Options.IsStrongPasswordOptions): Chain;
isSurrogatePair(): Chain;
isTaxID(locale: Options.TaxIDLocale): Chain;
isTime(options?: Options.IsTimeOptions): Chain;
isURL(options?: Options.IsURLOptions): Chain;
isUUID(version?: Options.UUIDVersion): Chain;
isUppercase(): Chain;
isVariableWidth(): Chain;
isVAT(countryCode: Options.VATCountryCode): Chain;
isWhitelisted(chars: string | readonly string[]): Chain;
matches(pattern: RegExp | string, modifiers?: string): any;
}

View File

@ -0,0 +1,347 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidatorsImpl = void 0;
const validator = require("validator");
const context_items_1 = require("../context-items");
class ValidatorsImpl {
constructor(builder, chain) {
this.builder = builder;
this.chain = chain;
this.negateNext = false;
}
addItem(item) {
this.builder.addItem(item);
this.lastValidator = item;
// Reset this.negateNext so that next validation isn't negated too
this.negateNext = false;
return this.chain;
}
// validation manipulation
not() {
this.negateNext = true;
return this.chain;
}
withMessage(message) {
this.lastValidator.message = message;
return this.chain;
}
// custom validators
custom(validator) {
return this.addItem(new context_items_1.CustomValidation(validator, this.negateNext));
}
exists(options = {}) {
let validator;
if (options.checkFalsy || options.values === 'falsy') {
validator = value => !!value;
}
else if (options.checkNull || options.values === 'null') {
validator = value => value != null;
}
else {
validator = value => value !== undefined;
}
return this.custom(validator);
}
isArray(options = {}) {
return this.custom(value => Array.isArray(value) &&
(typeof options.min === 'undefined' || value.length >= options.min) &&
(typeof options.max === 'undefined' || value.length <= options.max));
}
isObject(options = { strict: true }) {
return this.custom(value => typeof value === 'object' &&
(options.strict == null || options.strict ? value !== null && !Array.isArray(value) : true));
}
isString() {
return this.custom(value => typeof value === 'string');
}
isULID() {
return this.matches(/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i);
}
notEmpty(options) {
this.not();
return this.isEmpty(options);
}
// Standard validators
addStandardValidation(validator, ...options) {
return this.addItem(new context_items_1.StandardValidation(validator, this.negateNext, options));
}
contains(elem, options) {
return this.addStandardValidation(validator.contains, elem, options);
}
equals(comparison) {
return this.addStandardValidation(validator.equals, comparison);
}
isAbaRouting() {
return this.addStandardValidation(validator.isAbaRouting);
}
isAfter(dateOrOptions) {
return this.addStandardValidation(validator.isAfter, dateOrOptions);
}
isAlpha(locale, options) {
// TODO(v7): remove string[] support
const ignore = Array.isArray(options?.ignore) ? options?.ignore.join('') : options?.ignore;
return this.addStandardValidation(validator.isAlpha, locale, { ...options, ignore });
}
isAlphanumeric(locale, options) {
return this.addStandardValidation(validator.isAlphanumeric, locale, options);
}
isAscii() {
return this.addStandardValidation(validator.isAscii);
}
isBase32(options) {
return this.addStandardValidation(validator.isBase32, options);
}
isBase58() {
return this.addStandardValidation(validator.isBase58);
}
isBase64(options) {
return this.addStandardValidation(validator.isBase64, options);
}
isBefore(date) {
return this.addStandardValidation(validator.isBefore, date);
}
isBIC() {
return this.addStandardValidation(validator.isBIC);
}
/**
* There are basically three levels of strictness for this validator.
* Passing `{ strict: true }` as option only passes the validation if the value is a JS bool. (It also overrides the loose property of the options).
* Passing `{ loose: true|false }` along with no `strict` prop of with `strict` falsy follows the behaviour specified in validator.js docs.
*/
isBoolean(options) {
if (options?.strict) {
return this.custom(value => {
return value === true || value === false;
});
}
return this.addStandardValidation(validator.isBoolean, options);
}
isBtcAddress() {
return this.addStandardValidation(validator.isBtcAddress);
}
isByteLength(options) {
return this.addStandardValidation(validator.isByteLength, options);
}
isCreditCard() {
return this.addStandardValidation(validator.isCreditCard);
}
isCurrency(options) {
return this.addStandardValidation(validator.isCurrency, options);
}
isDataURI() {
return this.addStandardValidation(validator.isDataURI);
}
isDate(options) {
return this.addStandardValidation(validator.isDate, options);
}
isDecimal(options) {
return this.addStandardValidation(validator.isDecimal, options);
}
isDivisibleBy(number) {
return this.addStandardValidation(validator.isDivisibleBy, number);
}
isEAN() {
return this.addStandardValidation(validator.isEAN);
}
isEmail(options) {
return this.addStandardValidation(validator.isEmail, options);
}
isEmpty(options) {
return this.addStandardValidation(validator.isEmpty, options);
}
isEthereumAddress() {
return this.addStandardValidation(validator.isEthereumAddress);
}
isFQDN(options) {
return this.addStandardValidation(validator.isFQDN, options);
}
isFloat(options) {
return this.addStandardValidation(validator.isFloat, options);
}
isFreightContainerID() {
return this.addStandardValidation(validator.isFreightContainerID);
}
isFullWidth() {
return this.addStandardValidation(validator.isFullWidth);
}
isHalfWidth() {
return this.addStandardValidation(validator.isHalfWidth);
}
isHash(algorithm) {
return this.addStandardValidation(validator.isHash, algorithm);
}
isHexColor() {
return this.addStandardValidation(validator.isHexColor);
}
isHexadecimal() {
return this.addStandardValidation(validator.isHexadecimal);
}
isHSL() {
return this.addStandardValidation(validator.isHSL);
}
isIBAN(options) {
return this.addStandardValidation(validator.isIBAN, options);
}
isIdentityCard(locale) {
return this.addStandardValidation(validator.isIdentityCard, locale);
}
isIMEI(options) {
return this.addStandardValidation(validator.isIMEI, options);
}
isIP(version) {
return this.addStandardValidation(validator.isIP, version);
}
isIPRange(version) {
return this.addStandardValidation(validator.isIPRange, version);
}
isISBN(versionOrOptions) {
return this.addStandardValidation(validator.isISBN, versionOrOptions);
}
isISSN(options) {
return this.addStandardValidation(validator.isISSN, options);
}
isISIN() {
return this.addStandardValidation(validator.isISIN);
}
isISO6346() {
return this.addStandardValidation(validator.isISO6346);
}
isISO6391() {
return this.addStandardValidation(validator.isISO6391);
}
isISO8601(options) {
return this.addStandardValidation(validator.isISO8601, options);
}
isISO31661Alpha2() {
return this.addStandardValidation(validator.isISO31661Alpha2);
}
isISO31661Alpha3() {
return this.addStandardValidation(validator.isISO31661Alpha3);
}
isISO4217() {
return this.addStandardValidation(validator.isISO4217);
}
isISRC() {
return this.addStandardValidation(validator.isISRC);
}
isIn(values) {
return this.addStandardValidation(validator.isIn, values);
}
isInt(options) {
return this.addStandardValidation(validator.isInt, options);
}
isJSON(options) {
return this.addStandardValidation(validator.isJSON, options);
}
isJWT() {
return this.addStandardValidation(validator.isJWT);
}
isLatLong(options) {
return this.addStandardValidation(validator.isLatLong, options);
}
isLength(options) {
return this.addStandardValidation(validator.isLength, options);
}
isLicensePlate(locale) {
return this.addStandardValidation(validator.isLicensePlate, locale);
}
isLocale() {
return this.addStandardValidation(validator.isLocale);
}
isLowercase() {
return this.addStandardValidation(validator.isLowercase);
}
isLuhnNumber() {
return this.addStandardValidation(validator.isLuhnNumber);
}
isMagnetURI() {
return this.addStandardValidation(validator.isMagnetURI);
}
isMailtoURI(options) {
return this.addStandardValidation(validator.isMailtoURI, options);
}
isMACAddress(options) {
return this.addStandardValidation(validator.isMACAddress, options);
}
isMD5() {
return this.addStandardValidation(validator.isMD5);
}
isMimeType() {
return this.addStandardValidation(validator.isMimeType);
}
isMobilePhone(locale, options) {
return this.addStandardValidation(validator.isMobilePhone, locale, options);
}
isMongoId() {
return this.addStandardValidation(validator.isMongoId);
}
isMultibyte() {
return this.addStandardValidation(validator.isMultibyte);
}
isNumeric(options) {
return this.addStandardValidation(validator.isNumeric, options);
}
isOctal() {
return this.addStandardValidation(validator.isOctal);
}
isPassportNumber(countryCode) {
return this.addStandardValidation(validator.isPassportNumber, countryCode);
}
isPort() {
return this.addStandardValidation(validator.isPort);
}
isPostalCode(locale) {
return this.addStandardValidation(validator.isPostalCode, locale);
}
isRFC3339() {
return this.addStandardValidation(validator.isRFC3339);
}
isRgbColor(includePercentValues) {
return this.addStandardValidation(validator.isRgbColor, includePercentValues);
}
isSemVer() {
return this.addStandardValidation(validator.isSemVer);
}
isSlug() {
return this.addStandardValidation(validator.isSlug);
}
isStrongPassword(options) {
return this.addStandardValidation(validator.isStrongPassword, options);
}
isSurrogatePair() {
return this.addStandardValidation(validator.isSurrogatePair);
}
isTaxID(locale) {
return this.addStandardValidation(validator.isTaxID, locale);
}
isTime(options) {
return this.addStandardValidation(validator.isTime, options);
}
isURL(options) {
return this.addStandardValidation(validator.isURL, options);
}
isUUID(version) {
return this.addStandardValidation(validator.isUUID, version);
}
isUppercase() {
return this.addStandardValidation(validator.isUppercase);
}
isVariableWidth() {
return this.addStandardValidation(validator.isVariableWidth);
}
isVAT(countryCode) {
return this.addStandardValidation(validator.isVAT, countryCode);
}
isWhitelisted(chars) {
return this.addStandardValidation(validator.isWhitelisted, chars);
}
matches(pattern, modifiers) {
return this.addStandardValidation.apply(this, [
validator.matches,
...(typeof pattern === 'string'
? [pattern, modifiers]
: [pattern.source, [...new Set((modifiers || '') + pattern.flags)].join('')]),
]);
}
}
exports.ValidatorsImpl = ValidatorsImpl;

View File

@ -0,0 +1,184 @@
import { CustomValidator, ErrorMessage, FieldMessageFactory } from '../base';
import * as Options from '../options';
export type ExistsOptions = {
/**
* Defines which kind of value makes a field _NOT_ exist.
*
* - `undefined`: only `undefined` values; equivalent to `value !== undefined`
* - `null`: only `undefined` and `null` values; equivalent to `value != null`
* - `falsy`: all falsy values; equivalent to `!!value`
*
* @default 'undefined'
*/
values?: 'undefined' | 'null' | 'falsy';
/**
* Whether a field whose value is falsy should be considered non-existent.
* @default false
* @deprecated Use `values` instead
*/
checkFalsy?: boolean;
/**
* Whether a field whose value is `null` or `undefined` should be considered non-existent.
* @default false
* @deprecated Use `values` instead
*/
checkNull?: boolean;
};
export interface Validators<Return> {
/**
* Negates the result of the next validator.
*
* @example check('weekday').not().isIn(['sunday', 'saturday'])
* @returns the current validation chain
*/
not(): Return;
/**
* Sets the error message for the previous validator.
*
* @param message the message, which can be any value, or a function for dynamically creating the
* error message based on the field value
* @returns the current validation chain
*/
withMessage(message: FieldMessageFactory | ErrorMessage): Return;
/**
* Adds a custom validator to the validation chain.
*
* @param validator the custom validator
* @returns the current validation chain
*/
custom(validator: CustomValidator): Return;
/**
* Adds a validator to check that the fields exist in the request.
* By default, this means that the value of the fields may not be `undefined`;
* all other values are acceptable.
*
* @param options
* @returns the current validation chain
*/
exists(options?: ExistsOptions): Return;
/**
* Adds a validator to check if a value is an array.
*
* @param options
* @returns the current validation chain
*/
isArray(options?: {
min?: number;
max?: number;
}): Return;
/**
* Adds a validator to check if a value is an object.
*
* @param options
* @returns the current validation chain
*/
isObject(options?: {
strict?: boolean;
}): Return;
/**
* Adds a validator to check if a value is a string.
*
* @returns the current validation chain
*/
isString(): Return;
/**
* Adds a validator to check if a value is a ULID.
*
* @returns the current validation chain
*/
isULID(): Return;
/**
* Adds a validator to check if a value is not empty; that is, a string with length of 1 or more.
*
* @param options
* @returns the current validation chain
*/
notEmpty(options?: Options.IsEmptyOptions): Return;
contains(elem: any, options?: Options.ContainsOptions): Return;
equals(comparison: string): Return;
isAbaRouting(): Return;
isAfter(dateOrOptions?: string | Options.IsAfterOptions): Return;
isAlpha(locale?: Options.AlphaLocale, options?: Options.IsAlphaOptions): Return;
isAlphanumeric(locale?: Options.AlphanumericLocale, options?: Options.IsAlphanumericOptions): Return;
isAscii(): Return;
isBase32(options?: Options.IsBase32Options): Return;
isBase58(): Return;
isBase64(options?: Options.IsBase64Options): Return;
isBefore(date?: string): Return;
isBIC(): Return;
isBoolean(options?: Options.IsBooleanOptions): Return;
isBtcAddress(): Return;
isByteLength(options: Options.MinMaxExtendedOptions): Return;
isCreditCard(options?: Options.IsCreditCard): Return;
isCurrency(options?: Options.IsCurrencyOptions): Return;
isDataURI(): Return;
isDate(options?: Options.IsDateOptions): Return;
isDecimal(options?: Options.IsDecimalOptions): Return;
isDivisibleBy(number: number): Return;
isEAN(): Return;
isEmail(options?: Options.IsEmailOptions): Return;
isEmpty(options?: Options.IsEmptyOptions): Return;
isEthereumAddress(): Return;
isFQDN(options?: Options.IsFQDNOptions): Return;
isFloat(options?: Options.IsFloatOptions): Return;
isFreightContainerID(): Return;
isFullWidth(): Return;
isHalfWidth(): Return;
isHash(algorithm: Options.HashAlgorithm): Return;
isHexColor(): Return;
isHexadecimal(): Return;
isHSL(): Return;
isIBAN(options?: Options.IsIBANOptions): Return;
isIdentityCard(locale?: Options.IdentityCardLocale): Return;
isIMEI(options?: Options.IsIMEIOptions): Return;
isIP(version?: Options.IPVersion): Return;
isIPRange(version?: Options.IPVersion): Return;
isISBN(versionOrOptions?: number | Options.IsISBNOptions): Return;
isISSN(options?: Options.IsISSNOptions): Return;
isISIN(): Return;
isISO6346(): Return;
isISO6391(): Return;
isISO8601(options?: Options.IsISO8601Options): Return;
isISO31661Alpha2(): Return;
isISO31661Alpha3(): Return;
isISO4217(): Return;
isISRC(): Return;
isIn(values: readonly any[]): Return;
isInt(options?: Options.IsIntOptions): Return;
isJSON(options?: Options.IsJSONOptions): Return;
isJWT(): Return;
isLatLong(options?: Options.IsLatLongOptions): Return;
isLength(options: Options.MinMaxOptions): Return;
isLicensePlate(locale: Options.IsLicensePlateLocale): Return;
isLocale(): Return;
isLowercase(): Return;
isLuhnNumber(): Return;
isMagnetURI(): Return;
isMailtoURI(options?: Options.IsEmailOptions): Return;
isMACAddress(options?: Options.IsMACAddressOptions): Return;
isMD5(): Return;
isMimeType(): Return;
isMobilePhone(locale: Options.MobilePhoneLocale | readonly Options.MobilePhoneLocale[], options?: Options.IsMobilePhoneOptions): Return;
isMongoId(): Return;
isMultibyte(): Return;
isNumeric(options?: Options.IsNumericOptions): Return;
isOctal(): Return;
isPassportNumber(countryCode?: Options.PassportCountryCode): Return;
isPort(): Return;
isPostalCode(locale: Options.PostalCodeLocale): Return;
isRgbColor(includePercentValues?: boolean): Return;
isRFC3339(): Return;
isSemVer(): Return;
isSlug(): Return;
isStrongPassword(options?: Options.IsStrongPasswordOptions): Return;
isSurrogatePair(): Return;
isTaxID(locale: Options.TaxIDLocale): Return;
isTime(options: Options.IsTimeOptions): Return;
isURL(options?: Options.IsURLOptions): Return;
isUUID(version?: Options.UUIDVersion): Return;
isUppercase(): Return;
isVariableWidth(): Return;
isVAT(countryCode: Options.VATCountryCode): Return;
isWhitelisted(chars: string | readonly string[]): Return;
matches(pattern: RegExp | string, modifiers?: string): Return;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,20 @@
import { ContextItem } from './context-items';
import { Context, Optional } from './context';
import { Location } from './base';
export declare class ContextBuilder {
private readonly stack;
private fields;
private locations;
private message;
private optional;
private requestBail;
private visibility;
setFields(fields: string[]): this;
setLocations(locations: Location[]): this;
setMessage(message: any): this;
addItem(...items: ContextItem[]): this;
setOptional(options: Optional): this;
setRequestBail(): this;
setHidden(hidden: boolean, hiddenValue?: string): this;
build(): Context;
}

View File

@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextBuilder = void 0;
const context_1 = require("./context");
class ContextBuilder {
constructor() {
this.stack = [];
this.fields = [];
this.locations = [];
this.optional = false;
this.requestBail = false;
this.visibility = { type: 'visible' };
}
setFields(fields) {
this.fields = fields;
return this;
}
setLocations(locations) {
this.locations = locations;
return this;
}
setMessage(message) {
this.message = message;
return this;
}
addItem(...items) {
this.stack.push(...items);
return this;
}
setOptional(options) {
this.optional = options;
return this;
}
setRequestBail() {
this.requestBail = true;
return this;
}
setHidden(hidden, hiddenValue) {
if (hidden) {
this.visibility =
hiddenValue !== undefined ? { type: 'redacted', value: hiddenValue } : { type: 'hidden' };
}
else {
this.visibility = { type: 'visible' };
}
return this;
}
build() {
return new context_1.Context(this.fields, this.locations, this.stack, this.optional, this.requestBail, this.visibility, this.message);
}
}
exports.ContextBuilder = ContextBuilder;

View File

@ -0,0 +1,5 @@
import { Context } from '../context';
import { ContextItem } from './context-item';
export declare class Bail implements ContextItem {
run(context: Context): Promise<void>;
}

View File

@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Bail = void 0;
const base_1 = require("../base");
class Bail {
run(context) {
if (context.errors.length > 0) {
throw new base_1.ValidationHalt();
}
return Promise.resolve();
}
}
exports.Bail = Bail;

View File

@ -0,0 +1,9 @@
import { Meta } from '../base';
import { ContextRunner } from '../chain';
import { Context } from '../context';
import { ContextItem } from './context-item';
export declare class ChainCondition implements ContextItem {
private readonly chain;
constructor(chain: ContextRunner);
run(_context: Context, _value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChainCondition = void 0;
const base_1 = require("../base");
class ChainCondition {
constructor(chain) {
this.chain = chain;
}
async run(_context, _value, meta) {
const result = await this.chain.run(meta.req, { dryRun: true });
if (!result.isEmpty()) {
throw new base_1.ValidationHalt();
}
}
}
exports.ChainCondition = ChainCondition;

View File

@ -0,0 +1,5 @@
import { Meta } from '../base';
import { Context } from '../context';
export interface ContextItem {
run(context: Context, value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,8 @@
import { CustomValidator, Meta } from '../base';
import { Context } from '../context';
import { ContextItem } from './context-item';
export declare class CustomCondition implements ContextItem {
private readonly condition;
constructor(condition: CustomValidator);
run(_context: Context, value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomCondition = void 0;
const base_1 = require("../base");
class CustomCondition {
constructor(condition) {
this.condition = condition;
}
async run(_context, value, meta) {
try {
const result = this.condition(value, meta);
await result;
// if the promise resolved or the result is truthy somehow, then there's no validation halt.
if (!result) {
// the error thrown here is symbolic, it will be re-thrown in the catch clause anyway.
throw new Error();
}
}
catch (e) {
throw new base_1.ValidationHalt();
}
}
}
exports.CustomCondition = CustomCondition;

View File

@ -0,0 +1,10 @@
import { CustomValidator, Meta } from '../base';
import { Context } from '../context';
import { ContextItem } from './context-item';
export declare class CustomValidation implements ContextItem {
private readonly validator;
private readonly negated;
message: any;
constructor(validator: CustomValidator, negated: boolean);
run(context: Context, value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomValidation = void 0;
class CustomValidation {
constructor(validator, negated) {
this.validator = validator;
this.negated = negated;
}
async run(context, value, meta) {
try {
const result = this.validator(value, meta);
const actualResult = await result;
const isPromise = result && result.then;
const failed = this.negated ? actualResult : !actualResult;
// A promise that was resolved only adds an error if negated.
// Otherwise it always suceeds
if ((!isPromise && failed) || (isPromise && this.negated)) {
context.addError({ type: 'field', message: this.message, value, meta });
}
}
catch (err) {
if (this.negated) {
return;
}
context.addError({
type: 'field',
message: this.message || (err instanceof Error ? err.message : err),
value,
meta,
});
}
}
}
exports.CustomValidation = CustomValidation;

View File

@ -0,0 +1,5 @@
export * from './chain-condition';
export * from './context-item';
export * from './custom-condition';
export * from './custom-validation';
export * from './standard-validation';

View File

@ -0,0 +1,21 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./chain-condition"), exports);
__exportStar(require("./context-item"), exports);
__exportStar(require("./custom-condition"), exports);
__exportStar(require("./custom-validation"), exports);
__exportStar(require("./standard-validation"), exports);

View File

@ -0,0 +1,12 @@
import { Context } from '../context';
import { CustomSanitizer, Meta, StandardSanitizer } from '../base';
import { toString as toStringImpl } from '../utils';
import { ContextItem } from './context-item';
export declare class Sanitization implements ContextItem {
private readonly sanitizer;
private readonly custom;
private readonly options;
private readonly stringify;
constructor(sanitizer: StandardSanitizer | CustomSanitizer, custom: boolean, options?: any[], stringify?: typeof toStringImpl);
run(context: Context, value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sanitization = void 0;
const utils_1 = require("../utils");
class Sanitization {
constructor(sanitizer, custom, options = [],
// For testing only.
// Deliberately not calling it `toString` in order to not override `Object.prototype.toString`.
stringify = utils_1.toString) {
this.sanitizer = sanitizer;
this.custom = custom;
this.options = options;
this.stringify = stringify;
}
async run(context, value, meta) {
const { path, location } = meta;
const runCustomSanitizer = async () => {
const sanitizerValue = this.sanitizer(value, meta);
return Promise.resolve(sanitizerValue);
};
if (this.custom) {
const newValue = await runCustomSanitizer();
context.setData(path, newValue, location);
return;
}
const values = Array.isArray(value) ? value : [value];
const newValues = values.map(value => {
return this.sanitizer(this.stringify(value), ...this.options);
});
// We get only the first value of the array if the orginal value was wrapped.
context.setData(path, values !== value ? newValues[0] : newValues, location);
}
}
exports.Sanitization = Sanitization;

View File

@ -0,0 +1,13 @@
import { Meta, StandardValidator } from '../base';
import { toString as toStringImpl } from '../utils';
import { Context } from '../context';
import { ContextItem } from './context-item';
export declare class StandardValidation implements ContextItem {
private readonly validator;
private readonly negated;
private readonly options;
private readonly stringify;
message: any;
constructor(validator: StandardValidator, negated: boolean, options?: any[], stringify?: typeof toStringImpl);
run(context: Context, value: any, meta: Meta): Promise<void>;
}

View File

@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StandardValidation = void 0;
const utils_1 = require("../utils");
class StandardValidation {
constructor(validator, negated, options = [],
// For testing only.
// Deliberately not calling it `toString` in order to not override `Object.prototype.toString`.
stringify = utils_1.toString) {
this.validator = validator;
this.negated = negated;
this.options = options;
this.stringify = stringify;
}
async run(context, value, meta) {
const values = Array.isArray(value) ? value : [value];
values.forEach(value => {
const result = this.validator(this.stringify(value), ...this.options);
if (this.negated ? result : !result) {
context.addError({ type: 'field', message: this.message, value, meta });
}
});
}
}
exports.StandardValidation = StandardValidation;

View File

@ -0,0 +1,61 @@
import { FieldInstance, FieldValidationError, Location, Meta, Request, UnknownFieldInstance, ValidationError } from './base';
import { ContextItem } from './context-items';
/**
* Defines which kind of value makes a field optional.
*
* - `undefined`: only `undefined` values; equivalent to `value === undefined`
* - `null`: only `undefined` and `null` values; equivalent to `value == null`
* - `falsy`: all falsy values; equivalent to `!value`
* - `false`: not optional.
*/
export type Optional = 'undefined' | 'null' | 'falsy' | false;
export type AddErrorOptions = {
type: 'field';
message?: any;
value: any;
meta: Meta;
} | {
type: 'unknown_fields';
req: Request;
message?: any;
fields: UnknownFieldInstance[];
} | {
type: 'alternative';
req: Request;
message?: any;
nestedErrors: FieldValidationError[];
} | {
type: 'alternative_grouped';
req: Request;
message?: any;
nestedErrors: FieldValidationError[][];
};
export type ValueVisibility = {
type: 'visible';
} | {
type: 'hidden';
} | {
type: 'redacted';
value: string;
};
export declare class Context {
readonly fields: string[];
readonly locations: Location[];
readonly stack: ReadonlyArray<ContextItem>;
readonly optional: Optional;
readonly bail: boolean;
readonly visibility: ValueVisibility;
readonly message?: any | undefined;
private readonly _errors;
get errors(): ReadonlyArray<ValidationError>;
private readonly dataMap;
constructor(fields: string[], locations: Location[], stack: ReadonlyArray<ContextItem>, optional: Optional, bail: boolean, visibility?: ValueVisibility, message?: any | undefined);
getData(options?: {
requiredOnly: boolean;
}): FieldInstance[];
addFieldInstances(instances: FieldInstance[]): void;
setData(path: string, value: any, location: Location): void;
addError(opts: AddErrorOptions): void;
private updateVisibility;
}
export type ReadonlyContext = Pick<Context, Exclude<keyof Context, 'setData' | 'addFieldInstances' | 'addError'>>;

View File

@ -0,0 +1,117 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Context = void 0;
const _ = require("lodash");
function getDataMapKey(path, location) {
return `${location}:${path}`;
}
class Context {
get errors() {
return this._errors;
}
constructor(fields, locations, stack, optional, bail, visibility = { type: 'visible' }, message) {
this.fields = fields;
this.locations = locations;
this.stack = stack;
this.optional = optional;
this.bail = bail;
this.visibility = visibility;
this.message = message;
this._errors = [];
this.dataMap = new Map();
}
getData(options = { requiredOnly: false }) {
const { optional } = this;
const checks = options.requiredOnly && optional
? [
(value) => value !== undefined,
(value) => (optional === 'null' ? value != null : true),
(value) => (optional === 'falsy' ? value : true),
]
: [];
return _([...this.dataMap.values()])
.groupBy('originalPath')
.flatMap((instances, group) => {
const locations = _.uniqBy(instances, 'location');
// #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 && locations.length > 1 && !group.includes('*')) {
const withValue = instances.filter(instance => instance.value !== undefined);
return withValue.length ? withValue : [instances[0]];
}
return instances;
})
.filter(instance => checks.every(check => check(instance.value)))
.valueOf();
}
addFieldInstances(instances) {
instances.forEach(instance => {
this.dataMap.set(getDataMapKey(instance.path, instance.location), { ...instance });
});
}
setData(path, value, location) {
const instance = this.dataMap.get(getDataMapKey(path, location));
if (!instance) {
throw new Error('Attempt to write data that did not pre-exist in context');
}
instance.value = value;
}
addError(opts) {
const msg = opts.message || this.message || 'Invalid value';
let error;
switch (opts.type) {
case 'field':
error = this.updateVisibility({
type: 'field',
value: opts.value,
msg: typeof msg === 'function' ? msg(opts.value, opts.meta) : msg,
path: opts.meta?.path,
location: opts.meta?.location,
});
break;
case 'unknown_fields':
error = {
type: 'unknown_fields',
msg: typeof msg === 'function' ? msg(opts.fields, { req: opts.req }) : msg,
fields: opts.fields,
};
break;
case 'alternative':
error = {
type: 'alternative',
msg: typeof msg === 'function' ? msg(opts.nestedErrors, { req: opts.req }) : msg,
nestedErrors: opts.nestedErrors.map(error => this.updateVisibility(error)),
};
break;
case 'alternative_grouped':
error = {
type: 'alternative_grouped',
msg: typeof msg === 'function' ? msg(opts.nestedErrors, { req: opts.req }) : msg,
nestedErrors: opts.nestedErrors.map(errors => errors.map(error => this.updateVisibility(error))),
};
break;
default:
throw new Error(`Unhandled addError case`);
}
this._errors.push(error);
}
updateVisibility(error) {
switch (this.visibility.type) {
case 'hidden':
error = { ...error };
delete error.value;
return error;
case 'redacted':
return {
...error,
value: this.visibility.value,
};
case 'visible':
default:
return error;
}
}
}
exports.Context = Context;

View File

@ -0,0 +1,153 @@
import { CustomSanitizer, CustomValidator, ErrorMessage, FieldMessageFactory, Location, Middleware, Request, ValidationError } from './base';
import { ContextRunner, ValidationChain } from './chain';
import { MatchedDataOptions } from './matched-data';
import { checkExact } from './middlewares/exact';
import { OneOfOptions } from './middlewares/one-of';
import { DefaultSchemaKeys, ExtensionSanitizerSchemaOptions, ExtensionValidatorSchemaOptions, ParamSchema, RunnableValidationChains } from './middlewares/schema';
import { ErrorFormatter, Result } from './validation-result';
type CustomValidatorsMap = Record<string, CustomValidator>;
type CustomSanitizersMap = Record<string, CustomSanitizer>;
type CustomOptions<E = ValidationError> = {
errorFormatter?: ErrorFormatter<E>;
};
/**
* A validation chain that contains some extension validators/sanitizers.
*
* Built-in methods return the same chain type so that chaining using more of the extensions is
* possible.
*
* @example
* ```
* function createChain(chain: ValidationChainWithExtensions<'isAllowedDomain' | 'removeEmailAttribute'>) {
* return chain
* .isEmail()
* .isAllowedDomain()
* .trim()
* .removeEmailAttribute();
* }
* ```
*/
export type ValidationChainWithExtensions<T extends string> = Middleware & {
[K in keyof ValidationChain]: ValidationChain[K] extends (...args: infer A) => ValidationChain ? (...params: A) => ValidationChainWithExtensions<T> : ValidationChain[K];
} & {
[K in T]: () => ValidationChainWithExtensions<T>;
};
/**
* Schema of validations/sanitizations for a field, including extension validators/sanitizers
*/
export type ParamSchemaWithExtensions<V extends string, S extends string, T extends string = DefaultSchemaKeys> = {
[K in keyof ParamSchema<T> | V | S]?: K extends V ? ExtensionValidatorSchemaOptions : K extends S ? ExtensionSanitizerSchemaOptions : K extends keyof ParamSchema<T> ? ParamSchema<T>[K] : never;
};
/**
* Type of a validation chain created by a custom ExpressValidator instance.
*
* @example
* ```
* const myExpressValidator = new ExpressValidator({
* isAllowedDomain: value => value.endsWith('@gmail.com')
* });
*
* type MyCustomValidationChain = CustomValidationChain<typeof myExpressValidator>
* function createMyCustomChain(): MyCustomValidationChain {
* return myExpressValidator.body('email').isAllowedDomain();
* }
* ```
*/
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;
/**
* Mapping from field name to a validations/sanitizations schema, including extensions from an
* ExpressValidator instance.
*/
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;
export declare class ExpressValidator<V extends CustomValidatorsMap = {}, S extends CustomSanitizersMap = {}, E = ValidationError> {
private readonly validators?;
private readonly sanitizers?;
private readonly options?;
private readonly validatorEntries;
private readonly sanitizerEntries;
constructor(validators?: V | undefined, sanitizers?: S | undefined, options?: CustomOptions<E> | undefined);
private createChain;
buildCheckFunction(locations: Location[]): (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Creates a middleware/validation chain for one or more fields that may be located in
* any of the following:
*
* - `req.body`
* - `req.cookies`
* - `req.headers`
* - `req.params`
* - `req.query`
*
* @param fields a string or array of field names to validate/sanitize
* @param message an error message to use when failed validations don't specify a custom message.
* Defaults to `Invalid Value`.
*/
readonly check: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.body`.
*/
readonly body: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.cookies`.
*/
readonly cookie: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.headers`.
*/
readonly header: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.params`.
*/
readonly param: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.query`.
*/
readonly query: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => CustomValidationChain<this>;
/**
* Checks whether the request contains exactly only those fields that have been validated.
*
* This method is here for convenience; it does exactly the same as `checkExact`.
*
* @see {@link checkExact}
*/
readonly checkExact: typeof checkExact;
/**
* Creates an express middleware with validations for multiple fields at once in the form of
* a schema object.
*
* @param schema the schema to validate.
* @param defaultLocations which locations to validate in each field. Defaults to every location.
*/
readonly checkSchema: <T extends string = DefaultSchemaKeys>(schema: CustomSchema<this, T>, locations?: Location[]) => RunnableValidationChains<CustomValidationChain<this>>;
/**
* Creates a middleware that will ensure that at least one of the given validation chains
* or validation chain groups are valid.
*
* If none are, a single error of type `alternative` is added to the request,
* with the errors of each chain made available under the `nestedErrors` property.
*
* @param chains an array of validation chains to check if are valid.
* If any of the items of `chains` is an array of validation chains, then all of them
* must be valid together for the request to be considered valid.
*/
oneOf(chains: (CustomValidationChain<this> | CustomValidationChain<this>[])[], options?: OneOfOptions): Middleware & ContextRunner;
/**
* Extracts the validation errors of an express request using the default error formatter of this
* instance.
*
* @see {@link validationResult()}
* @param req the express request object
* @returns a `Result` which will by default use the error formatter passed when
* instantiating `ExpressValidator`.
*/
readonly validationResult: (req: Request) => Result<E>;
/**
* Extracts data validated or sanitized from the request, and builds an object with them.
*
* This method is a shortcut for `matchedData`; it does nothing different than it.
*
* @see {@link matchedData}
*/
matchedData(req: Request, options?: Partial<MatchedDataOptions>): Record<string, any>;
}
export {};

View File

@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressValidator = void 0;
const matched_data_1 = require("./matched-data");
const check_1 = require("./middlewares/check");
const exact_1 = require("./middlewares/exact");
const one_of_1 = require("./middlewares/one-of");
const schema_1 = require("./middlewares/schema");
const validation_result_1 = require("./validation-result");
/* eslint-enable no-use-before-define */
class ExpressValidator {
constructor(validators, sanitizers, options) {
this.validators = validators;
this.sanitizers = sanitizers;
this.options = options;
/**
* Creates a middleware/validation chain for one or more fields that may be located in
* any of the following:
*
* - `req.body`
* - `req.cookies`
* - `req.headers`
* - `req.params`
* - `req.query`
*
* @param fields a string or array of field names to validate/sanitize
* @param message an error message to use when failed validations don't specify a custom message.
* Defaults to `Invalid Value`.
*/
this.check = this.buildCheckFunction(['body', 'cookies', 'headers', 'params', 'query']);
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.body`.
*/
this.body = this.buildCheckFunction(['body']);
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.cookies`.
*/
this.cookie = this.buildCheckFunction(['cookies']);
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.headers`.
*/
this.header = this.buildCheckFunction(['headers']);
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.params`.
*/
this.param = this.buildCheckFunction(['params']);
/**
* Same as {@link ExpressValidator.check}, but only validates in `req.query`.
*/
this.query = this.buildCheckFunction(['query']);
/**
* Checks whether the request contains exactly only those fields that have been validated.
*
* This method is here for convenience; it does exactly the same as `checkExact`.
*
* @see {@link checkExact}
*/
this.checkExact = exact_1.checkExact;
/**
* Creates an express middleware with validations for multiple fields at once in the form of
* a schema object.
*
* @param schema the schema to validate.
* @param defaultLocations which locations to validate in each field. Defaults to every location.
*/
// NOTE: This method references its own type, so the type cast is necessary.
this.checkSchema = (0, schema_1.createCheckSchema)((...args) => this.createChain(...args), Object.keys(this.validators || {}), Object.keys(this.sanitizers || {}));
/**
* Extracts the validation errors of an express request using the default error formatter of this
* instance.
*
* @see {@link validationResult()}
* @param req the express request object
* @returns a `Result` which will by default use the error formatter passed when
* instantiating `ExpressValidator`.
*/
this.validationResult = (req) => {
const formatter = this.options?.errorFormatter;
const result = (0, validation_result_1.validationResult)(req);
return formatter ? result.formatWith(formatter) : result;
};
this.validatorEntries = Object.entries(validators || {});
this.sanitizerEntries = Object.entries(sanitizers || {});
// Can't use arrow function in the declaration of `buildCheckFunction` due to the following
// error which only happens when tests are run without Jest cache (so CI only):
//
// 'buildCheckFunction' implicitly has type 'any' because it does not have a type annotation
// and is referenced directly or indirectly in its own initializer
this.buildCheckFunction = this.buildCheckFunction.bind(this);
}
createChain(fields = '', locations = [], message) {
const middleware = (0, check_1.check)(fields, locations, message);
const boundValidators = Object.fromEntries(this.validatorEntries.map(([name, fn]) => [name, () => middleware.custom(fn)]));
const boundSanitizers = Object.fromEntries(this.sanitizerEntries.map(([name, fn]) => [name, () => middleware.customSanitizer(fn)]));
return Object.assign(middleware, boundValidators, boundSanitizers);
}
buildCheckFunction(locations) {
return (fields, message) => this.createChain(fields, locations, message);
}
/**
* Creates a middleware that will ensure that at least one of the given validation chains
* or validation chain groups are valid.
*
* If none are, a single error of type `alternative` is added to the request,
* with the errors of each chain made available under the `nestedErrors` property.
*
* @param chains an array of validation chains to check if are valid.
* If any of the items of `chains` is an array of validation chains, then all of them
* must be valid together for the request to be considered valid.
*/
oneOf(chains, options) {
return (0, one_of_1.oneOf)(chains, options);
}
/**
* Extracts data validated or sanitized from the request, and builds an object with them.
*
* This method is a shortcut for `matchedData`; it does nothing different than it.
*
* @see {@link matchedData}
*/
matchedData(req, options) {
return (0, matched_data_1.matchedData)(req, options);
}
}
exports.ExpressValidator = ExpressValidator;

View File

@ -0,0 +1,22 @@
import { FieldInstance, Location, Request, UnknownFieldInstance } from './base';
export type SelectFields = (req: Request, fields: string[], locations: Location[]) => FieldInstance[];
export declare const selectFields: SelectFields;
export declare const selectUnknownFields: (req: Request, knownFields: string[], locations: Location[]) => UnknownFieldInstance[];
/**
* Reconstructs a field path from a list of path segments.
*
* Most segments will be concatenated by a dot, for example `['foo', 'bar']` becomes `foo.bar`.
* However, a numeric segment will be wrapped in brackets to match regular JS array syntax:
*
* ```
* reconstructFieldPath(['foo', 0, 'bar']) // foo[0].bar
* ```
*
* Segments which have a special character such as `.` will be wrapped in brackets and quotes,
* which also matches JS syntax for objects with such keys.
*
* ```
* reconstructFieldPath(['foo', 'bar.baz', 'qux']) // foo["bar.baz"].qux
* ```
*/
export declare function reconstructFieldPath(segments: readonly string[]): string;

View File

@ -0,0 +1,221 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectUnknownFields = exports.selectFields = void 0;
exports.reconstructFieldPath = reconstructFieldPath;
const _ = require("lodash");
const selectFields = (req, fields, locations) => _(fields)
.flatMap(field => _.flatMap(locations, location => {
return expandField(req, field, location);
}))
// Avoid duplicates if multiple field selections would return the same field twice.
// E.g. with fields = ['*.foo', 'bar.foo'] and req.body = { bar: { foo: 1 }, baz: { foo: 2 } },
// the instance bla.foo would appear twice, and baz.foo once.
.uniqWith(isSameFieldInstance)
.value();
exports.selectFields = selectFields;
function isSameFieldInstance(a, b) {
return a.path === b.path && a.location === b.location;
}
function expandField(req, field, location) {
const originalPath = field;
const pathToExpand = location === 'headers' ? field.toLowerCase() : field;
const paths = expandPath(req[location], pathToExpand, []);
return paths.map(({ path, values }) => {
const value = path === '' ? req[location] : _.get(req[location], path);
return {
location,
path,
originalPath,
pathValues: values,
value,
};
});
}
function expandPath(object, path, currPath, currValues = []) {
const segments = _.toPath(path);
if (!segments.length) {
// no more paths to traverse
return [
{
path: reconstructFieldPath(currPath),
values: currValues,
},
];
}
const key = segments[0];
const rest = segments.slice(1);
if (object != null && !_.isObjectLike(object)) {
if (key === '**') {
if (!rest.length) {
// globstar leaves are always selected
return [
{
path: reconstructFieldPath(currPath),
values: currValues,
},
];
}
return [];
}
if (key === '*') {
// wildcard position does not exist
return [];
}
// value is a primitive, paths being traversed from here might be in their prototype, return the entire path
return [
{
path: reconstructFieldPath([...currPath, ...segments]),
values: currValues,
},
];
}
// Use a non-null value so that inexistent fields are still selected
object = object || {};
if (key === '*') {
return Object.keys(object).flatMap(key => expandPath(object[key], rest, currPath.concat(key), currValues.concat(key)));
}
if (key === '**') {
return Object.keys(object).flatMap(key => {
const nextPath = currPath.concat(key);
const value = object[key];
// recursively find matching subpaths
const selectedPaths = expandPath(value, segments, nextPath, [key]).concat(
// skip the first remaining segment, if it matches the current key
rest[0] === key ? expandPath(value, rest.slice(1), nextPath, []) : []);
return _.uniqBy(selectedPaths, ({ path }) => path).map(({ path, values }) => ({
path,
values: values.length ? [...currValues, values.flat()] : currValues,
}));
});
}
return expandPath(object[key], rest, currPath.concat(key), currValues);
}
const selectUnknownFields = (req, knownFields, locations) => {
const tree = {};
knownFields.map(field => {
const segments = field === '' ? [''] : _.toPath(field);
pathToTree(segments, tree);
});
const instances = [];
for (const location of locations) {
if (req[location] != null) {
instances.push(...findUnknownFields(location, req[location], tree));
}
}
return instances;
};
exports.selectUnknownFields = selectUnknownFields;
function pathToTree(segments, tree) {
// Will either create or merge into existing branch for the current path segment
const branch = tree[segments[0]] || (tree[segments[0]] = {});
if (segments.length > 1) {
pathToTree(segments.slice(1), branch);
}
else {
// Leaf value.
branch[''] = {};
}
}
/**
* Performs a depth-first search for unknown fields in `value`.
* The path to the unknown fields will be pushed to the `unknownFields` argument.
*
* Known fields must be passed via `tree`. A field won't be considered unknown if:
* - its branch is validated as a whole; that is, it contains an empty string key (e.g `{ ['']: {} }`); OR
* - its path is individually validated; OR
* - it's covered by a wildcard (`*`).
*
* @returns the list of unknown fields
*/
function findUnknownFields(location, value, tree, treePath = [], unknownFields = []) {
const globstarBranch = tree['**'];
if (tree[''] || globstarBranch?.['']) {
// The rest of the tree from here is covered by some validation chain
// For example, when the current treePath is `['foo', 'bar']` but `foo` is known
return unknownFields;
}
if (typeof value !== 'object') {
if (!treePath.length || globstarBranch) {
// This is either
// a. a req.body that isn't an object (e.g. `req.body = 'bla'`), and wasn't validated either
// b. a leaf value which wasn't the target of a globstar path, e.g. `foo.**.bar`
unknownFields.push({
path: reconstructFieldPath(treePath),
value,
location,
});
}
return unknownFields;
}
const wildcardBranch = tree['*'];
for (const key of Object.keys(value)) {
const keyBranch = tree[key];
const path = treePath.concat([key]);
if (!keyBranch && !wildcardBranch && !globstarBranch) {
// No trees cover this path, so it's an unknown one.
unknownFields.push({
path: reconstructFieldPath(path),
value: value[key],
location,
});
continue;
}
const keyUnknowns = keyBranch ? findUnknownFields(location, value[key], keyBranch, path) : [];
const wildcardUnknowns = wildcardBranch
? findUnknownFields(location, value[key], wildcardBranch, path)
: [];
const globstarUnknowns = globstarBranch
? findUnknownFields(location, value[key], { ['**']: globstarBranch, ...globstarBranch }, path)
: [];
// If any of the tested branches contain only known fields, then don't mark the fields not covered
// by the other branches to the list of unknown ones.
// For example, `foo` is more comprehensive than `foo.*.bar`.
if ((!keyBranch || keyUnknowns.length) &&
(!wildcardBranch || wildcardUnknowns.length) &&
(!globstarBranch || globstarUnknowns.length)) {
unknownFields.push(...keyUnknowns, ...wildcardUnknowns, ...globstarUnknowns);
}
}
return unknownFields;
}
/**
* Reconstructs a field path from a list of path segments.
*
* Most segments will be concatenated by a dot, for example `['foo', 'bar']` becomes `foo.bar`.
* However, a numeric segment will be wrapped in brackets to match regular JS array syntax:
*
* ```
* reconstructFieldPath(['foo', 0, 'bar']) // foo[0].bar
* ```
*
* Segments which have a special character such as `.` will be wrapped in brackets and quotes,
* which also matches JS syntax for objects with such keys.
*
* ```
* reconstructFieldPath(['foo', 'bar.baz', 'qux']) // foo["bar.baz"].qux
* ```
*/
function reconstructFieldPath(segments) {
return segments.reduce((prev, segment) => {
let part = '';
segment = segment === '\\*' ? '*' : segment;
// TODO: Handle brackets?
if (segment.includes('.')) {
// Special char key access
part = `["${segment}"]`;
}
else if (/^\d+$/.test(segment)) {
// Index access
part = `[${segment}]`;
}
else if (prev) {
// Object key access
part = `.${segment}`;
}
else {
// Top level key
part = segment;
}
return prev + part;
}, '');
}

View File

@ -0,0 +1,9 @@
export { Location, Meta, CustomValidator, CustomSanitizer, AlternativeMessageFactory, FieldMessageFactory, GroupedAlternativeMessageFactory, UnknownFieldMessageFactory, FieldValidationError, AlternativeValidationError, GroupedAlternativeValidationError, UnknownFieldsError, ValidationError, } from './base';
export { ContextRunner, ValidationChain } from './chain';
export * from './middlewares/exact';
export * from './middlewares/one-of';
export * from './middlewares/validation-chain-builders';
export { checkSchema, Schema, ParamSchema } from './middlewares/schema';
export * from './matched-data';
export * from './validation-result';
export * from './express-validator';

View File

@ -0,0 +1,25 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkSchema = void 0;
__exportStar(require("./middlewares/exact"), exports);
__exportStar(require("./middlewares/one-of"), exports);
__exportStar(require("./middlewares/validation-chain-builders"), exports);
var schema_1 = require("./middlewares/schema");
Object.defineProperty(exports, "checkSchema", { enumerable: true, get: function () { return schema_1.checkSchema; } });
__exportStar(require("./matched-data"), exports);
__exportStar(require("./validation-result"), exports);
__exportStar(require("./express-validator"), exports);

View File

@ -0,0 +1,26 @@
import { Location, Request } from './base';
export type MatchedDataOptions = {
/**
* Whether the value returned by `matchedData()` should include data deemed optional.
* @default false
*/
includeOptionals: boolean;
/**
* An array of locations in the request to extract the data from.
*/
locations: Location[];
/**
* Whether the value returned by `matchedData()` should include only values that have passed
* validation.
* @default true
*/
onlyValidData: boolean;
};
/**
* Extracts data validated or sanitized from the request, and builds an object with them.
*
* @param req the express request object
* @param options
* @returns an object of data that's been validated or sanitized in the passed request
*/
export declare function matchedData<T extends object = Record<string, any>>(req: Request, options?: Partial<MatchedDataOptions>): T;

View File

@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchedData = matchedData;
const _ = require("lodash");
const base_1 = require("./base");
/**
* Extracts data validated or sanitized from the request, and builds an object with them.
*
* @param req the express request object
* @param options
* @returns an object of data that's been validated or sanitized in the passed request
*/
function matchedData(req, options = {}) {
const internalReq = req;
const fieldExtractor = createFieldExtractor(options.includeOptionals !== true);
const validityFilter = createValidityFilter(options.onlyValidData);
const locationFilter = createLocationFilter(options.locations);
return _(internalReq[base_1.contextsKey])
.flatMap(fieldExtractor)
.filter(validityFilter)
.map(field => field.instance)
.filter(locationFilter)
.reduce((state, instance) => _.set(state, instance.path, instance.value), {});
}
function createFieldExtractor(removeOptionals) {
return (context) => {
const instances = context.getData({ requiredOnly: removeOptionals });
return instances.map((instance) => ({ instance, context }));
};
}
function createValidityFilter(onlyValidData = true) {
return !onlyValidData
? () => true
: (field) => {
const hasError = field.context.errors.some(error => error.type === 'field' &&
error.location === field.instance.location &&
error.path === field.instance.path);
return !hasError;
};
}
function createLocationFilter(locations = []) {
// No locations mean all locations
const allLocations = locations.length === 0;
return allLocations ? () => true : (field) => locations.includes(field.location);
}

View File

@ -0,0 +1,3 @@
import { ErrorMessage, FieldMessageFactory, Location } from '../base';
import { ValidationChain } from '../chain';
export declare function check(fields?: string | string[], locations?: Location[], message?: FieldMessageFactory | ErrorMessage): ValidationChain;

View File

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.check = check;
const chain_1 = require("../chain");
const context_builder_1 = require("../context-builder");
const utils_1 = require("../utils");
function check(fields = '', locations = [], message) {
const builder = new context_builder_1.ContextBuilder()
.setFields(Array.isArray(fields) ? fields : [fields])
.setLocations(locations)
.setMessage(message);
const runner = new chain_1.ContextRunnerImpl(builder);
const middleware = async (req, _res, next) => {
try {
await runner.run(req);
next();
}
catch (e) {
next(e);
}
};
return Object.assign(middleware, (0, utils_1.bindAll)(runner), (0, utils_1.bindAll)(new chain_1.SanitizersImpl(builder, middleware)), (0, utils_1.bindAll)(new chain_1.ValidatorsImpl(builder, middleware)), (0, utils_1.bindAll)(new chain_1.ContextHandlerImpl(builder, middleware)), { builder });
}

View File

@ -0,0 +1,29 @@
import { ErrorMessage, Location, Middleware, UnknownFieldMessageFactory } from '../base';
import { ContextRunner, ValidationChain } from '../chain';
type CheckExactOptions = {
/**
* The list of locations which `checkExact()` should check.
* @default ['body', 'params', 'query']
*/
locations?: readonly Location[];
message?: UnknownFieldMessageFactory | ErrorMessage;
};
type CheckExactInput = ValidationChain | ValidationChain[] | (ValidationChain | ValidationChain[])[];
/**
* Checks whether the request contains exactly only those fields that have been validated.
*
* Unknown fields, if found, will generate an error of type `unknown_fields`.
*
* @param chains either a single chain, an array of chains, or a mixed array of chains and array of chains.
* This means that all of the below are valid:
* ```
* checkExact(check('foo'))
* checkExact([check('foo'), check('bar')])
* checkExact([check('foo'), check('bar')])
* checkExact(checkSchema({ ... }))
* checkExact([checkSchema({ ... }), check('foo')])
* ```
* @param opts
*/
export declare function checkExact(chains?: CheckExactInput, opts?: CheckExactOptions): Middleware & ContextRunner;
export {};

View File

@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkExact = checkExact;
const base_1 = require("../base");
const chain_1 = require("../chain");
const context_1 = require("../context");
const field_selection_1 = require("../field-selection");
const utils_1 = require("../utils");
/**
* Checks whether the request contains exactly only those fields that have been validated.
*
* Unknown fields, if found, will generate an error of type `unknown_fields`.
*
* @param chains either a single chain, an array of chains, or a mixed array of chains and array of chains.
* This means that all of the below are valid:
* ```
* checkExact(check('foo'))
* checkExact([check('foo'), check('bar')])
* checkExact([check('foo'), check('bar')])
* checkExact(checkSchema({ ... }))
* checkExact([checkSchema({ ... }), check('foo')])
* ```
* @param opts
*/
function checkExact(chains, opts) {
// Don't include all locations by default. Browsers will add cookies and headers that the user
// might not want to validate, which would be a footgun.
const locations = opts?.locations || ['body', 'params', 'query'];
const chainsArr = Array.isArray(chains) ? chains.flat() : chains ? [chains] : [];
const run = async (req) => {
const internalReq = req;
const fieldsByLocation = new Map();
await (0, utils_1.runAllChains)(req, chainsArr);
// The chains above will have added contexts to the request
(internalReq[base_1.contextsKey] || []).forEach(context => {
context.locations.forEach(location => {
if (!locations.includes(location)) {
return;
}
const locationFields = fieldsByLocation.get(location) || [];
locationFields.push(...context.fields);
fieldsByLocation.set(location, locationFields);
});
});
// when none of the chains matched anything, then everything is unknown.
if (!fieldsByLocation.size) {
locations.forEach(location => fieldsByLocation.set(location, []));
}
let unknownFields = [];
for (const [location, fields] of fieldsByLocation.entries()) {
unknownFields = unknownFields.concat((0, field_selection_1.selectUnknownFields)(req, fields, [location]));
}
const context = new context_1.Context([], [], [], false, false);
if (unknownFields.length) {
context.addError({
type: 'unknown_fields',
req,
message: opts?.message || 'Unknown field(s)',
fields: unknownFields,
});
}
internalReq[base_1.contextsKey] = internalReq[base_1.contextsKey] || [];
internalReq[base_1.contextsKey].push(context);
return new chain_1.ResultWithContextImpl(context);
};
const middleware = (req, _res, next) => run(req).then(() => next(), next);
return Object.assign(middleware, { run });
}

View File

@ -0,0 +1,28 @@
import { AlternativeMessageFactory, ErrorMessage, GroupedAlternativeMessageFactory, Middleware } from '../base';
import { ContextRunner, ValidationChain } from '../chain';
export type OneOfErrorType = 'grouped' | 'least_errored' | 'flat';
export type OneOfOptions = {
/**
* The error message to use in case none of the chains are valid.
*/
message?: AlternativeMessageFactory | ErrorMessage;
errorType?: Exclude<OneOfErrorType, 'grouped'>;
} | {
/**
* The error message to use in case none of the chain groups are valid.
*/
message?: GroupedAlternativeMessageFactory | ErrorMessage;
errorType?: 'grouped';
};
/**
* Creates a middleware that will ensure that at least one of the given validation chains
* or validation chain groups are valid.
*
* If none are, a single `AlternativeValidationError` or `GroupedAlternativeValidationError`
* is added to the request, with the errors of each chain made available under the `nestedErrors` property.
*
* @param chains an array of validation chains to check if are valid.
* If any of the items of `chains` is an array of validation chains, then all of them
* must be valid together for the request to be considered valid.
*/
export declare function oneOf(chains: (ValidationChain | ValidationChain[])[], options?: OneOfOptions): Middleware & ContextRunner;

View File

@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.oneOf = oneOf;
const _ = require("lodash");
const chain_1 = require("../chain");
const context_builder_1 = require("../context-builder");
const utils_1 = require("../utils");
// A dummy context item that gets added to surrogate contexts just to make them run
const dummyItem = { async run() { } };
/**
* Creates a middleware that will ensure that at least one of the given validation chains
* or validation chain groups are valid.
*
* If none are, a single `AlternativeValidationError` or `GroupedAlternativeValidationError`
* is added to the request, with the errors of each chain made available under the `nestedErrors` property.
*
* @param chains an array of validation chains to check if are valid.
* If any of the items of `chains` is an array of validation chains, then all of them
* must be valid together for the request to be considered valid.
*/
function oneOf(chains, options = {}) {
const run = async (req, opts) => {
const surrogateContext = new context_builder_1.ContextBuilder().addItem(dummyItem).build();
// Run each group of chains in parallel
const promises = chains.map(async (chain) => {
const group = Array.isArray(chain) ? chain : [chain];
const results = await (0, utils_1.runAllChains)(req, group, { dryRun: true });
const { contexts, groupErrors } = results.reduce(({ contexts, groupErrors }, result) => {
const { context } = result;
contexts.push(context);
const fieldErrors = context.errors.filter((error) => error.type === 'field');
groupErrors.push(...fieldErrors);
return { contexts, groupErrors };
}, {
contexts: [],
groupErrors: [],
});
// #536: The data from a chain within oneOf() can only be made available to e.g. matchedData()
// if its entire group is valid.
if (!groupErrors.length) {
contexts.forEach(context => {
surrogateContext.addFieldInstances(context.getData());
});
}
return groupErrors;
});
const allErrors = await Promise.all(promises);
const success = allErrors.some(groupErrors => groupErrors.length === 0);
if (!success) {
const message = options.message || 'Invalid value(s)';
switch (options.errorType) {
case 'flat':
surrogateContext.addError({
type: 'alternative',
req,
message,
nestedErrors: _.flatMap(allErrors),
});
break;
case 'least_errored':
let leastErroredIndex = 0;
for (let i = 1; i < allErrors.length; i++) {
if (allErrors[i].length < allErrors[leastErroredIndex].length) {
leastErroredIndex = i;
}
}
surrogateContext.addError({
type: 'alternative',
req,
message,
nestedErrors: allErrors[leastErroredIndex],
});
break;
case 'grouped':
default:
// grouped
surrogateContext.addError({
type: 'alternative_grouped',
req,
message,
nestedErrors: allErrors,
});
break;
}
}
// Final context running pass to ensure contexts are added and values are modified properly
return await new chain_1.ContextRunnerImpl(surrogateContext).run(req, opts);
};
const middleware = (req, _res, next) => run(req).then(() => next(), next);
return Object.assign(middleware, { run });
}

View File

@ -0,0 +1,108 @@
import { CustomSanitizer, CustomValidator, ErrorMessage, FieldMessageFactory, Location, Request } from '../base';
import { BailOptions, OptionalOptions, ValidationChain, ValidationChainLike } from '../chain';
import { ResultWithContext } from '../chain/context-runner';
import { Sanitizers } from '../chain/sanitizers';
import { Validators } from '../chain/validators';
type BaseValidatorSchemaOptions = {
/**
* The error message if there's a validation error,
* or a function for creating an error message dynamically.
*/
errorMessage?: FieldMessageFactory | ErrorMessage;
/**
* Whether the validation should be reversed.
*/
negated?: boolean;
/**
* Whether the validation should bail after running this validator
*/
bail?: boolean | BailOptions;
/**
* Specify a condition upon which this validator should run.
* Can either be a validation chain, or a custom validator function.
*/
if?: CustomValidator | ValidationChain;
};
type ValidatorSchemaOptions<K extends keyof Validators<any>> = boolean | (BaseValidatorSchemaOptions & {
/**
* Options to pass to the validator.
*/
options?: Parameters<Validators<any>[K]> | Parameters<Validators<any>[K]>[0];
});
type CustomValidatorSchemaOptions = BaseValidatorSchemaOptions & {
/**
* The implementation of a custom validator.
*/
custom: CustomValidator;
};
export type ExtensionValidatorSchemaOptions = boolean | BaseValidatorSchemaOptions;
export type ValidatorsSchema = {
[K in Exclude<keyof Validators<any>, 'not' | 'withMessage'>]?: ValidatorSchemaOptions<K>;
};
type SanitizerSchemaOptions<K extends keyof Sanitizers<any>> = boolean | {
/**
* Options to pass to the sanitizer.
*/
options?: Parameters<Sanitizers<any>[K]> | Parameters<Sanitizers<any>[K]>[0];
};
type CustomSanitizerSchemaOptions = {
/**
* The implementation of a custom sanitizer.
*/
customSanitizer: CustomSanitizer;
};
export type ExtensionSanitizerSchemaOptions = true;
export type SanitizersSchema = {
[K in keyof Sanitizers<any>]?: SanitizerSchemaOptions<K>;
};
type BaseParamSchema = {
/**
* Which request location(s) the field to validate is.
* If unset, the field will be checked in every request location.
*/
in?: Location | Location[];
/**
* The general error message in case a validator doesn't specify one,
* or a function for creating the error message dynamically.
*/
errorMessage?: FieldMessageFactory | any;
/**
* Whether this field should be considered optional
*/
optional?: boolean | {
options?: OptionalOptions;
};
};
export type DefaultSchemaKeys = keyof BaseParamSchema | keyof ValidatorsSchema | keyof SanitizersSchema;
/**
* Defines a schema of validations/sanitizations for a field
*/
export type ParamSchema<T extends string = DefaultSchemaKeys> = BaseParamSchema & ValidatorsSchema & SanitizersSchema & {
[K in T]?: K extends keyof BaseParamSchema ? BaseParamSchema[K] : K extends keyof ValidatorsSchema ? ValidatorsSchema[K] : K extends keyof SanitizersSchema ? SanitizersSchema[K] : CustomValidatorSchemaOptions | CustomSanitizerSchemaOptions;
};
/**
* Defines a mapping from field name to a validations/sanitizations schema.
*/
export type Schema<T extends string = DefaultSchemaKeys> = Record<string, ParamSchema<T>>;
/**
* Shortcut type for the return of a {@link checkSchema()}-like function.
*/
export type RunnableValidationChains<C extends ValidationChainLike> = C[] & {
run(req: Request): Promise<ResultWithContext[]>;
};
/**
* Factory for a {@link checkSchema()} function which can have extension validators and sanitizers.
*
* @see {@link checkSchema()}
*/
export declare function createCheckSchema<C extends ValidationChainLike>(createChain: (fields?: string | string[], locations?: Location[], errorMessage?: any) => C, extraValidators?: (keyof C)[], extraSanitizers?: (keyof C)[]): <T extends string = DefaultSchemaKeys>(schema: Schema<T>, defaultLocations?: Location[]) => RunnableValidationChains<C>;
/**
* Creates an express middleware with validations for multiple fields at once in the form of
* a schema object.
*
* @param schema the schema to validate.
* @param defaultLocations
* @returns
*/
export declare const checkSchema: <T extends string = DefaultSchemaKeys>(schema: Schema<T>, defaultLocations?: Location[]) => RunnableValidationChains<ValidationChain>;
export {};

View File

@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkSchema = void 0;
exports.createCheckSchema = createCheckSchema;
const _ = require("lodash");
const chain_1 = require("../chain");
const utils_1 = require("../utils");
const check_1 = require("./check");
const validLocations = ['body', 'cookies', 'headers', 'params', 'query'];
const protectedNames = ['errorMessage', 'in', 'optional'];
/**
* Factory for a {@link checkSchema()} function which can have extension validators and sanitizers.
*
* @see {@link checkSchema()}
*/
function createCheckSchema(createChain, extraValidators = [], extraSanitizers = []) {
/** Type guard for an object entry for a standard validator. */
function isStandardValidator(entry) {
return (
// #664 - explicitly exclude properties which should be set per validator
!['not', 'withMessage'].includes(entry[0]) &&
(entry[0] in chain_1.ValidatorsImpl.prototype || extraValidators.includes(entry[0])) &&
entry[1]);
}
/** Type guard for an object entry for a standard sanitizer. */
function isStandardSanitizer(entry) {
return ((entry[0] in chain_1.SanitizersImpl.prototype || extraSanitizers.includes(entry[0])) &&
entry[1]);
}
/** Type guard for an object entry for a custom validator. */
function isCustomValidator(entry) {
return (!isStandardValidator(entry) &&
!isStandardSanitizer(entry) &&
typeof entry[1] === 'object' &&
entry[1] &&
typeof entry[1].custom === 'function');
}
/** Type guard for an object entry for a custom sanitizer. */
function isCustomSanitizer(entry) {
return (!isStandardValidator(entry) &&
!isStandardSanitizer(entry) &&
typeof entry[1] === 'object' &&
entry[1] &&
typeof entry[1].customSanitizer === 'function');
}
return (schema, defaultLocations = validLocations) => {
const chains = Object.keys(schema).map(field => {
const config = schema[field];
const chain = createChain(field, ensureLocations(config, defaultLocations), config.errorMessage);
// optional doesn't matter where it happens in the chain
if (config.optional) {
chain.optional(config.optional === true ? true : config.optional.options);
}
for (const entry of Object.entries(config)) {
if (protectedNames.includes(entry[0]) || !entry[1]) {
continue;
}
if (!isStandardValidator(entry) &&
!isStandardSanitizer(entry) &&
!isCustomValidator(entry) &&
!isCustomSanitizer(entry)) {
console.warn(`express-validator: schema of "${field}" has unknown validator/sanitizer "${entry[0]}"`);
continue;
}
// For validators, stuff that must come _before_ the validator itself in the chain.
if ((isStandardValidator(entry) || isCustomValidator(entry)) && entry[1] !== true) {
const [, validatorConfig] = entry;
validatorConfig.if && chain.if(validatorConfig.if);
validatorConfig.negated && chain.not();
}
if (isStandardValidator(entry) || isStandardSanitizer(entry)) {
const options = entry[1] ? (entry[1] === true ? [] : _.castArray(entry[1].options)) : [];
chain[entry[0]](...options);
}
if (isCustomValidator(entry)) {
chain.custom(entry[1].custom);
}
if (isCustomSanitizer(entry)) {
chain.customSanitizer(entry[1].customSanitizer);
}
// For validators, stuff that must come _after_ the validator itself in the chain.
if ((isStandardValidator(entry) || isCustomValidator(entry)) && entry[1] !== true) {
const [, validatorConfig] = entry;
validatorConfig.bail &&
chain.bail(validatorConfig.bail === true ? {} : validatorConfig.bail);
validatorConfig.errorMessage && chain.withMessage(validatorConfig.errorMessage);
}
}
return chain;
});
const run = async (req) => (0, utils_1.runAllChains)(req, chains);
return Object.assign(chains, { run });
};
}
/**
* Creates an express middleware with validations for multiple fields at once in the form of
* a schema object.
*
* @param schema the schema to validate.
* @param defaultLocations
* @returns
*/
exports.checkSchema = createCheckSchema(check_1.check);
function ensureLocations(config, defaults) {
// .filter(Boolean) is done because in can be undefined -- which is not going away from the type
// See https://github.com/Microsoft/TypeScript/pull/29955 for details
const locations = Array.isArray(config.in)
? config.in
: [config.in].filter(Boolean);
const actualLocations = locations.length ? locations : defaults;
return actualLocations.filter(location => validLocations.includes(location));
}

View File

@ -0,0 +1,43 @@
import { ErrorMessage, FieldMessageFactory, Location } from '../base';
/**
* Creates a variant of `check()` that checks the given request locations.
*
* @example
* const checkBodyAndQuery = buildCheckFunction(['body', 'query']);
*/
export declare function buildCheckFunction(locations: Location[]): (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Creates a middleware/validation chain for one or more fields that may be located in
* any of the following:
*
* - `req.body`
* - `req.cookies`
* - `req.headers`
* - `req.params`
* - `req.query`
*
* @param fields a string or array of field names to validate/sanitize
* @param message an error message to use when failed validations don't specify a custom message.
* Defaults to `Invalid Value`.
*/
export declare const check: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Same as {@link check()}, but only validates `req.body`.
*/
export declare const body: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Same as {@link check()}, but only validates `req.cookies`.
*/
export declare const cookie: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Same as {@link check()}, but only validates `req.headers`.
*/
export declare const header: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Same as {@link check()}, but only validates `req.params`.
*/
export declare const param: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;
/**
* Same as {@link check()}, but only validates `req.query`.
*/
export declare const query: (fields?: string | string[], message?: FieldMessageFactory | ErrorMessage) => import("..").ValidationChain;

View File

@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.query = exports.param = exports.header = exports.cookie = exports.body = exports.check = void 0;
exports.buildCheckFunction = buildCheckFunction;
const check_1 = require("./check");
/**
* Creates a variant of `check()` that checks the given request locations.
*
* @example
* const checkBodyAndQuery = buildCheckFunction(['body', 'query']);
*/
function buildCheckFunction(locations) {
return (fields, message) => (0, check_1.check)(fields, locations, message);
}
/**
* Creates a middleware/validation chain for one or more fields that may be located in
* any of the following:
*
* - `req.body`
* - `req.cookies`
* - `req.headers`
* - `req.params`
* - `req.query`
*
* @param fields a string or array of field names to validate/sanitize
* @param message an error message to use when failed validations don't specify a custom message.
* Defaults to `Invalid Value`.
*/
exports.check = buildCheckFunction(['body', 'cookies', 'headers', 'params', 'query']);
/**
* Same as {@link check()}, but only validates `req.body`.
*/
exports.body = buildCheckFunction(['body']);
/**
* Same as {@link check()}, but only validates `req.cookies`.
*/
exports.cookie = buildCheckFunction(['cookies']);
/**
* Same as {@link check()}, but only validates `req.headers`.
*/
exports.header = buildCheckFunction(['headers']);
/**
* Same as {@link check()}, but only validates `req.params`.
*/
exports.param = buildCheckFunction(['params']);
/**
* Same as {@link check()}, but only validates `req.query`.
*/
exports.query = buildCheckFunction(['query']);

View File

@ -0,0 +1,352 @@
export type URLProtocol = 'http' | 'https' | 'ftp' | string;
export type UUIDVersion = 1 | 2 | 3 | 4 | 5 | 7 | '1' | '2' | '3' | '4' | '5' | '7' | 'all';
export type IPVersion = 4 | 6;
export type AlphaLocale = 'ar' | 'ar-AE' | 'ar-BH' | 'ar-DZ' | 'ar-EG' | 'ar-IQ' | 'ar-JO' | 'ar-KW' | 'ar-LB' | 'ar-LY' | 'ar-MA' | 'ar-QA' | 'ar-QM' | 'ar-SA' | 'ar-SD' | 'ar-SY' | 'ar-TN' | 'ar-YE' | 'az-AZ' | 'bg-BG' | 'bn-BD' | 'cs-CZ' | 'da-DK' | 'de-DE' | 'el-GR' | 'en-AU' | 'en-GB' | 'en-HK' | 'en-IN' | 'en-NZ' | 'en-US' | 'en-ZA' | 'en-ZM' | 'es-ES' | 'eo' | 'fa-AF' | 'fa-IR' | 'fi-FI' | 'fr-FR' | 'he' | 'hi-IN' | 'hu-HU' | 'id-ID' | 'it-IT' | 'ja-JP' | 'kk-KZ' | 'ko-KR' | 'ku-IQ' | 'nb-NO' | 'nl-NL' | 'nn-NO' | 'pl-PL' | 'pt-BR' | 'pt-PT' | 'ru-RU' | 'si-LK' | 'sk-SK' | 'sl-SI' | 'sr-RS' | 'sr-RS@latin' | 'sv-SE' | 'th-TH' | 'tr-TR' | 'uk-UA' | 'vi-VN';
export type AlphanumericLocale = 'ar' | 'ar-AE' | 'ar-BH' | 'ar-DZ' | 'ar-EG' | 'ar-IQ' | 'ar-JO' | 'ar-KW' | 'ar-LB' | 'ar-LY' | 'ar-MA' | 'ar-QA' | 'ar-QM' | 'ar-SA' | 'ar-SD' | 'ar-SY' | 'ar-TN' | 'ar-YE' | 'az-AZ' | 'bg-BG' | 'bn-BD' | 'cs-CZ' | 'da-DK' | 'de-DE' | 'el-GR' | 'en-AU' | 'en-GB' | 'en-HK' | 'en-IN' | 'en-NZ' | 'en-US' | 'en-ZA' | 'en-ZM' | 'es-ES' | 'eo' | 'fa-AF' | 'fa-IR' | 'fi-FI' | 'fr-FR' | 'fr-BE' | 'he' | 'hi-IN' | 'hu-HU' | 'it-IT' | 'id-ID' | 'ja-JP' | 'kk-KZ' | 'ko-KR' | 'ku-IQ' | 'nb-NO' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'pl-PL' | 'pt-BR' | 'pt-PT' | 'ru-RU' | 'si-LK' | 'sk-SK' | 'sl-SI' | 'sr-RS' | 'sr-RS@latin' | 'sv-SE' | 'th-TH' | 'tr-TR' | 'uk-UA' | 'vi-VN';
export type MobilePhoneLocale = 'any' | 'am-AM' | 'ar-AE' | 'ar-BH' | 'ar-DZ' | 'ar-EG' | 'ar-EH' | 'ar-IQ' | 'ar-JO' | 'ar-KW' | 'ar-LB' | 'ar-LY' | 'ar-MA' | 'ar-OM' | 'ar-PS' | 'ar-SA' | 'ar-SD' | 'ar-SY' | 'ar-TN' | 'ar-YE' | 'az-AZ' | 'be-BY' | 'bg-BG' | 'bn-BD' | 'bs-BA' | 'cs-CZ' | 'de-AT' | 'de-CH' | 'de-DE' | 'de-LU' | 'da-DK' | 'dv-MV' | 'dz-BT' | 'el-CY' | 'el-GR' | 'en-AG' | 'en-AI' | 'en-AU' | 'en-BM' | 'en-BS' | 'en-BW' | 'en-CA' | 'en-GB' | 'en-GG' | 'en-GH' | 'en-GY' | 'en-HK' | 'en-HN' | 'en-IE' | 'en-IN' | 'en-JM' | 'en-KE' | 'en-KI' | 'en-KN' | 'en-LS' | 'en-MT' | 'en-MU' | 'en-MW' | 'en-NA' | 'en-NG' | 'en-NZ' | 'en-PG' | 'en-PH' | 'en-PK' | 'en-RW' | 'en-SG' | 'en-SL' | 'en-SS' | 'en-TZ' | 'en-UG' | 'en-US' | 'en-ZA' | 'en-ZM' | 'en-ZW' | 'es-AR' | 'es-BO' | 'es-CL' | 'es-CO' | 'es-CR' | 'es-CU' | 'es-DO' | 'es-EC' | 'es-ES' | 'es-HN' | 'es-MX' | 'es-NI' | 'es-PA' | 'es-PE' | 'es-PY' | 'es-SV' | 'es-UY' | 'es-VE' | 'et-EE' | 'fa-AF' | 'fa-IR' | 'fi-FI' | 'fj-FJ' | 'fo-FO' | 'fr-BE' | 'fr-BF' | 'fr-BJ' | 'fr-CD' | 'fr-CF' | 'fr-CH' | 'fr-CM' | 'fr-FR' | 'fr-GF' | 'fr-GP' | 'fr-MQ' | 'fr-PF' | 'fr-RE' | 'fr-WF' | 'ga-IE' | 'he-IL' | 'hu-HU' | 'id-ID' | 'ir-IR' | 'it-CH' | 'it-IT' | 'it-SM' | 'ja-JP' | 'ka-GE' | 'kk-KZ' | 'kl-GL' | 'ko-KR' | 'ky-KG' | 'lt-LT' | 'lv-LV' | 'mg-MG' | 'mn-MN' | 'ms-MY' | 'my-MM' | 'mz-MZ' | 'nb-NO' | 'nl-AW' | 'nl-BE' | 'nl-NL' | 'ne-NP' | 'nn-NO' | 'pl-PL' | 'pt-AO' | 'pt-BR' | 'pt-PT' | 'ro-MD' | 'ro-RO' | 'ru-RU' | 'si-LK' | 'sk-SK' | 'sl-SI' | 'so-SO' | 'sq-AL' | 'sr-RS' | 'sv-SE' | 'tg-TJ' | 'th-TH' | 'tk-TM' | 'tr-TR' | 'uk-UA' | 'uz-Uz' | 'vi-VN' | 'zh-CN' | 'zh-HK' | 'zh-TW';
export type PostalCodeLocale = 'any' | 'AD' | 'AT' | 'AU' | 'AZ' | 'BA' | 'BE' | 'BG' | 'BR' | 'BY' | 'CA' | 'CH' | 'CN' | 'CZ' | 'DE' | 'DK' | 'DO' | 'DZ' | 'EE' | 'ES' | 'FI' | 'FR' | 'GB' | 'GR' | 'HR' | 'HT' | 'HU' | 'ID' | 'IL' | 'IN' | 'IR' | 'IS' | 'IT' | 'JP' | 'KE' | 'KR' | 'LI' | 'LK' | 'LT' | 'LU' | 'LV' | 'MT' | 'MX' | 'MY' | 'NL' | 'NO' | 'NP' | 'NZ' | 'PL' | 'PR' | 'PT' | 'RO' | 'RU' | 'SA' | 'SE' | 'SG' | 'SI' | 'SK' | 'TH' | 'TN' | 'TW' | 'UA' | 'US' | 'ZA' | 'ZM';
export type HashAlgorithm = 'md4' | 'md5' | 'sha1' | 'sha256' | 'sha384' | 'sha512' | 'ripemd128' | 'ripemd160' | 'tiger128' | 'tiger160' | 'tiger192' | 'crc32' | 'crc32b';
export type IBANCode = 'AD' | 'AE' | 'AL' | 'AT' | 'AZ' | 'BA' | 'BE' | 'BG' | 'BH' | 'BR' | 'BY' | 'CH' | 'CR' | 'CY' | 'CZ' | 'DE' | 'DK' | 'DO' | 'DZ' | 'EE' | 'EG' | 'ES' | 'FI' | 'FO' | 'FR' | 'GB' | 'GE' | 'GI' | 'GL' | 'GR' | 'GT' | 'HR' | 'HU' | 'IE' | 'IL' | 'IQ' | 'IR' | 'IS' | 'IT' | 'JO' | 'KW' | 'KZ' | 'LB' | 'LC' | 'LI' | 'LT' | 'LU' | 'LV' | 'MC' | 'MD' | 'ME' | 'MK' | 'MR' | 'MT' | 'MU' | 'MZ' | 'NL' | 'NO' | 'PK' | 'PL' | 'PS' | 'PT' | 'QA' | 'RO' | 'RS' | 'SA' | 'SC' | 'SE' | 'SI' | 'SK' | 'SM' | 'SV' | 'TL' | 'TN' | 'TR' | 'UA' | 'VA' | 'VG' | 'XK';
export interface IsIBANOptions {
whitelist?: readonly IBANCode[];
blacklist?: readonly IBANCode[];
}
export type IdentityCardLocale = 'any' | 'ar-LY' | 'ar-TN' | 'ES' | 'FI' | 'he-IL' | 'hk-HK' | 'IN' | 'IT' | 'IR' | 'MZ' | 'NO' | 'PL' | 'TH' | 'zh-CN' | 'zh-TW';
export type PassportCountryCode = 'AM' | 'AR' | 'AT' | 'AU' | 'AZ' | 'BE' | 'BG' | 'BY' | 'BR' | 'CA' | 'CH' | 'CN' | 'CY' | 'CZ' | 'DE' | 'DK' | 'DZ' | 'EE' | 'ES' | 'FI' | 'FR' | 'GB' | 'GR' | 'HR' | 'HU' | 'ID' | 'IE' | 'IN' | 'IR' | 'IS' | 'IT' | 'JM' | 'JP' | 'KR' | 'KZ' | 'LI' | 'LT' | 'LU' | 'LV' | 'LY' | 'MT' | 'MY' | 'MZ' | 'NL' | 'NZ' | 'PH' | 'PK' | 'PL' | 'PO' | 'PT' | 'RO' | 'RU' | 'SE' | 'SL' | 'SK' | 'TH' | 'TR' | 'UA' | 'US' | 'ZA';
export type IsLicensePlateLocale = 'cs-CZ' | 'de-DE' | 'de-LI' | 'en-NI' | 'en-PK' | 'es-AR' | 'fi-FI' | 'hu-HU' | 'pt-BR' | 'pt-PT' | 'sq-AL' | 'sv-SE' | 'any';
export type TaxIDLocale = 'bg-BG' | 'cs-CZ' | 'de-AT' | 'de-DE' | 'dk-DK' | 'el-CY' | 'el-GR' | 'en-CA' | 'en-GB' | 'en-IE' | 'en-US' | 'es-AR' | 'es-ES' | 'et-EE' | 'fi-FI' | 'fr-BE' | 'fr-FR' | 'fr-LU' | 'hr-HR' | 'hu-HU' | 'it-IT' | 'lb-LU' | 'lt-LT' | 'lv-LV' | 'mt-MT' | 'nl-BE' | 'nl-NL' | 'pl-PL' | 'pt-BR' | 'pt-PT' | 'ro-RO' | 'sk-SK' | 'sl-SI' | 'sv-SE' | 'uk-UA';
export type VATCountryCode = 'GB' | 'IT' | 'NL' | 'AT' | 'BE' | 'BG' | 'HR' | 'CU' | 'CY' | 'CZ' | 'DK' | 'EE' | 'FI' | 'FR' | 'DE' | 'EL' | 'HU' | 'IE' | 'LV' | 'LT' | 'LU' | 'MT' | 'PL' | 'PT' | 'RO' | 'SK' | 'SI' | 'ES' | 'SE' | 'AL' | 'MK' | 'AU' | 'BY' | 'CA' | 'IS' | 'IN' | 'ID' | 'IL' | 'KZ' | 'NZ' | 'NG' | 'NO' | 'PH' | 'RU' | 'SM' | 'SA' | 'RS' | 'CH' | 'TR' | 'UA' | 'UZ' | 'AR' | 'BO' | 'BR' | 'CL' | 'CO' | 'CR' | 'EC' | 'SV' | 'GT' | 'HN' | 'MX' | 'NI' | 'PA' | 'PY' | 'PE' | 'DO' | 'UY' | 'VE';
export interface MinMaxOptions {
min?: number;
max?: number;
}
export interface MinMaxExtendedOptions extends MinMaxOptions {
lt?: number;
gt?: number;
}
/**
* defaults to
* {
* ignoreCase: false|
* minOccurrences: 1
* }
*/
export interface ContainsOptions {
ignoreCase?: boolean;
minOccurrences?: number;
}
/**
* defaults to
* {
* comparisonDate: Date().toString()
* }
*/
export interface IsAfterOptions {
comparisonDate?: string;
}
export interface IsAlphaOptions {
ignore?: string | string[] | RegExp;
}
export interface IsAlphanumericOptions {
ignore?: string | RegExp;
}
/**
* defaults to
* {
* crockford: false
* }
*/
export interface IsBase32Options {
crockford?: boolean;
}
/**
* defaults to
* {
* urlSafe: false
* }
*/
export interface IsBase64Options {
urlSafe?: boolean;
}
/**
* defaults to
* {
* strict: false
* loose: false
* }
*/
export interface IsBooleanOptions {
strict?: boolean;
loose?: boolean;
}
export interface IsCreditCard {
provider?: 'amex' | 'dinersclub' | 'discover' | 'jcb' | 'mastercard' | 'unionpay' | 'visa';
}
/**
* defaults to
* {
* symbol: '$'|
* require_symbol: false|
* allow_space_after_symbol: false|
* symbol_after_digits: false|
* allow_negatives: true|
* parens_for_negatives: false|
* negative_sign_before_digits: false|
* negative_sign_after_digits: false|
* allow_negative_sign_placeholder: false|
* thousands_separator: '|'|
* decimal_separator: '.'|
* allow_space_after_digits: false
* }
*/
export interface IsCurrencyOptions {
symbol?: string;
require_symbol?: boolean;
allow_space_after_symbol?: boolean;
symbol_after_digits?: boolean;
allow_negatives?: boolean;
parens_for_negatives?: boolean;
negative_sign_before_digits?: boolean;
negative_sign_after_digits?: boolean;
allow_negative_sign_placeholder?: boolean;
thousands_separator?: string;
decimal_separator?: string;
allow_decimal?: boolean;
require_decimal?: boolean;
digits_after_decimal?: number[];
allow_space_after_digits?: boolean;
}
/**
* defaults to
* {
* format: 'YYYY/MM/DD'|
* delimiters: ['/'| '-']|
* strictMode: false
* }
*/
export interface IsDateOptions {
format?: string;
delimiters?: string[];
strictMode?: boolean;
}
export interface IsDecimalOptions {
decimal_digits?: string;
force_decimal?: boolean;
locale?: AlphanumericLocale;
blacklisted_chars?: string;
}
export interface IsEmailOptions {
allow_display_name?: boolean;
allow_underscores?: boolean;
allow_utf8_local_part?: boolean;
require_tld?: boolean;
ignore_max_length?: boolean;
allow_ip_domain?: boolean;
domain_specific_validation?: boolean;
blacklisted_chars?: string;
host_blacklist?: string[];
host_whitelist?: string[];
}
/**
* defaults to
* {
* ignore_whitespace: false
* }
*/
export interface IsEmptyOptions {
ignore_whitespace: boolean;
}
export interface IsFloatOptions extends MinMaxExtendedOptions {
locale?: AlphanumericLocale;
}
/**
* defaults to
* {
* require_tld: true|
* allow_underscores: false|
* allow_trailing_dot: false|
* allow_numeric_tld: false|
* allow_wildcard: false|
* ignore_max_length: false
* }
*/
export interface IsFQDNOptions {
require_tld?: boolean;
allow_underscores?: boolean;
allow_trailing_dot?: boolean;
allow_numeric_tld?: boolean;
allow_wildcard?: boolean;
ignore_max_length?: boolean;
}
export interface IsIntOptions extends MinMaxExtendedOptions {
allow_leading_zeroes?: boolean;
}
/**
* defaults to
* {
* allow_primitives: false
* }
*/
export interface IsJSONOptions {
allow_primitives?: boolean;
}
/**
* defaults to
* {
* checkDMS: false
* }
*/
export interface IsLatLongOptions {
checkDMS?: boolean;
}
/**
* defaults to
* {
* allow_hyphens: false
* }
*/
export interface IsIMEIOptions {
allow_hyphens?: boolean;
}
/**
* defaults to
* {
* strict: false|
* strictSeparator: false
* }
*/
export interface IsISO8601Options {
strict?: boolean;
strictSeparator?: boolean;
}
export interface IsISBNOptions {
version?: '10' | '13';
}
/**
* defaults to
* {
* case_sensitive: false|
* require_hyphen: false
* }
*/
export interface IsISSNOptions {
case_sensitive?: boolean;
require_hyphen?: boolean;
}
/**
* defaults to
* ```js
* {
* no_separators: false
* }
* ```
*/
export interface IsMACAddressOptions {
no_separators?: boolean;
/**
* @deprecated use `no_separators` instead
*/
no_colons?: boolean;
eui?: '48' | '64';
}
export interface IsMobilePhoneOptions {
strictMode?: boolean;
}
/**
* defaults to
* {
* no_symbols: false
* }
*/
export interface IsNumericOptions {
no_symbols: boolean;
locale?: AlphanumericLocale;
}
/**
* defaults to
* {
* minLength: 8|
* minLowercase: 1|
* minUppercase: 1|
* minNumbers: 1|
* minSymbols: 1|
* returnScore: false|
* pointsPerUnique: 1|
* pointsPerRepeat: 0.5|
* pointsForContainingLower: 10|
* pointsForContainingUpper: 10|
* pointsForContainingNumber: 10|
* pointsForContainingSymbol: 10
* }
*/
export interface IsStrongPasswordOptions {
minLength?: number;
minLowercase?: number;
minUppercase?: number;
minNumbers?: number;
minSymbols?: number;
returnScore?: boolean;
pointsPerUnique?: number;
pointsPerRepeat?: number;
pointsForContainingLower?: number;
pointsForContainingUpper?: number;
pointsForContainingNumber?: number;
pointsForContainingSymbol?: number;
}
/**
* defaults to
* {
* protocols: ['http'|'https'|'ftp']|
* require_tld: true|
* require_protocol: false|
* require_host: true|
* require_port: false;
* require_valid_protocol: true|
* allow_underscores: false|
* host_whitelist: false|
* host_blacklist: false|
* allow_trailing_dot: false|
* allow_protocol_relative_urls: false|
* validate_length: true|
* allow_fragments: true|
* allow_query_components: true
* }
*/
export interface IsURLOptions extends IsFQDNOptions {
protocols?: URLProtocol[];
require_protocol?: boolean;
require_host?: boolean;
require_port?: boolean;
require_valid_protocol?: boolean;
host_whitelist?: (string | RegExp)[];
host_blacklist?: (string | RegExp)[];
allow_protocol_relative_urls?: boolean;
disallow_auth?: boolean;
validate_length?: boolean;
allow_fragments?: boolean;
allow_query_components?: boolean;
}
/**
* defaults to
* {
* hourFormat: 'hour24'|
* mode: 'default'|
* };
*/
export interface IsTimeOptions {
hourFormat?: 'hour24' | 'hour12';
mode?: 'default' | 'withSeconds';
}
export interface NormalizeEmailOptions {
all_lowercase?: boolean;
gmail_lowercase?: boolean;
gmail_remove_dots?: boolean;
gmail_remove_subaddress?: boolean;
gmail_convert_googlemaildotcom?: boolean;
outlookdotcom_lowercase?: boolean;
outlookdotcom_remove_subaddress?: boolean;
yahoo_lowercase?: boolean;
yahoo_remove_subaddress?: boolean;
icloud_lowercase?: boolean;
icloud_remove_subaddress?: boolean;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,12 @@
import { Request } from './base';
import { ContextRunningOptions, ResultWithContext, ValidationChainLike } from './chain';
export declare const bindAll: <T>(object: T) => { [K in keyof T]: T[K]; };
export declare function toString(value: any): string;
/**
* Runs all validation chains, and returns their results.
*
* If one of them has a request-level bail set, the previous chains will be awaited on so that
* results are not skewed, which can be slow.
* If this same chain also contains errors, no further chains are run.
*/
export declare function runAllChains(req: Request, chains: readonly ValidationChainLike[], runOpts?: ContextRunningOptions): Promise<ResultWithContext[]>;

View File

@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.bindAll = void 0;
exports.toString = toString;
exports.runAllChains = runAllChains;
const bindAll = (object) => {
const protoKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(object));
protoKeys.forEach(key => {
const maybeFn = object[key];
if (typeof maybeFn === 'function' && key !== 'constructor') {
object[key] = maybeFn.bind(object);
}
});
return object;
};
exports.bindAll = bindAll;
function toString(value) {
if (value instanceof Date) {
return value.toISOString();
}
else if (value && typeof value === 'object' && value.toString) {
if (typeof value.toString !== 'function') {
return Object.getPrototypeOf(value).toString.call(value);
}
return value.toString();
}
else if (value == null || (isNaN(value) && !value.length)) {
return '';
}
return String(value);
}
/**
* Runs all validation chains, and returns their results.
*
* If one of them has a request-level bail set, the previous chains will be awaited on so that
* results are not skewed, which can be slow.
* If this same chain also contains errors, no further chains are run.
*/
async function runAllChains(req, chains, runOpts) {
const promises = [];
for (const chain of chains) {
const bails = chain.builder.build().bail;
if (bails) {
await Promise.all(promises);
}
const resultPromise = chain.run(req, runOpts);
promises.push(resultPromise);
if (bails) {
const result = await resultPromise;
if (!result.isEmpty()) {
break;
}
}
}
return Promise.all(promises);
}

View File

@ -0,0 +1,67 @@
import { Request, ValidationError } from './base';
/**
* Given a validation error, returns a new value that represents it.
*/
export type ErrorFormatter<T = any> = (error: ValidationError) => T;
type ToArrayOptions = {
/**
* Whether only the first error of each field should be returned.
* @default false
*/
onlyFirstError?: boolean;
};
export type ResultFactory<T> = (req: Request) => Result<T>;
interface ResultFactoryBuilderOptions<T = any> {
/**
* The default error formatter of every {@link Result} instance returned by
* the custom `validationResult()` function.
*/
formatter: ErrorFormatter<T>;
}
/**
* Extracts the validation errors of an express request
*/
export declare const validationResult: ResultFactory<ValidationError> & {
withDefaults: typeof withDefaults;
};
/**
* The current state of the validation errors in a request.
*/
export declare class Result<T = any> {
private formatter;
private readonly errors;
constructor(formatter: ErrorFormatter<T>, errors: readonly ValidationError[]);
/**
* Gets the validation errors as an array.
*
* @param options.onlyFirstError whether only the first error of each
*/
array(options?: ToArrayOptions): T[];
/**
* Gets the validation errors as an object.
* If a field has more than one error, only the first one is set in the resulting object.
*
* @returns an object from field name to error
*/
mapped(): Record<string, T>;
/**
* Specifies a function to format errors with.
* @param formatter the function to use for formatting errors
* @returns A new {@link Result} instance with the given formatter
*/
formatWith<T2>(formatter: ErrorFormatter<T2>): Result<T2>;
/**
* @returns `true` if there are no errors, `false` otherwise
*/
isEmpty(): boolean;
/**
* Throws an error if there are validation errors.
*/
throw(): void;
}
/**
* Creates a `validationResult`-like function with default options passed to every {@link Result} it
* returns.
*/
declare function withDefaults<T = any>(options?: Partial<ResultFactoryBuilderOptions<T>>): ResultFactory<T>;
export {};

View File

@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Result = exports.validationResult = void 0;
const _ = require("lodash");
const base_1 = require("./base");
const utils_1 = require("./utils");
/**
* Extracts the validation errors of an express request
*/
exports.validationResult = Object.assign(withDefaults(), { withDefaults });
/**
* The current state of the validation errors in a request.
*/
class Result {
constructor(formatter, errors) {
this.formatter = formatter;
this.errors = errors;
}
/**
* Gets the validation errors as an array.
*
* @param options.onlyFirstError whether only the first error of each
*/
array(options) {
return options && options.onlyFirstError
? Object.values(this.mapped())
: this.errors.map(this.formatter);
}
/**
* Gets the validation errors as an object.
* If a field has more than one error, only the first one is set in the resulting object.
*
* @returns an object from field name to error
*/
mapped() {
return this.errors.reduce((mapping, error) => {
const key = error.type === 'field' ? error.path : `_${error.type}`;
if (!mapping[key]) {
mapping[key] = this.formatter(error);
}
return mapping;
}, {});
}
/**
* Specifies a function to format errors with.
* @param formatter the function to use for formatting errors
* @returns A new {@link Result} instance with the given formatter
*/
formatWith(formatter) {
return new Result(formatter, this.errors);
}
/**
* @returns `true` if there are no errors, `false` otherwise
*/
isEmpty() {
return this.errors.length === 0;
}
/**
* Throws an error if there are validation errors.
*/
throw() {
if (!this.isEmpty()) {
throw Object.assign(new Error(), (0, utils_1.bindAll)(this));
}
}
}
exports.Result = Result;
/**
* Creates a `validationResult`-like function with default options passed to every {@link Result} it
* returns.
*/
function withDefaults(options = {}) {
const defaults = {
formatter: error => error,
};
const actualOptions = _.defaults(options, defaults);
return (req) => {
const contexts = req[base_1.contextsKey] || [];
const errors = _.flatMap(contexts, 'errors');
return new Result(actualOptions.formatter, errors);
};
}

View File

@ -0,0 +1,72 @@
{
"name": "express-validator",
"description": "Express middleware for the validator module.",
"author": "Christoph Tavan <dev@tavan.de>",
"contributors": [
"Rusty Bailey <rustylbailey@gmail.com>",
"Gustavo Henke <guhenke@gmail.com>",
"Federico Ciardi <fed.ciardi@gmail.com>"
],
"version": "7.2.1",
"homepage": "https://express-validator.github.io",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/express-validator/express-validator.git"
},
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"files": [
"lib",
"!src",
"!website",
"!docs"
],
"scripts": {
"build": "tsc",
"clean": "git clean -Xf lib",
"docs:build": "npm --prefix ./website run build",
"docs:publish": "USE_SSH=true DEPLOYMENT_BRANCH=master npm --prefix ./website run publish-gh-pages",
"docs:regenerate-api": "npm --prefix ./website run regenerate-api",
"docs:serve": "npm --prefix ./website run serve",
"docs:start": "npm --prefix ./website start",
"docs:version": "npm --prefix ./website run version",
"prepublishOnly": "tsc",
"postpublish": "npm run docs:publish",
"test": "jest",
"lint": "eslint --ignore-path .gitignore 'src/**/*.ts' && prettier -c ."
},
"engines": {
"node": ">= 8.0.0"
},
"dependencies": {
"lodash": "^4.17.21",
"validator": "~13.12.0"
},
"devDependencies": {
"@docusaurus/core": "^3.1.0",
"@docusaurus/plugin-client-redirects": "^3.1.0",
"@docusaurus/preset-classic": "^3.1.0",
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.7.0",
"prettier": "^2.8.1",
"ts-jest": "^29.1.5",
"typescript": "^5.7.2"
},
"keywords": [
"express",
"validator",
"validation",
"validate",
"sanitize",
"sanitization",
"xss"
]
}