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,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);
};
}