import validatorJs from 'validator'; import type { DateTime, DurationObjectUnits } from 'luxon'; import type { Request, HttpContext } from '@adonisjs/core/http'; import type { Options as NormalizeUrlOptions } from 'normalize-url'; import type { FileValidationOptions, MultipartFile } from '@adonisjs/bodyparser/types'; /** * Accepted duration units. Luxon has removed support for * singular unit names, but we are supporting them to * avoid breaking changes */ export type DurationUnits = keyof DurationObjectUnits | 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond'; /** * Helper */ type WithOptional = Omit & Partial>; /** * Shape of a rule. This is what methods from the * rules object returns */ export type Rule = { name: string; options?: any; }; export type SchemaRef = { readonly __$isRef: true; value: T; key: string; }; /** * The shape of rule after it has been passed */ export type ParsedRule = { name: string; async: boolean; allowUndefineds: boolean; compiledOptions: Options; }; /** * Shape of schema core literal type */ export type SchemaLiteral = { type: 'literal'; subtype: string; nullable: boolean; optional: boolean; rules: ParsedRule[]; }; /** * Shape of schema core object type */ export type SchemaObject = { type: 'object'; nullable: boolean; optional: boolean; rules: ParsedRule[]; children?: ParsedSchemaTree; }; /** * Shape of schema core array type */ export type SchemaArray = { type: 'array'; nullable: boolean; optional: boolean; rules: ParsedRule[]; each?: SchemaLiteral | SchemaObject | SchemaArray; }; /** * Shape of parsed schema tree. This is not extensible. All newly * added rules will be of type literal. */ export type ParsedSchemaTree = { [key: string]: SchemaLiteral | SchemaObject | SchemaArray; }; /** * The runtime values passed to a validation runtime */ export type ValidationRuntimeOptions = { root: any; tip: any; field: string; pointer: string; refs: { [key: string]: SchemaRef; }; arrayExpressionPointer?: string; errorReporter: ErrorReporterContract; mutate: (newValue: any) => void; }; export type NodeType = 'array' | 'object' | 'literal'; export type NodeSubType = Exclude; /** * Compiler internal representation of a field to produce * the compiled output */ export type ValidationField = { name: string; type: 'literal' | 'identifier'; }; /** * Shape of an async validation function */ export type AsyncValidation = { compile(type: NodeType, subtype: NodeSubType, options?: any, rulesTree?: { [key: string]: any; }): ParsedRule; validate(value: any, compiledOptions: T, runtimeOptions: ValidationRuntimeOptions): Promise; }; /** * Shape of an sync validation function */ export type SyncValidation = { compile(type: NodeType, subtype: SubType, options?: any, rulesTree?: { [key: string]: any; }): ParsedRule; validate(value: any, compiledOptions: T, runtimeOptions: ValidationRuntimeOptions): void; }; /** * Shape of custom messages accepted the validator */ export interface CustomMessages { '*'?: (field: string, rule: string, arrayExpressionPointer?: string, args?: any) => string; [key: string]: unknown; } /** * Returns the most appropriate message for the current * validation error. */ export interface MessagesBagContract { get(pointer: string, rule: string, message: string, arrayExpressionPointer?: string, args?: any): string; } /** * The interface that every error reporter must adhere * to. */ export interface ErrorReporterContract { hasErrors: boolean; report(pointer: string, rule: string, message?: string, arrayExpressionPointer?: string, args?: any): void; toError(): any; toJSON(): Messages; } /** * The reporter constructor contract */ export interface ErrorReporterConstructorContract { new (messages: MessagesBagContract, bail: boolean): ErrorReporterContract; } /** * Shape of the JSON API error node */ export type JsonApiErrorNode = { source: { pointer: string; }; code: string; title: string; meta?: any; }; /** * Shape of the API error node */ export type ApiErrorNode = { message: string; field: string; rule: string; args?: any; }; /** * Shape of the Vanilla error node */ export type VanillaErrorNode = { [field: string]: string[]; }; /** * The contract every validation must adhere to */ export type ValidationContract = AsyncValidation | SyncValidation; /** * The compiler output function */ export interface CompilerOutput { (data: any, validations: { [rule: string]: ValidationContract; }, errorReporter: ErrorReporterContract, helpers: { exists: (value: any) => boolean; isObject: (value: any) => boolean; }, refs: { [key: string]: SchemaRef; }): Promise; } /** * Typed schema is a key-value pair of the `field` and a schema * function that returns `getTree` method and optional `type` * for it */ export type TypedSchema = { [field: string]: { t?: any; getTree(): SchemaLiteral | SchemaObject | SchemaArray; }; }; /** * Signature to define a string or optional string type */ export interface StringType { (options?: { escape?: boolean; trim?: boolean; } | Rule[], rules?: Rule[]): { t: string; getTree(): SchemaLiteral; }; optional(options?: { escape?: boolean; trim?: boolean; } | Rule[], rules?: Rule[]): { t?: string; getTree(): SchemaLiteral; }; nullable(options?: { escape?: boolean; trim?: boolean; } | Rule[], rules?: Rule[]): { t: string | null; getTree(): SchemaLiteral; }; nullableAndOptional(options?: { escape?: boolean; trim?: boolean; } | Rule[], rules?: Rule[]): { t?: string | null; getTree(): SchemaLiteral; }; } /** * Signature to define a date or an optional date type */ export interface DateType { (options?: { format?: string; }, rules?: Rule[]): { t: DateTime; getTree(): SchemaLiteral; }; optional(options?: { format?: string; }, rules?: Rule[]): { t?: DateTime; getTree(): SchemaLiteral; }; nullable(options?: { format?: string; }, rules?: Rule[]): { t: DateTime | null; getTree(): SchemaLiteral; }; nullableAndOptional(options?: { format?: string; }, rules?: Rule[]): { t?: DateTime | null; getTree(): SchemaLiteral; }; } /** * Signature to define a boolean or optional boolean type */ export interface BooleanType { (rules?: Rule[]): { t: boolean; getTree(): SchemaLiteral; }; optional(rules?: Rule[]): { t?: boolean; getTree(): SchemaLiteral; }; nullable(rules?: Rule[]): { t: boolean | null; getTree(): SchemaLiteral; }; nullableAndOptional(rules?: Rule[]): { t?: boolean | null; getTree(): SchemaLiteral; }; } /** * Signature to define a number or optional number type */ export interface NumberType { (rules?: Rule[]): { t: number; getTree(): SchemaLiteral; }; optional(rules?: Rule[]): { t?: number; getTree(): SchemaLiteral; }; nullable(rules?: Rule[]): { t: number | null; getTree(): SchemaLiteral; }; nullableAndOptional(rules?: Rule[]): { t?: number | null; getTree(): SchemaLiteral; }; } /** * Signature to define an object with members or an optional object * with members. */ export interface ObjectType { (rules?: Rule[]): { members(schema: T): { t: { [P in keyof T]: T[P]['t']; }; getTree(): SchemaObject; }; anyMembers(): { t: { [key: string]: any; }; getTree(): SchemaObject; }; }; optional(rules?: Rule[]): { members(schema: T): { t?: { [P in keyof T]: T[P]['t']; }; getTree(): SchemaObject; }; anyMembers(): { t?: { [key: string]: any; }; getTree(): SchemaObject; }; }; nullable(rules?: Rule[]): { members(schema: T): { t: { [P in keyof T]: T[P]['t']; } | null; getTree(): SchemaObject; }; anyMembers(): { t: { [key: string]: any; } | null; getTree(): SchemaObject; }; }; nullableAndOptional(rules?: Rule[]): { members(schema: T): { t?: { [P in keyof T]: T[P]['t']; } | null; getTree(): SchemaObject; }; anyMembers(): { t?: { [key: string]: any; } | null; getTree(): SchemaObject; }; }; } /** * Signature to define an array with members or an optional array * with members or array/optional array with optional members */ export interface ArrayType { (rules?: Rule[]): { members(schema: T): { t: T['t'][]; getTree(): SchemaArray; }; anyMembers(): { t: any[]; getTree(): SchemaArray; }; }; optional(rules?: Rule[]): { members(schema: T): { t?: T['t'][]; getTree(): SchemaArray; }; anyMembers(): { t?: any[]; getTree(): SchemaArray; }; }; nullable(rules?: Rule[]): { members(schema: T): { t: T['t'][] | null; getTree(): SchemaArray; }; anyMembers(): { t: any[] | null; getTree(): SchemaArray; }; }; nullableAndOptional(rules?: Rule[]): { members(schema: T): { t?: T['t'][] | null; getTree(): SchemaArray; }; anyMembers(): { t?: any[] | null; getTree(): SchemaArray; }; }; } /** * Options accepted by the enum type */ export type AllowedEnumOptions = SchemaRef | readonly unknown[]; /** * Conditionally finds the return value for the Enum options */ export type EnumReturnValue = Options extends SchemaRef ? R extends readonly unknown[] ? R[number] : unknown : Options extends readonly unknown[] ? Options[number] : never; /** * Conditionally finds the return value for the EnumSet options */ export type EnumSetReturnValue = Options extends SchemaRef ? R extends readonly unknown[] ? R[number][] : unknown : Options extends readonly unknown[] ? Options[number][] : never; /** * BigInt schema types */ export interface BigIntType { (rules?: Rule[]): { t: bigint; getTree(): SchemaLiteral; }; optional(rules?: Rule[]): { t?: bigint; getTree(): SchemaLiteral; }; nullable(rules?: Rule[]): { t: bigint | null; getTree(): SchemaLiteral; }; nullableAndOptional(rules?: Rule[]): { t?: bigint | null; getTree(): SchemaLiteral; }; } /** * Signature to define an enum type. We accept a static list of enum * values or a ref that is resolved lazily. */ export interface EnumType { (options: Options, rules?: Rule[]): { t: EnumReturnValue; getTree(): SchemaLiteral; }; optional(options: Options, rules?: Rule[]): { t?: EnumReturnValue; getTree(): SchemaLiteral; }; nullable(options: Options, rules?: Rule[]): { t: EnumReturnValue | null; getTree(): SchemaLiteral; }; nullableAndOptional(options: Options, rules?: Rule[]): { t?: EnumReturnValue | null; getTree(): SchemaLiteral; }; } /** * Signature to define an enum set type */ export interface EnumSetType { (options: Options, rules?: Rule[]): { t: EnumSetReturnValue; getTree(): SchemaLiteral; }; optional(options: Options, rules?: Rule[]): { t?: EnumSetReturnValue; getTree(): SchemaLiteral; }; nullable(options: Options, rules?: Rule[]): { t: EnumSetReturnValue | null; getTree(): SchemaLiteral; }; nullableAndOptional(options: Options, rules?: Rule[]): { t?: EnumSetReturnValue | null; getTree(): SchemaLiteral; }; } /** * Type for validating multipart files */ export interface FileType { (options?: Partial, rules?: Rule[]): { t: MultipartFile; getTree(): SchemaLiteral; }; optional(options?: Partial, rules?: Rule[]): { t?: MultipartFile; getTree(): SchemaLiteral; }; nullable(options?: Partial, rules?: Rule[]): { t: MultipartFile | null; getTree(): SchemaLiteral; }; nullableAndOptional(options?: Partial, rules?: Rule[]): { t?: MultipartFile | null; getTree(): SchemaLiteral; }; } /** * Shape of `schema.create` output */ export type ParsedTypedSchema = { props: { [P in keyof T]: T[P]['t']; }; tree: ParsedSchemaTree; }; /** * List of schema methods available to define a schema */ export interface Schema { string: StringType; boolean: BooleanType; number: NumberType; date: DateType; bigint: BigIntType; enum: EnumType; enumSet: EnumSetType; object: ObjectType; array: ArrayType; file: FileType; refs: (refs: T) => { [K in keyof T]: SchemaRef; }; create(schema: T): ParsedTypedSchema; } /** * Shape of validator config. */ export type ValidatorConfig = { bail?: boolean; existsStrict?: boolean; reporter?: () => Promise | Promise<{ default: ErrorReporterConstructorContract; }>; }; /** * Method to use content negotiation to define a custom per http request * repoter */ export type RequestNegotiator = (request: Request) => ErrorReporterConstructorContract; /** * A callback method to return default messages for validation. */ export type DefaultMessagesCallback = (ctx?: HttpContext) => CustomMessages; /** * Resolved config passed to the configure method and use internally */ export type ValidatorResolvedConfig = { bail?: boolean; existsStrict?: boolean; reporter?: ErrorReporterConstructorContract; messages?: DefaultMessagesCallback; negotiator: RequestNegotiator; }; /** * The validator node object accepted by the validated * method */ export type ValidatorNode> = { schema: T; data: any; refs?: { [key: string]: SchemaRef; }; cacheKey?: string; messages?: CustomMessages; existsStrict?: boolean; reporter?: ErrorReporterConstructorContract; bail?: boolean; }; /** * Shape of validator accepted by the request.validate function. It can be * a class with constructor that accepts the Context or a plain object * with properties accepted by the `validator.validate` method. */ export type RequestValidatorNode> = (new (ctx: HttpContext) => WithOptional, 'data'>) | WithOptional, 'data'>; /** * Shape of the function that validates the compiler output */ export type ValidateFn = >(validator: ValidatorNode) => Promise; /** * Email validation and sanitization options */ export type EmailRuleOptions = EmailValidationOptions & { /** * @deprecated */ sanitize?: boolean | { lowerCase?: boolean; removeDots?: boolean; removeSubaddress?: boolean; }; }; /** * Options to validate email */ export type EmailValidationOptions = { allowDisplayName?: boolean; requireDisplayName?: boolean; allowUtf8LocalPart?: boolean; requireTld?: boolean; ignoreMaxLength?: boolean; allowIpDomain?: boolean; domainSpecificValidation?: boolean; hostBlacklist?: string[]; }; /** * Options accepted by the normalizeEmail * rule */ export type EmailNormalizationOptions = { allLowercase?: boolean; gmailLowercase?: boolean; gmailRemoveDots?: boolean; gmailRemoveSubaddress?: boolean; gmailConvertGooglemaildotcom?: boolean; outlookdotcomLowercase?: boolean; outlookdotcomRemoveSubaddress?: boolean; yahooLowercase?: boolean; yahooRemoveSubaddress?: boolean; icloudLowercase?: boolean; icloudRemoveSubaddress?: boolean; }; /** * URL validation options */ export type UrlValidationOptions = { protocols?: ('http' | 'https' | 'ftp')[]; requireTld?: boolean; requireProtocol?: boolean; requireHost?: boolean; allowedHosts?: string[]; bannedHosts?: string[]; validateLength?: boolean; }; /** * Extended options with deprecated properties */ export type UrlOptions = UrlValidationOptions & { ensureProtocol?: string | boolean; stripWWW?: boolean; }; /** * URL normalization options */ export type UrlNormalizationOptions = { -readonly [K in keyof NormalizeUrlOptions]: NormalizeUrlOptions[K]; }; /** * List of available validation rules. The rules are not the validation * implementations, but instead a pointer to the validation implementation * by it's name. * * Do not add primitives here, since they are covered by the types. For example * schema.string will add rules.string automatically. */ export interface Rules { /** * Field under validation must always exists */ required(): Rule; /** * Field under validation can be nullable, but not undefined * or an empty string */ nullable(): Rule; /** * Field under validation must always exists if the * target field exists */ requiredIfExists(field: string): Rule; /** * Field under validation must always exists, if all of the * target field exists */ requiredIfExistsAll(field: string[]): Rule; /** * Field under validation must always exists, if any of the * target fields exists */ requiredIfExistsAny(field: string[]): Rule; /** * Field under validation must always exists, if target field * does not exists */ requiredIfNotExists(field: string): Rule; /** * Field under validation must always exists, if all of the * target fields does not exists */ requiredIfNotExistsAll(field: string[]): Rule; /** * Field under validation must always exists, if any of the * target fields does not exists */ requiredIfNotExistsAny(field: string[]): Rule; /** * Field under validation must always exists, when the defined * expecations are met */ requiredWhen(field: string, operator: 'in' | 'notIn', comparisonValues: any[] | SchemaRef): Rule; requiredWhen(field: string, operator: '>' | '<' | '>=' | '<=', comparisonValue: number | SchemaRef): Rule; requiredWhen(field: string, operator: 'in' | 'notIn' | '=' | '!=' | '>' | '<' | '>=' | '<=', comparisonValues: any | SchemaRef): Rule; /** * Number must be unsigned */ unsigned(): Rule; /** * Number must be in a specific range of values */ range(start: number, stop: number): Rule; /** * String must be alpha */ alpha(options?: { allow?: ('space' | 'underscore' | 'dash')[]; }): Rule; /** * String must be alphaNum */ alphaNum(options?: { allow?: ('space' | 'underscore' | 'dash')[]; }): Rule; /** * String must match regex */ regex(regexPattern: RegExp): Rule; /** * Validate string value to be formatted as an email * address */ email(options?: EmailValidationOptions): Rule; /** * Value must be a valid email address * @deprecated */ email(options?: EmailRuleOptions): Rule; /** * Normalize email address */ normalizeEmail(options: EmailNormalizationOptions): Rule; /** * Value must be a valid url */ url(options?: UrlValidationOptions): Rule; /** * Value must be a valid url * @deprecated */ url(options?: UrlOptions): Rule; /** * Normalize URL */ normalizeUrl(options?: NormalizeUrlOptions): Rule; /** * Trim string value */ trim(): Rule; /** * Escape string value */ escape(): Rule; /** * Value must be valid as ip address regex. Optionally you can * define a ipv version */ ip(options?: { version?: '4' | 4 | 6 | '6'; }): Rule; /** * Value must be valid as per uuid format. Optionally you can * define a uuid version */ uuid(options?: { version?: validatorJs.UUIDVersion; }): Rule; /** * Value must pass the mobile regex rule */ mobile(options?: { strict?: boolean; locale?: validatorJs.MobilePhoneLocale[]; }): Rule; /** * Length of string or array must be below or same as the defined length */ maxLength(length: number): Rule; /** * Length of string or array must be above the defined length */ minLength(length: number): Rule; /** * Confirm field to be exists and have the same value */ confirmed(field?: string): Rule; /** * The value of field must be distinct inside the array */ distinct(field: string): Rule; /** * The value of date must be after a given duration */ after(duration: number, unit: DurationUnits): Rule; /** * The value of date must be after a given date */ after(date: SchemaRef): Rule; /** * The value of date must be after the given keyword. * * After "today" is equivalent to 0, days * After "tomorrow" is equivalent to 1, day */ after(keyword: 'today' | 'tomorrow'): Rule; /** * The value of date must be before a given duration */ before(duration: number, unit: DurationUnits): Rule; /** * The value of date must be before a given date */ before(date: SchemaRef): Rule; /** * The value of date must be before the given keyword. * * Before "today" is equivalent to 0, days * Before "yesterday" is equivalent to 1, day */ before(keyword: 'today' | 'yesterday'): Rule; /** * The value of date must be after a given date */ afterField(field: string): Rule; /** * The value of date must be after or equal to a given date */ afterOrEqualToField(field: string): Rule; /** * The value of date must be before a given date */ beforeField(field: string): Rule; /** * The value of date must be before or equal to a given date */ beforeOrEqualToField(field: string): Rule; /** * Ensure value is not in the defined array */ notIn(keywords: (number | string)[] | SchemaRef<(number | string)[]>): Rule; /** * The value of string must be equalToValue */ equalTo(equalToValue: string | SchemaRef): Rule; /** * The value of date must be after or equal to a given duration */ afterOrEqual(duration: number, unit: DurationUnits): Rule; /** * The value of date must be after or equal to a given date */ afterOrEqual(date: SchemaRef): Rule; /** * The value of date must be after or equal to the given keyword. * * After "today" is equivalent to 0, days * After "tomorrow" is equivalent to 1, day */ afterOrEqual(keyword: 'today' | 'tomorrow'): Rule; /** * The value of date must be before or equal to a given duration */ beforeOrEqual(duration: number, unit: DurationUnits): Rule; /** * The value of date must be before or equal to a given date */ beforeOrEqual(date: SchemaRef): Rule; /** * The value of date must be before or equal to the given keyword. * * Before "today" is equivalent to 0, days * Before "yesterday" is equivalent to 1, day */ beforeOrEqual(keyword: 'today' | 'yesterday'): Rule; } export interface ValidationExceptionContract { new (flashToSession: boolean, messages?: any): { handle(error: InstanceType, ctx: HttpContext): any; }; } export {};