Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 221 additions & 59 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EnumType } from "typescript";

export type ValidationRuleName =
| "any"
Expand All @@ -23,7 +24,7 @@ export type ValidationRuleName =
| "tuple"
| "url"
| "uuid"
| string;
| (string & {});

/**
* Validation schema definition for "any" built-in validator
Expand All @@ -41,47 +42,47 @@ export interface RuleAny extends RuleCustom {
* @see https://github.com/icebob/fastest-validator#array
*/
export interface RuleArray<T = any> extends RuleCustom {
/**
* Name of built-in validator
*/
type: "array";
/**
* If true, the validator accepts an empty array [].
* @default true
*/
empty?: boolean;
/**
* Minimum count of elements
*/
min?: number;
/**
* Maximum count of elements
*/
max?: number;
/**
* Fixed count of elements
*/
length?: number;
/**
* The array must contain this element too
*/
contains?: T | T[];
/**
* Name of built-in validator
*/
type: "array";
/**
* If true, the validator accepts an empty array [].
* @default true
*/
empty?: boolean;
/**
* Minimum count of elements
*/
min?: number;
/**
* Maximum count of elements
*/
max?: number;
/**
* Fixed count of elements
*/
length?: number;
/**
* The array must contain this element too
*/
contains?: T | T[];
/**
* The array must be unique (array of objects is always unique).
*/
unique?: boolean;
/**
* Every element must be an element of the enum array
*/
enum?: T[];
/**
* Validation rules that should be applied to each element of array
*/
items?: ValidationRule;
/**
* Every element must be an element of the enum array
*/
enum?: T[];
/**
* Validation rules that should be applied to each element of array
*/
items?: ValidationRule;
/**
* Wrap value into array if different type provided
*/
convert?: boolean
convert?: boolean;
}

/**
Expand Down Expand Up @@ -523,7 +524,7 @@ export interface RuleTuple<T = any> extends RuleCustom {
/**
* If true, the validator accepts an empty array [].
*/
empty?: boolean
empty?: boolean;
/**
* Validation rules that should be applied to the corresponding element of array
*/
Expand Down Expand Up @@ -904,13 +905,15 @@ export interface ValidationSchemaMetaKeys {
/**
* Definition for validation schema based on validation rules
*/
export type ValidationSchema<T = any> = ValidationSchemaMetaKeys & {
export type ValidationSchema = ValidationSchemaMetaKeys & {
/**
* List of validation rules for each defined field
* List of validation rules for each defined field.
* Note that `boolean` is only acceptable for ValidationSchemaMetaKeys.
* However, omitting it here would cause TypeScript errors.
* @see https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures
*/
[key in keyof T]: ValidationRule | undefined | any;
}

[key: string]: ValidationRule | boolean | undefined;
};

/**
* Structure with description of validation error message
Expand Down Expand Up @@ -961,7 +964,7 @@ export interface ValidatorConstructorOptions {
/**
* Immediately halt after the first error
*/
haltOnFirstError?: boolean
haltOnFirstError?: boolean;

/**
* Default settings for rules
Expand Down Expand Up @@ -1005,7 +1008,7 @@ export interface Context<DATA = any> {
customs: {
[ruleName: string]: { schema: RuleCustom; messages: MessagesType };
};
meta?: object;
meta?: Record<string, any>;
data: DATA;
}

Expand Down Expand Up @@ -1048,17 +1051,19 @@ export interface CheckFunctionOptions {
meta?: object | null;
}

export interface SyncCheckFunction {
(value: any, opts?: CheckFunctionOptions): true | ValidationError[]
async: false
export interface SyncCheckFunction<T = any> {
(value: T, opts?: CheckFunctionOptions): true | ValidationError[];
async: false;
}

export interface AsyncCheckFunction {
(value: any, opts?: CheckFunctionOptions): Promise<true | ValidationError[]>
async: true
export interface AsyncCheckFunction<T = any> {
(value: T, opts?: CheckFunctionOptions): Promise<true | ValidationError[]>;
async: true;
}

export default class Validator {
export default class Validator<
VCO extends ValidatorConstructorOptions = ValidatorConstructorOptions
> {
/**
* List of possible error messages
*/
Expand All @@ -1078,7 +1083,7 @@ export default class Validator {
* Constructor of validation class
* @param {ValidatorConstructorOptions} opts List of possible validator constructor options
*/
constructor(opts?: ValidatorConstructorOptions);
constructor(opts?: VCO);

/**
* Register a custom validation rule in validation object
Expand Down Expand Up @@ -1132,19 +1137,25 @@ export default class Validator {
* @param {ValidationSchema | ValidationSchema[]} schema Validation schema definition that should be used for validation
* @return {(value: any) => (true | ValidationError[])} function that can be used next for validation of current schema
*/
compile<T = any>(
schema: ValidationSchema<T> | ValidationSchema<T>[]
): SyncCheckFunction | AsyncCheckFunction;
compile<
S extends ValidationRule | ValidationSchema | ValidationSchema[],
CompiledType =
| TypeFromAnySchema<S>
// We don't do type inference for `considerNullAsAValue`, to keep it simple.
| (VCO extends { considerNullAsAValue: true } ? any : never)
>(
schema: S
): SyncCheckFunction<CompiledType> | AsyncCheckFunction<CompiledType>;

/**
* Native validation method to validate obj
* @param {any} value that should be validated
* @param {ValidationSchema} schema Validation schema definition that should be used for validation
* @return {{true} | ValidationError[]}
*/
validate(
value: any,
schema: ValidationSchema
validate<VS extends ValidationSchema>(
value: TypeFromValidationSchema<VS>,
schema: VS
): true | ValidationError[] | Promise<true | ValidationError[]>;

/**
Expand All @@ -1153,7 +1164,12 @@ export default class Validator {
* @return {ValidationRule}
*/
getRuleFromSchema(
name: ValidationRuleName | ValidationRuleName[] | ValidationSchema | ValidationSchema[] | { [key: string]: unknown }
name:
| ValidationRuleName
| ValidationRuleName[]
| ValidationSchema
| ValidationSchema[]
| { [key: string]: unknown }
): {
messages: MessagesType;
schema: ValidationSchema;
Expand All @@ -1171,5 +1187,151 @@ export default class Validator {
*/
normalize(
value: ValidationSchema | string | any
): ValidationRule | ValidationSchema
): ValidationRule | ValidationSchema;
}

/*
*
* INFERENCE TYPES
*
*/

type TypeFromAnySchema<
Schema extends
| ValidationRuleName
| ValidationRuleObject
| ValidationRuleObject[]
| ValidationSchema
| ValidationSchema[]
> = Schema extends ValidationRule
? TypeFromValidationRule<Schema>
: // Basic ValidationSchema?
Schema extends ValidationSchema
? TypeFromValidationSchema<Schema>
: // ValidationSchema array? We take the union of each entry.
Schema extends ValidationSchema[]
? { [K in number]: TypeFromValidationSchema<Schema[K]> }[number]
: never;

/**
* Infers the type of a ValidationSchema.
* E.g.
* ```ts
* { param1: {type: "string"},
* param2: {type: "number"} }
* ```
* returns type `{ param1: string, param2: number}`
*/
type TypeFromValidationSchema<Schema extends ValidationSchema> = Optionalize<{
[Param in Exclude<
keyof Schema,
keyof ValidationSchemaMetaKeys
>]: TypeFromValidationRule<Exclude<Schema[Param], boolean>>;
}>;

type TypeFromValidationRule<VR extends ValidationRule | undefined> =
VR extends ValidationRuleObject
? TypeFromValidationRuleObject<VR>
: // VR is a string that is present in type name->type map?
VR extends keyof BasicValidatorTypeMap
? BasicValidatorTypeMap[VR]
: // Array of ValidationRuleObjects.
VR extends ValidationRule[]
? { [K in number]: TypeFromValidationRule<VR[K]> }[number]
: // None of the above...
any;

/**
* Infers type from fastest-validator schema property definition.
*
* E.g.
* - `{ type: "number", default: 2}` returns type `number | undefined`.
* - `{ type: "array", items: "string"}` returns type `string[]`
*/
type TypeFromValidationRuleObject<VR extends ValidationRuleObject> =
// Base type inferred from the `type` property
| TypeFromValidationRuleInner<VR>

// Include the type of `default` if it exists
| (VR extends { default: infer D } ? (D & {}) | undefined | null : never)

// Allow `undefined` and `null` if `optional` is true.
| (VR extends { optional: true } ? undefined | null : never)

// Include `null` if `optional` is true
| (VR extends { nullable: true } ? null : never);

/**
* Infers the type from a fastest-validator string type, e.g.
* the string `"number"` returns type `number`, `"boolean"` returns `boolean`.
*
* Supports complex types `"array"`, `"object"` or `"multi"` too. In that case,
* provide the correct `"items"`, `"params"`, or `"rules"` schema.
* `"tuple"` or types like email are not supported.
*/
type TypeFromValidationRuleInner<VR extends ValidationRuleObject> =
VR["type"] extends keyof BasicValidatorTypeMap
? BasicValidatorTypeMap[VR["type"]]
: // Type array?
VR["type"] extends "array"
? Array<TypeFromValidationRule<VR["items"]>>
: // Type multi (union type)?
VR extends "multi"
? MultiType<VR["params"]>
: // Type object?
VR extends "object"
? TypeFromValidationSchema<VR["rules"]>
: // TODO: Tuples
// None of the above...
any;

/** Fastest-validator types with primitive mapping. */
type BasicValidatorTypeMap = {
any: any;
boolean: boolean;
class: any;
currency: string;
custom: any;
date: string;
email: string;
enum: EnumType;
equal: any;
forbidden: any;
function: Function;
luhn: string;
mac: string;
number: number;
objectID: any;
record: object;
string: string;
tuple: any[];
url: string;
uuid: string;
};

/**
* Infers schema definitions from an array of schema properties ("multitype") into one type.
* **Attention**: Using multi with more than one rule of type object fails.
*/
type MultiType<
ParameterSchemas extends (ValidationRuleObject | ValidationRuleName)[] = []
> = {
[Index in keyof ParameterSchemas]: TypeFromValidationRule<
ParameterSchemas[Index]
>;
}[number];

/**
* A helper type that takes an object and makes properties optional
* if their type includes `undefined`.
*
* For example, for `{ a: string | undefined, b: string }`, it returns
* `{ a?: string | undefined, b: string }`.
*/
type Optionalize<T extends object> = {
// Pick optional properties and make them optional
[K in keyof T as undefined extends T[K] ? K : never]?: T[K];
} & {
// Pick required properties
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
};
Loading