Comparing version 5.0.0-alpha.4 to 5.0.0-alpha.5
import { Criteria, CriteriaValidator, Schema, SchemaState, ValueComparator } from '../types'; | ||
/** | ||
* Map a list of field names that must be defined alongside this field. | ||
* Map a list of field names that must be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -19,2 +19,10 @@ export declare function and<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T>; | ||
/** | ||
* Require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
export declare function required<T>(state: SchemaState<T>): void; | ||
/** | ||
* Dont require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
export declare function optional<T>(state: SchemaState<T>): void; | ||
/** | ||
* Disallow null values. | ||
@@ -24,6 +32,2 @@ */ | ||
/** | ||
* Require this field to NOT be explicitly defined. | ||
*/ | ||
export declare function notRequired<T>(state: SchemaState<T>): void; | ||
/** | ||
* Allow null values. | ||
@@ -37,10 +41,14 @@ */ | ||
/** | ||
* Map a list of field names that must have at least 1 defined. | ||
* Map a list of field names that must have at least 1 defined when in a shape/object. | ||
*/ | ||
export declare function or<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T>; | ||
/** | ||
* Require this field to be explicitly defined. | ||
* Allow undefined values. | ||
*/ | ||
export declare function required<T>(state: SchemaState<T>): void; | ||
export declare function undefinable<T>(state: SchemaState<T>): void; | ||
/** | ||
* Disallow undefined values. | ||
*/ | ||
export declare function notUndefinable<T>(state: SchemaState<T>): void; | ||
/** | ||
* Validate with a specific schema when a condition is met. | ||
@@ -50,5 +58,5 @@ */ | ||
/** | ||
* Map a list of field names that must not be defined alongside this field. | ||
* Map a list of field names that must not be defined alongside this field when in a shape/object. | ||
*/ | ||
export declare function xor<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T>; | ||
//# sourceMappingURL=common.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { Constructor, Schema, UnknownObject } from './types'; | ||
import { Constructor, DefaultValue, Schema, SchemaValidateOptions, UnknownObject } from './types'; | ||
import { ValidationError } from './ValidationError'; | ||
@@ -22,3 +22,5 @@ export declare function isObject(value: unknown): value is object; | ||
export declare function prettyValue(value: unknown): string | null; | ||
export declare function tryAndCollect(validator: () => boolean | void, validError: ValidationError, collectErrors?: boolean): boolean; | ||
export declare function collectErrors(collectionError: ValidationError, validator: () => boolean | void): boolean; | ||
export declare function extractDefaultValue<T>(defaultValue: DefaultValue<T>, path: string, { currentObject, rootObject }: SchemaValidateOptions): T; | ||
export declare function typeOf(value: unknown): string; | ||
//# sourceMappingURL=helpers.d.ts.map |
@@ -1,9 +0,11 @@ | ||
import { ArrayCriterias, CommonCriterias, DefaultValue, InferNullable, Schema } from '../types'; | ||
import { ArrayCriterias, CommonCriterias, DefaultValue, InferNullable, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface ArraySchema<T = unknown[]> extends Schema<T>, ArrayCriterias<ArraySchema<T>>, CommonCriterias<ArraySchema<T>> { | ||
never: () => ArraySchema<never>; | ||
notNullable: () => ArraySchema<NonNullable<T>>; | ||
notNullable: () => ArraySchema<NotNull<T>>; | ||
notUndefinable: () => ArraySchema<NotUndefined<T>>; | ||
nullable: () => ArraySchema<T | null>; | ||
of: <V>(schema: Schema<V>) => ArraySchema<InferNullable<T, V[]>>; | ||
undefinable: () => ArraySchema<T | undefined>; | ||
} | ||
export declare function array<T = unknown>(defaultValue?: DefaultValue<T[]>): ArraySchema<T[]>; | ||
//# sourceMappingURL=array.d.ts.map |
@@ -1,10 +0,12 @@ | ||
import { CommonCriterias, DefaultValue, Options, Schema } from '../types'; | ||
import { CommonCriterias, DefaultValue, NotNull, NotUndefined, Options, Schema } from '../types'; | ||
export interface BooleanSchema<T = boolean> extends Schema<T>, CommonCriterias<BooleanSchema<T>> { | ||
never: () => BooleanSchema<never>; | ||
notNullable: () => BooleanSchema<NonNullable<T>>; | ||
notNullable: () => BooleanSchema<NotNull<T>>; | ||
notUndefinable: () => BooleanSchema<NotUndefined<T>>; | ||
nullable: () => BooleanSchema<T | null>; | ||
onlyFalse: (options?: Options) => BooleanSchema<false>; | ||
onlyTrue: (options?: Options) => BooleanSchema<true>; | ||
undefinable: () => BooleanSchema<T | undefined>; | ||
} | ||
export declare function bool(defaultValue?: DefaultValue<boolean>): BooleanSchema; | ||
//# sourceMappingURL=bool.d.ts.map |
@@ -1,8 +0,10 @@ | ||
import { CommonCriterias, CriteriaValidator, DefaultValue, Schema } from '../types'; | ||
import { CommonCriterias, CriteriaValidator, DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface CustomSchema<T> extends Schema<T>, CommonCriterias<CustomSchema<T>> { | ||
never: () => CustomSchema<never>; | ||
notNullable: () => CustomSchema<NonNullable<T>>; | ||
notNullable: () => CustomSchema<NotNull<T>>; | ||
notUndefinable: () => CustomSchema<NotUndefined<T>>; | ||
nullable: () => CustomSchema<T | null>; | ||
undefinable: () => CustomSchema<T | undefined>; | ||
} | ||
export declare function custom<T>(validator: CriteriaValidator<T>, defaultValue?: DefaultValue<T>): CustomSchema<T>; | ||
export declare function custom<T>(validator: CriteriaValidator<T>, defaultValue: DefaultValue<T>): CustomSchema<T>; | ||
//# sourceMappingURL=custom.d.ts.map |
@@ -1,8 +0,10 @@ | ||
import { CommonCriterias, DateCriterias, DefaultValue, Schema } from '../types'; | ||
import { CommonCriterias, DateCriterias, DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface DateSchema<T = Date> extends Schema<T>, DateCriterias<DateSchema<T>>, CommonCriterias<DateSchema<T>> { | ||
never: () => DateSchema<never>; | ||
notNullable: () => DateSchema<NonNullable<T>>; | ||
notNullable: () => DateSchema<NotNull<T>>; | ||
notUndefinable: () => DateSchema<NotUndefined<T>>; | ||
nullable: () => DateSchema<T | null>; | ||
undefinable: () => DateSchema<T | undefined>; | ||
} | ||
export declare function date(defaultValue?: DefaultValue<Date>): DateSchema<Date>; | ||
//# sourceMappingURL=date.d.ts.map |
@@ -1,8 +0,10 @@ | ||
import { AnyFunction, CommonCriterias, DefaultValue, Schema } from '../types'; | ||
import { AnyFunction, CommonCriterias, DefaultValueInitializer, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface FunctionSchema<T = AnyFunction> extends Schema<T>, CommonCriterias<FunctionSchema<T>> { | ||
never: () => FunctionSchema<never>; | ||
notNullable: () => FunctionSchema<NonNullable<T>>; | ||
notNullable: () => FunctionSchema<NotNull<T>>; | ||
notUndefinable: () => FunctionSchema<NotUndefined<T>>; | ||
nullable: () => FunctionSchema<T | null>; | ||
undefinable: () => FunctionSchema<T | undefined>; | ||
} | ||
export declare function func<T extends (...args: any[]) => any = AnyFunction>(defaultValue?: DefaultValue<T>): FunctionSchema<T>; | ||
export declare function func<T extends AnyFunction = AnyFunction>(defaultValue?: DefaultValueInitializer<T>): FunctionSchema<T>; | ||
//# sourceMappingURL=func.d.ts.map |
@@ -1,9 +0,11 @@ | ||
import { CommonCriterias, Constructor, InferNullable, Schema } from '../types'; | ||
import { CommonCriterias, Constructor, InferNullable, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface InstanceSchema<T> extends Schema<T>, CommonCriterias<InstanceSchema<T>> { | ||
never: () => InstanceSchema<never>; | ||
notNullable: () => InstanceSchema<NonNullable<T>>; | ||
notNullable: () => InstanceSchema<NotNull<T>>; | ||
notUndefinable: () => InstanceSchema<NotUndefined<T>>; | ||
nullable: () => InstanceSchema<T | null>; | ||
of: <C>(ref: Constructor<C>, loose?: boolean) => InstanceSchema<InferNullable<T, C>>; | ||
undefinable: () => InstanceSchema<T | undefined>; | ||
} | ||
export declare function instance(): InstanceSchema<Object | null>; | ||
//# sourceMappingURL=instance.d.ts.map |
@@ -1,8 +0,10 @@ | ||
import { DefaultValue, Schema } from '../types'; | ||
import { DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface LazySchema<T = boolean> extends Schema<T> { | ||
never: () => LazySchema<never>; | ||
notNullable: () => LazySchema<NonNullable<T>>; | ||
notNullable: () => LazySchema<NotNull<T>>; | ||
notUndefinable: () => LazySchema<NotUndefined<T>>; | ||
nullable: () => LazySchema<T | null>; | ||
undefinable: () => LazySchema<T | undefined>; | ||
} | ||
export declare function lazy<T>(factory: (value: unknown) => Schema<T>, defaultValue: DefaultValue<T | null | undefined>): LazySchema<T>; | ||
//# sourceMappingURL=lazy.d.ts.map |
@@ -1,9 +0,11 @@ | ||
import { CommonCriterias, DefaultValue, InferNullable, NumberCriterias, Options, Schema } from '../types'; | ||
import { CommonCriterias, DefaultValue, InferNullable, NotNull, NotUndefined, NumberCriterias, Options, Schema } from '../types'; | ||
export interface NumberSchema<T = number> extends Schema<T>, NumberCriterias<NumberSchema<T>>, CommonCriterias<NumberSchema<T>> { | ||
never: () => NumberSchema<never>; | ||
notNullable: () => NumberSchema<NonNullable<T>>; | ||
notNullable: () => NumberSchema<NotNull<T>>; | ||
notUndefinable: () => NumberSchema<NotUndefined<T>>; | ||
nullable: () => NumberSchema<T | null>; | ||
oneOf: <I extends number = number>(list: I[], options?: Options) => NumberSchema<InferNullable<T, I>>; | ||
undefinable: () => NumberSchema<T | undefined>; | ||
} | ||
export declare function number<T extends number>(defaultValue?: DefaultValue<number>): NumberSchema<T>; | ||
//# sourceMappingURL=number.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { CommonCriterias, DefaultValue, InferNullable, ObjectCriterias, Options, Schema } from '../types'; | ||
import { CommonCriterias, DefaultValue, InferNullable, NotNull, NotUndefined, ObjectCriterias, Options, Schema } from '../types'; | ||
import { StringSchema } from './string'; | ||
@@ -6,7 +6,9 @@ export interface ObjectSchema<T = object> extends Schema<T>, ObjectCriterias<ObjectSchema<T>>, CommonCriterias<ObjectSchema<T>> { | ||
never: () => ObjectSchema<never>; | ||
notNullable: () => ObjectSchema<NonNullable<T>>; | ||
notNullable: () => ObjectSchema<NotNull<T>>; | ||
notUndefinable: () => ObjectSchema<NotUndefined<T>>; | ||
nullable: () => ObjectSchema<T | null>; | ||
of: <V, K extends PropertyKey = keyof T>(schema: Schema<V>) => ObjectSchema<InferNullable<T, Record<K, V>>>; | ||
undefinable: () => ObjectSchema<T | undefined>; | ||
} | ||
export declare function object<V = unknown, K extends PropertyKey = string>(defaultValue?: DefaultValue<Record<K, V>>): ObjectSchema<Record<K, V>>; | ||
//# sourceMappingURL=object.d.ts.map |
@@ -1,10 +0,12 @@ | ||
import { Blueprint, CommonCriterias, Schema, ShapeCriterias } from '../types'; | ||
import { Blueprint, CommonCriterias, NotNull, NotUndefined, Schema, ShapeCriterias } from '../types'; | ||
export interface ShapeSchema<T> extends Schema<Required<T>>, ShapeCriterias<ShapeSchema<T>>, CommonCriterias<ShapeSchema<T>> { | ||
never: () => ShapeSchema<never>; | ||
notNullable: () => ShapeSchema<NonNullable<T>>; | ||
notNullable: () => ShapeSchema<NotNull<T>>; | ||
notUndefinable: () => ShapeSchema<NotUndefined<T>>; | ||
nullable: () => ShapeSchema<T | null>; | ||
/** @internal */ | ||
of: <S extends object>(schema: Blueprint<S>) => ShapeSchema<S>; | ||
undefinable: () => ShapeSchema<T | undefined>; | ||
} | ||
export declare function shape<T extends object>(blueprint: Blueprint<T>): ShapeSchema<T>; | ||
//# sourceMappingURL=shape.d.ts.map |
@@ -1,9 +0,11 @@ | ||
import { CommonCriterias, DefaultValue, InferNullable, Options, Schema, StringCriterias } from '../types'; | ||
import { CommonCriterias, DefaultValue, InferNullable, NotNull, NotUndefined, Options, Schema, StringCriterias } from '../types'; | ||
export interface StringSchema<T = string> extends Schema<T>, StringCriterias<StringSchema<T>>, CommonCriterias<StringSchema<T>> { | ||
never: () => StringSchema<never>; | ||
notNullable: () => StringSchema<NonNullable<T>>; | ||
notNullable: () => StringSchema<NotNull<T>>; | ||
notUndefinable: () => StringSchema<NotUndefined<T>>; | ||
nullable: () => StringSchema<T | null>; | ||
oneOf: <I extends string = string>(list: I[], options?: Options) => StringSchema<InferNullable<T, I>>; | ||
undefinable: () => StringSchema<T | undefined>; | ||
} | ||
export declare function string<T extends string = string>(defaultValue?: DefaultValue<string>): StringSchema<T>; | ||
//# sourceMappingURL=string.d.ts.map |
import { InferTupleItems } from '../criteria/tuples'; | ||
import { CommonCriterias, Schema } from '../types'; | ||
import { CommonCriterias, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface TupleSchema<T> extends Schema<Required<T>>, CommonCriterias<TupleSchema<T>> { | ||
never: () => TupleSchema<never>; | ||
notNullable: () => TupleSchema<NonNullable<T>>; | ||
notNullable: () => TupleSchema<NotNull<T>>; | ||
notUndefinable: () => TupleSchema<NotUndefined<T>>; | ||
nullable: () => TupleSchema<T | null>; | ||
/** @internal */ | ||
of: <I extends unknown[]>(schemas: InferTupleItems<I>) => TupleSchema<I>; | ||
undefinable: () => TupleSchema<T | undefined>; | ||
} | ||
export declare function tuple<T extends unknown[] = unknown[]>(schemas: InferTupleItems<T>): TupleSchema<T>; | ||
//# sourceMappingURL=tuple.d.ts.map |
@@ -1,9 +0,11 @@ | ||
import { AnySchema, CommonCriterias, DefaultValue, Schema } from '../types'; | ||
import { AnySchema, CommonCriterias, DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface UnionSchema<T> extends Schema<T>, CommonCriterias<UnionSchema<T>> { | ||
never: () => UnionSchema<never>; | ||
notNullable: () => UnionSchema<NonNullable<T>>; | ||
notNullable: () => UnionSchema<NotNull<T>>; | ||
notUndefinable: () => UnionSchema<NotUndefined<T>>; | ||
nullable: () => UnionSchema<T | null>; | ||
of: (schemas: AnySchema[]) => UnionSchema<T>; | ||
undefinable: () => UnionSchema<T | undefined>; | ||
} | ||
export declare function union<T = unknown>(defaultValue: DefaultValue<T>): UnionSchema<T>; | ||
//# sourceMappingURL=union.d.ts.map |
@@ -19,4 +19,4 @@ export declare type Primitive = bigint | boolean | number | string | symbol | null | undefined; | ||
export interface Criteria<Input> { | ||
skipIfNull?: boolean; | ||
skipIfOptional?: boolean; | ||
dontSkipIfNull?: boolean; | ||
dontSkipIfUndefined?: boolean; | ||
validate: CriteriaValidator<Input>; | ||
@@ -29,4 +29,4 @@ } | ||
deprecate: (message: string) => S; | ||
notRequired: () => S; | ||
only: () => S; | ||
optional: () => S; | ||
or: (...keys: string[]) => S; | ||
@@ -79,3 +79,2 @@ required: () => S; | ||
export interface SchemaValidateOptions { | ||
collectErrors?: boolean; | ||
currentObject?: UnknownObject; | ||
@@ -86,2 +85,3 @@ rootObject?: UnknownObject; | ||
schema: () => string; | ||
state: () => SchemaState<Output>; | ||
type: () => string; | ||
@@ -97,2 +97,3 @@ validate: (value: unknown, path?: string, options?: SchemaValidateOptions) => Output; | ||
type: string; | ||
undefinable: boolean; | ||
} | ||
@@ -117,4 +118,6 @@ export interface SchemaOptions<T> { | ||
} : T; | ||
export declare type NotNull<T> = T extends null ? never : T; | ||
export declare type NotUndefined<T> = T extends undefined ? never : T; | ||
export declare type AnySchema = Schema<any>; | ||
export declare type AnyFunction = (...args: any[]) => any; | ||
//# sourceMappingURL=types.d.ts.map |
421
esm/index.js
@@ -21,6 +21,4 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
const key = pathKey(path); | ||
const valueLabel = prettyValue(value); | ||
const typeLabel = key.includes('[') ? 'member' : 'field'; | ||
const label = valueLabel ? `Invalid ${typeLabel} "${key}" with value ${valueLabel}.` : `Invalid ${typeLabel} "${key}".`; | ||
this.message = `${label} ${this.message}`; | ||
const type = key.includes('[') ? 'member' : 'field'; | ||
this.message = `Invalid ${type} "${key}". ${this.message}`; | ||
} | ||
@@ -49,3 +47,3 @@ } | ||
function isSchema(value) { | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.state === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
} | ||
@@ -158,2 +156,6 @@ | ||
function prettyValue(value) { | ||
if (value instanceof Date) { | ||
return value.toLocaleDateString(); | ||
} | ||
switch (typeof value) { | ||
@@ -185,3 +187,3 @@ case 'string': | ||
function tryAndCollect(validator, validError, collectErrors) { | ||
function collectErrors(collectionError, validator) { | ||
let result = false; | ||
@@ -196,6 +198,4 @@ | ||
} catch (error) { | ||
if (error instanceof Error && collectErrors) { | ||
validError.addError(error); | ||
} else { | ||
throw error; | ||
if (error instanceof Error) { | ||
collectionError.addError(error); | ||
} | ||
@@ -207,20 +207,19 @@ } | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
function extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}) { | ||
return typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
} | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
@@ -235,3 +234,2 @@ /** | ||
function validate(state, validators, initialValue, path = '', { | ||
collectErrors = true, | ||
currentObject = {}, | ||
@@ -247,4 +245,8 @@ rootObject = currentObject | ||
if (value === undefined) { | ||
value = typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
invalid(!state.required, 'Field is required and must be defined.', path, undefined); | ||
if (!state.undefinable) { | ||
value = extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
} | ||
} else { | ||
@@ -265,25 +267,16 @@ if (process.env.NODE_ENV !== "production" && metadata.deprecatedMessage) { | ||
const optimalError = new OptimalError(); | ||
validators.forEach(test => { | ||
if (test.skipIfNull && value === null || test.skipIfOptional && !state.required && value === state.defaultValue || state.never) { | ||
if (!test.dontSkipIfNull && state.nullable && value === null || !test.dontSkipIfUndefined && state.undefinable && value === undefined || state.never) { | ||
return; | ||
} | ||
tryAndCollect(() => { | ||
const result = test.validate(value, path, { | ||
collectErrors, | ||
currentObject, | ||
rootObject | ||
}); | ||
const result = test.validate(value, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}, optimalError, collectErrors); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}); | ||
if (optimalError.errors.length > 0) { | ||
throw optimalError; | ||
} | ||
return value; | ||
@@ -304,3 +297,4 @@ } | ||
required: false, | ||
type | ||
type, | ||
undefinable: false | ||
}; | ||
@@ -324,2 +318,6 @@ const validators = []; | ||
state() { | ||
return state; | ||
}, | ||
type() { | ||
@@ -329,5 +327,15 @@ return state.type; | ||
// @ts-expect-error Ignore null/undefined | ||
validate(value, path, options) { | ||
const result = validate(state, validators, value, path, options); | ||
return cast && result !== null ? cast(result) : result; | ||
if (state.nullable && result === null) { | ||
return null; | ||
} | ||
if (state.undefinable && result === undefined) { | ||
return undefined; | ||
} | ||
return cast ? cast(result) : result; | ||
} | ||
@@ -357,4 +365,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -379,4 +385,2 @@ invalid(value.length > 0, options.message ?? 'Array cannot be empty.', path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -407,6 +411,4 @@ if (!Array.isArray(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(value.length === size, options.message ?? `Array length must be ${size}.`, path, value); | ||
invalid(value.length === size, options.message ?? `Array length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -423,2 +425,4 @@ | ||
}); | ||
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ | ||
/** | ||
@@ -431,4 +435,7 @@ * Require this field to only be false. | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
invalid(!value, options.message ?? 'May only be `false`.', path, value); | ||
invalid(value === false, options.message ?? 'May only be `false`.', path, value); | ||
} | ||
@@ -446,4 +453,7 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
invalid(value, options.message ?? 'May only be `true`.', path, value); | ||
invalid(value === true, options.message ?? 'May only be `true`.', path, value); | ||
} | ||
@@ -470,6 +480,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of "${state.type}".`, path, value); | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of \`${state.type}\`.`, path, value); | ||
} | ||
@@ -484,6 +492,27 @@ | ||
}); | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
} | ||
} | ||
/** | ||
* Map a list of field names that must be defined alongside this field. | ||
* Map a list of field names that must be defined alongside this field when in a shape/object. | ||
*/ | ||
function and(state, ...keys) { | ||
@@ -495,2 +524,5 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -500,3 +532,3 @@ currentObject | ||
const andKeys = [...new Set([pathKey(path), ...keys])].sort(); | ||
const undefs = andKeys.filter(key => currentObject?.[key] === undefined || currentObject?.[key] === null); // Only error once when one of the struct is defined | ||
const undefs = andKeys.filter(key => currentObject?.[key] == null); // Only error once when one of the struct is defined | ||
@@ -529,2 +561,4 @@ if (undefs.length === andKeys.length) { | ||
invalid(false, error.message, path, value); | ||
} else if (error instanceof ValidationError || error instanceof OptimalError) { | ||
throw error; | ||
} | ||
@@ -558,18 +592,26 @@ } | ||
/** | ||
* Disallow null values. | ||
* Require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
function required(state) { | ||
state.required = true; | ||
} | ||
/** | ||
* Require this field to NOT be explicitly defined. | ||
* Dont require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notRequired(state) { | ||
function optional(state) { | ||
state.required = false; | ||
} | ||
/** | ||
* Disallow null values. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
} | ||
/** | ||
* Allow null values. | ||
@@ -597,4 +639,8 @@ */ | ||
return { | ||
validate(value, path) { | ||
invalid(value === defaultValue, `Value may only be "${defaultValue}".`, path, value); | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
const testValue = extractDefaultValue(defaultValue, path, validateOptions); | ||
invalid(value === testValue, `Value may only be "${testValue}".`, path, value); | ||
} | ||
@@ -605,3 +651,3 @@ | ||
/** | ||
* Map a list of field names that must have at least 1 defined. | ||
* Map a list of field names that must have at least 1 defined when in a shape/object. | ||
*/ | ||
@@ -616,2 +662,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -628,10 +677,18 @@ currentObject | ||
/** | ||
* Require this field to be explicitly defined. | ||
* Allow undefined values. | ||
*/ | ||
function required(state) { | ||
state.required = true; | ||
function undefinable(state) { | ||
state.undefinable = true; | ||
} | ||
/** | ||
* Disallow undefined values. | ||
*/ | ||
function notUndefinable(state) { | ||
state.undefinable = false; | ||
} | ||
/** | ||
* Validate with a specific schema when a condition is met. | ||
@@ -653,2 +710,5 @@ */ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
@@ -658,3 +718,3 @@ const passed = typeof condition === 'function' ? condition(value, validateOptions.currentObject, validateOptions.rootObject) : condition === value; | ||
if (passed) { | ||
return pass.validate(value, path); | ||
return pass.validate(value, path, validateOptions); | ||
} | ||
@@ -672,3 +732,3 @@ | ||
/** | ||
* Map a list of field names that must not be defined alongside this field. | ||
* Map a list of field names that must not be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -683,2 +743,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -701,8 +764,10 @@ currentObject | ||
never: never, | ||
required: required, | ||
optional: optional, | ||
notNullable: notNullable, | ||
notRequired: notRequired, | ||
nullable: nullable, | ||
only: only, | ||
or: or, | ||
required: required, | ||
undefinable: undefinable, | ||
notUndefinable: notUndefinable, | ||
when: when, | ||
@@ -723,6 +788,4 @@ xor: xor | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidDate(value) && value > afterDate, options.message ?? `Date must come after ${afterDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value > afterDate, options.message ?? `Date must come after ${prettyValue(afterDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -745,6 +808,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidDate(value) && value < beforeDate, options.message ?? `Date must come before ${beforeDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value < beforeDate, options.message ?? `Date must come before ${prettyValue(beforeDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -772,6 +833,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), options.message ?? `Date must be between ${startDate.toLocaleDateString()} and ${endDate.toLocaleDateString()}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), options.message ?? `Date must be between ${prettyValue(startDate)} and ${prettyValue(endDate)}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -798,6 +857,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), options.message ?? `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), options.message ?? `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -814,6 +871,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidNumber(value) && value % 1 !== 0, options.message ?? 'Number must be a float.', path, value); | ||
invalid(isValidNumber(value) && value % 1 !== 0, options.message ?? `Number must be a float, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -834,9 +889,7 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
if (options.inclusive) { | ||
invalid(isValidNumber(value) && value >= min, options.message ?? `Number must be greater than or equal to ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value >= min, options.message ?? `Number must be greater than or equal to ${min}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
invalid(isValidNumber(value) && value > min, options.message ?? `Number must be greater than ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value > min, options.message ?? `Number must be greater than ${min}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -864,6 +917,4 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Number.isSafeInteger(value), options.message ?? 'Number must be an integer.', path, value); | ||
invalid(Number.isSafeInteger(value), options.message ?? `Number must be an integer, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -884,9 +935,7 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
if (options.inclusive) { | ||
invalid(isValidNumber(value) && value <= max, options.message ?? `Number must be less than or equal to ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value <= max, options.message ?? `Number must be less than or equal to ${max}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
invalid(isValidNumber(value) && value < max, options.message ?? `Number must be less than ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value < max, options.message ?? `Number must be less than ${max}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -914,6 +963,4 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidNumber(value) && value < 0, options.message ?? 'Number must be negative.', path, value); | ||
invalid(isValidNumber(value) && value < 0, options.message ?? `Number must be negative, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -934,6 +981,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(list.includes(value), options.message ?? `Number must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), options.message ?? `Number must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -950,6 +995,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isValidNumber(value) && value > 0, options.message ?? 'Number must be positive.', path, value); | ||
invalid(isValidNumber(value) && value > 0, options.message ?? `Number must be positive, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -983,4 +1026,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1010,4 +1051,2 @@ if (isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1032,4 +1071,2 @@ invalid(Object.keys(value).length > 0, options.message ?? 'Object cannot be empty.', path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1062,6 +1099,7 @@ if (!isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Object.keys(value).length === size, options.message ?? (size === 1 ? `Object must have ${size} property.` : `Object must have ${size} properties.`), path, value); | ||
const { | ||
length | ||
} = Object.keys(value); | ||
invalid(length === size, options.message ?? (size === 1 ? `Object must have ${size} property, received ${length}.` : `Object must have ${size} properties, received ${length}.`), path, value); | ||
} | ||
@@ -1101,4 +1139,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1114,11 +1150,18 @@ if (value) { | ||
const collectionError = new ValidationError('The following validations have failed:', path, value); | ||
const currentValidateOptions = { ...validateOptions, | ||
currentObject: value | ||
}; | ||
Object.keys(schemas).forEach(prop => { | ||
const key = prop; | ||
const schema = schemas[key]; | ||
tryAndCollect(() => { | ||
shape[key] = schema.validate(value[key], path ? `${path}.${key}` : String(key), { ...validateOptions, | ||
currentObject: value | ||
}); | ||
}, collectionError, validateOptions.collectErrors); // Delete the prop and mark it as known | ||
const currentPath = path ? `${path}.${key}` : String(key); | ||
if (schema.state().required) { | ||
invalid(key in value && value[key] !== undefined, 'Field is required and must be defined.', currentPath, undefined); | ||
} | ||
collectErrors(collectionError, () => { | ||
shape[key] = schema.validate(value[key], currentPath, currentValidateOptions); | ||
}); // Delete the prop and mark it as known | ||
delete unknown[key]; | ||
@@ -1162,5 +1205,2 @@ }); | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1183,5 +1223,2 @@ invalid(value.includes(token, options.index ?? 0), options.message ?? `String does not include "${token}".`, path, value); | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1244,4 +1281,2 @@ invalid(!!value.match(pattern), `${options.message ?? 'String does not match.'} (pattern "${pattern.source}")`, path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1264,6 +1299,4 @@ invalid(isValidString(value), options.message ?? 'String cannot be empty.', path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(list.includes(value), options.message ?? `String must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), options.message ?? `String must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1280,6 +1313,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(value === value.toLocaleLowerCase(), options.message ?? 'String must be lower cased.', path, value); | ||
invalid(value === value.toLocaleLowerCase(), options.message ?? `String must be lower cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1296,6 +1327,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(value === value.toLocaleUpperCase(), options.message ?? 'String must be upper cased.', path, value); | ||
invalid(value === value.toLocaleUpperCase(), options.message ?? `String must be upper cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1316,6 +1345,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(value.length === size, options.message ?? `String length must be ${size}.`, path, value); | ||
invalid(value.length === size, options.message ?? `String length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -1352,6 +1379,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items.`, path, value); | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items, received ${value.length}.`, path, value); | ||
return itemsSchemas.map((item, i) => item.validate(value[i], `${path}[${i}]`, validateOptions)); | ||
@@ -1367,14 +1392,2 @@ } | ||
}); | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
/** | ||
@@ -1384,3 +1397,2 @@ * Require field value to be one of a specific schema type. | ||
function of(state, schemas) { | ||
@@ -1393,4 +1405,2 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1408,3 +1418,3 @@ let nextValue = value; | ||
return tryAndCollect(() => { | ||
return collectErrors(collectionError, () => { | ||
if (valueType === schemaType || valueType === 'object/shape' && schemaType === 'object' || valueType === 'object/shape' && schemaType === 'shape' || valueType === 'array/tuple' && schemaType === 'array' || valueType === 'array/tuple' && schemaType === 'tuple' || schemaType === 'custom') { | ||
@@ -1417,3 +1427,3 @@ // Dont pass path so its not included in the error message | ||
return false; | ||
}, collectionError, validateOptions.collectErrors); | ||
}); | ||
}); | ||
@@ -1425,3 +1435,3 @@ | ||
} else { | ||
invalid(false, `Value must be one of: ${allowedValues}.`, path, value); | ||
invalid(false, `Received ${valueType} but value must be one of: ${allowedValues}.`, path, value); | ||
} | ||
@@ -1450,6 +1460,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Array.isArray(value), 'Must be an array.', path, value); | ||
invalid(Array.isArray(value), `Must be an array, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1469,10 +1477,14 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a plain object.', path, value); | ||
invalid(isObject(value), `Must be a plain object, received ${typeOf(value)}.`, path, value); | ||
} | ||
}]); | ||
} // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
} // All schemas need a default value to operate correctly, | ||
// but functions are a weird one. We want to verify that | ||
// "this value is a function" without needing to return | ||
// a default value (predicates, etc), but also sometimes | ||
// return a default value when undefined is passed | ||
// (option objects, etc). So by default (pun intended), | ||
// this schema's default value is `undefined`. | ||
@@ -1487,7 +1499,4 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'function', 'Must be a function.', path, value); | ||
invalid(typeof value === 'function', `Must be a function, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1507,6 +1516,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a schema.', path, value); | ||
// Dont use `isSchema` and rely on the shape below | ||
invalid(isObject(value), `Must be a schema, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1516,5 +1524,6 @@ | ||
return shape.of({ | ||
schema: func().notNullable().required(), | ||
type: func().notNullable().required(), | ||
validate: func().notNullable().required() | ||
schema: func().notNullable().notUndefinable(), | ||
state: func().notNullable().notUndefinable(), | ||
type: func().notNullable().notUndefinable(), | ||
validate: func().notNullable().notUndefinable() | ||
}); | ||
@@ -1538,6 +1547,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'boolean', 'Must be a boolean.', path, value); | ||
invalid(typeof value === 'boolean', `Must be a boolean, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1566,7 +1573,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
const time = createDate(value); | ||
invalid(isValidDate(time), 'Must be a string, number, or `Date` that resolves to a valid date.', path, value); | ||
invalid(isValidDate(time), `Must be a string, number, or \`Date\` that resolves to a valid date, received ${typeOf(value)}.`, path, value); | ||
return time; | ||
@@ -1586,6 +1591,10 @@ } | ||
}, [state => ({ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
let valueType = typeOf(value); | ||
validate(value, path) { | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? 'Must be a class instance.' : `Must be an instance of ${state.type}.`, path, value); | ||
if (valueType === 'class') { | ||
valueType = prettyValue(value); | ||
} | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? `Must be a class instance, received ${valueType}.` : `Must be an instance of \`${state.type}\`, received ${valueType}.`, path, value); | ||
} | ||
@@ -1607,6 +1616,2 @@ | ||
}, [{ | ||
// Avoid recursion by returning early and using the provided default value | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1638,6 +1643,4 @@ const schema = factory(value); | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'number', 'Must be a number.', path, value); | ||
invalid(typeof value === 'number', `Must be a number, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1662,4 +1665,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1671,3 +1672,3 @@ if (value === undefined) { | ||
invalid(isObject(value), 'Must be a shaped object.', path, value); | ||
invalid(isObject(value), `Must be a shaped object, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1692,6 +1693,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'string', 'Must be a string.', path, value); | ||
invalid(typeof value === 'string', `Must be a string, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1711,4 +1710,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1720,3 +1717,3 @@ if (value === undefined) { | ||
invalid(Array.isArray(value), 'Must be a tuple.', path, value); | ||
invalid(Array.isArray(value), `Must be a tuple, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1761,2 +1758,6 @@ } | ||
try { | ||
if (value == null) { | ||
throw new Error('Avoid null/undefined'); | ||
} | ||
schema.validate(value); | ||
@@ -1792,5 +1793,3 @@ } catch { | ||
try { | ||
return schema.validate(struct, options.prefix ?? '', { | ||
collectErrors: true, | ||
...validateOptions, | ||
return schema.validate(struct, options.prefix ?? '', { ...validateOptions, | ||
currentObject: object, | ||
@@ -1797,0 +1796,0 @@ rootObject: object |
@@ -27,6 +27,4 @@ // Bundled with Packemon: https://packemon.dev | ||
const key = pathKey(path); | ||
const valueLabel = prettyValue(value); | ||
const typeLabel = key.includes('[') ? 'member' : 'field'; | ||
const label = valueLabel ? `Invalid ${typeLabel} "${key}" with value ${valueLabel}.` : `Invalid ${typeLabel} "${key}".`; | ||
this.message = `${label} ${this.message}`; | ||
const type = key.includes('[') ? 'member' : 'field'; | ||
this.message = `Invalid ${type} "${key}". ${this.message}`; | ||
} | ||
@@ -55,3 +53,3 @@ } | ||
function isSchema(value) { | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.state === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
} | ||
@@ -164,2 +162,6 @@ | ||
function prettyValue(value) { | ||
if (value instanceof Date) { | ||
return value.toLocaleDateString(); | ||
} | ||
switch (typeof value) { | ||
@@ -191,3 +193,3 @@ case 'string': | ||
function tryAndCollect(validator, validError, collectErrors) { | ||
function collectErrors(collectionError, validator) { | ||
let result = false; | ||
@@ -202,6 +204,4 @@ | ||
} catch (error) { | ||
if (error instanceof Error && collectErrors) { | ||
validError.addError(error); | ||
} else { | ||
throw error; | ||
if (error instanceof Error) { | ||
collectionError.addError(error); | ||
} | ||
@@ -213,20 +213,19 @@ } | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
function extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}) { | ||
return typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
} | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
@@ -241,3 +240,2 @@ /** | ||
function validate(state, validators, initialValue, path = '', { | ||
collectErrors = true, | ||
currentObject = {}, | ||
@@ -253,4 +251,8 @@ rootObject = currentObject | ||
if (value === undefined) { | ||
value = typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
invalid(!state.required, 'Field is required and must be defined.', path, undefined); | ||
if (!state.undefinable) { | ||
value = extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
} | ||
} else { | ||
@@ -271,25 +273,16 @@ if (process.env.NODE_ENV !== "production" && metadata.deprecatedMessage) { | ||
const optimalError = new OptimalError(); | ||
validators.forEach(test => { | ||
if (test.skipIfNull && value === null || test.skipIfOptional && !state.required && value === state.defaultValue || state.never) { | ||
if (!test.dontSkipIfNull && state.nullable && value === null || !test.dontSkipIfUndefined && state.undefinable && value === undefined || state.never) { | ||
return; | ||
} | ||
tryAndCollect(() => { | ||
const result = test.validate(value, path, { | ||
collectErrors, | ||
currentObject, | ||
rootObject | ||
}); | ||
const result = test.validate(value, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}, optimalError, collectErrors); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}); | ||
if (optimalError.errors.length > 0) { | ||
throw optimalError; | ||
} | ||
return value; | ||
@@ -310,3 +303,4 @@ } | ||
required: false, | ||
type | ||
type, | ||
undefinable: false | ||
}; | ||
@@ -330,2 +324,6 @@ const validators = []; | ||
state() { | ||
return state; | ||
}, | ||
type() { | ||
@@ -335,5 +333,15 @@ return state.type; | ||
// @ts-expect-error Ignore null/undefined | ||
validate(value, path, options) { | ||
const result = validate(state, validators, value, path, options); | ||
return cast && result !== null ? cast(result) : result; | ||
if (state.nullable && result === null) { | ||
return null; | ||
} | ||
if (state.undefinable && result === undefined) { | ||
return undefined; | ||
} | ||
return cast ? cast(result) : result; | ||
} | ||
@@ -363,4 +371,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -387,4 +393,2 @@ var _options$message; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -415,8 +419,6 @@ if (!Array.isArray(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message2; | ||
invalid(value.length === size, (_options$message2 = options.message) !== null && _options$message2 !== void 0 ? _options$message2 : `Array length must be ${size}.`, path, value); | ||
invalid(value.length === size, (_options$message2 = options.message) !== null && _options$message2 !== void 0 ? _options$message2 : `Array length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -433,2 +435,4 @@ | ||
}); | ||
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ | ||
/** | ||
@@ -441,6 +445,9 @@ * Require this field to only be false. | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
var _options$message3; | ||
invalid(!value, (_options$message3 = options.message) !== null && _options$message3 !== void 0 ? _options$message3 : 'May only be `false`.', path, value); | ||
invalid(value === false, (_options$message3 = options.message) !== null && _options$message3 !== void 0 ? _options$message3 : 'May only be `false`.', path, value); | ||
} | ||
@@ -458,6 +465,9 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
var _options$message4; | ||
invalid(value, (_options$message4 = options.message) !== null && _options$message4 !== void 0 ? _options$message4 : 'May only be `true`.', path, value); | ||
invalid(value === true, (_options$message4 = options.message) !== null && _options$message4 !== void 0 ? _options$message4 : 'May only be `true`.', path, value); | ||
} | ||
@@ -486,6 +496,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of "${state.type}".`, path, value); | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of \`${state.type}\`.`, path, value); | ||
} | ||
@@ -500,6 +508,27 @@ | ||
}); | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
} | ||
} | ||
/** | ||
* Map a list of field names that must be defined alongside this field. | ||
* Map a list of field names that must be defined alongside this field when in a shape/object. | ||
*/ | ||
function and(state, ...keys) { | ||
@@ -511,2 +540,5 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -516,3 +548,3 @@ currentObject | ||
const andKeys = [...new Set([pathKey(path), ...keys])].sort(); | ||
const undefs = andKeys.filter(key => (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) === undefined || (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) === null); // Only error once when one of the struct is defined | ||
const undefs = andKeys.filter(key => (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) == null); // Only error once when one of the struct is defined | ||
@@ -545,2 +577,4 @@ if (undefs.length === andKeys.length) { | ||
invalid(false, error.message, path, value); | ||
} else if (error instanceof ValidationError || error instanceof OptimalError) { | ||
throw error; | ||
} | ||
@@ -574,18 +608,26 @@ } | ||
/** | ||
* Disallow null values. | ||
* Require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
function required(state) { | ||
state.required = true; | ||
} | ||
/** | ||
* Require this field to NOT be explicitly defined. | ||
* Dont require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notRequired(state) { | ||
function optional(state) { | ||
state.required = false; | ||
} | ||
/** | ||
* Disallow null values. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
} | ||
/** | ||
* Allow null values. | ||
@@ -613,4 +655,8 @@ */ | ||
return { | ||
validate(value, path) { | ||
invalid(value === defaultValue, `Value may only be "${defaultValue}".`, path, value); | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
const testValue = extractDefaultValue(defaultValue, path, validateOptions); | ||
invalid(value === testValue, `Value may only be "${testValue}".`, path, value); | ||
} | ||
@@ -621,3 +667,3 @@ | ||
/** | ||
* Map a list of field names that must have at least 1 defined. | ||
* Map a list of field names that must have at least 1 defined when in a shape/object. | ||
*/ | ||
@@ -632,2 +678,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -644,10 +693,18 @@ currentObject | ||
/** | ||
* Require this field to be explicitly defined. | ||
* Allow undefined values. | ||
*/ | ||
function required(state) { | ||
state.required = true; | ||
function undefinable(state) { | ||
state.undefinable = true; | ||
} | ||
/** | ||
* Disallow undefined values. | ||
*/ | ||
function notUndefinable(state) { | ||
state.undefinable = false; | ||
} | ||
/** | ||
* Validate with a specific schema when a condition is met. | ||
@@ -669,2 +726,5 @@ */ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
@@ -674,3 +734,3 @@ const passed = typeof condition === 'function' ? condition(value, validateOptions.currentObject, validateOptions.rootObject) : condition === value; | ||
if (passed) { | ||
return pass.validate(value, path); | ||
return pass.validate(value, path, validateOptions); | ||
} | ||
@@ -688,3 +748,3 @@ | ||
/** | ||
* Map a list of field names that must not be defined alongside this field. | ||
* Map a list of field names that must not be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -699,2 +759,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -717,8 +780,10 @@ currentObject | ||
never: never, | ||
required: required, | ||
optional: optional, | ||
notNullable: notNullable, | ||
notRequired: notRequired, | ||
nullable: nullable, | ||
only: only, | ||
or: or, | ||
required: required, | ||
undefinable: undefinable, | ||
notUndefinable: notUndefinable, | ||
when: when, | ||
@@ -739,8 +804,6 @@ xor: xor | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message5; | ||
invalid(isValidDate(value) && value > afterDate, (_options$message5 = options.message) !== null && _options$message5 !== void 0 ? _options$message5 : `Date must come after ${afterDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value > afterDate, (_options$message5 = options.message) !== null && _options$message5 !== void 0 ? _options$message5 : `Date must come after ${prettyValue(afterDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -763,8 +826,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message6; | ||
invalid(isValidDate(value) && value < beforeDate, (_options$message6 = options.message) !== null && _options$message6 !== void 0 ? _options$message6 : `Date must come before ${beforeDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value < beforeDate, (_options$message6 = options.message) !== null && _options$message6 !== void 0 ? _options$message6 : `Date must come before ${prettyValue(beforeDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -792,8 +853,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message7; | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), (_options$message7 = options.message) !== null && _options$message7 !== void 0 ? _options$message7 : `Date must be between ${startDate.toLocaleDateString()} and ${endDate.toLocaleDateString()}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), (_options$message7 = options.message) !== null && _options$message7 !== void 0 ? _options$message7 : `Date must be between ${prettyValue(startDate)} and ${prettyValue(endDate)}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -820,8 +879,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message8; | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), (_options$message8 = options.message) !== null && _options$message8 !== void 0 ? _options$message8 : `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), (_options$message8 = options.message) !== null && _options$message8 !== void 0 ? _options$message8 : `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -838,8 +895,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message9; | ||
invalid(isValidNumber(value) && value % 1 !== 0, (_options$message9 = options.message) !== null && _options$message9 !== void 0 ? _options$message9 : 'Number must be a float.', path, value); | ||
invalid(isValidNumber(value) && value % 1 !== 0, (_options$message9 = options.message) !== null && _options$message9 !== void 0 ? _options$message9 : `Number must be a float, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -860,4 +915,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -867,7 +920,7 @@ if (options.inclusive) { | ||
invalid(isValidNumber(value) && value >= min, (_options$message10 = options.message) !== null && _options$message10 !== void 0 ? _options$message10 : `Number must be greater than or equal to ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value >= min, (_options$message10 = options.message) !== null && _options$message10 !== void 0 ? _options$message10 : `Number must be greater than or equal to ${min}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
var _options$message11; | ||
invalid(isValidNumber(value) && value > min, (_options$message11 = options.message) !== null && _options$message11 !== void 0 ? _options$message11 : `Number must be greater than ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value > min, (_options$message11 = options.message) !== null && _options$message11 !== void 0 ? _options$message11 : `Number must be greater than ${min}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -895,8 +948,6 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message12; | ||
invalid(Number.isSafeInteger(value), (_options$message12 = options.message) !== null && _options$message12 !== void 0 ? _options$message12 : 'Number must be an integer.', path, value); | ||
invalid(Number.isSafeInteger(value), (_options$message12 = options.message) !== null && _options$message12 !== void 0 ? _options$message12 : `Number must be an integer, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -917,4 +968,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -924,7 +973,7 @@ if (options.inclusive) { | ||
invalid(isValidNumber(value) && value <= max, (_options$message13 = options.message) !== null && _options$message13 !== void 0 ? _options$message13 : `Number must be less than or equal to ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value <= max, (_options$message13 = options.message) !== null && _options$message13 !== void 0 ? _options$message13 : `Number must be less than or equal to ${max}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
var _options$message14; | ||
invalid(isValidNumber(value) && value < max, (_options$message14 = options.message) !== null && _options$message14 !== void 0 ? _options$message14 : `Number must be less than ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value < max, (_options$message14 = options.message) !== null && _options$message14 !== void 0 ? _options$message14 : `Number must be less than ${max}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -952,8 +1001,6 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message15; | ||
invalid(isValidNumber(value) && value < 0, (_options$message15 = options.message) !== null && _options$message15 !== void 0 ? _options$message15 : 'Number must be negative.', path, value); | ||
invalid(isValidNumber(value) && value < 0, (_options$message15 = options.message) !== null && _options$message15 !== void 0 ? _options$message15 : `Number must be negative, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -974,8 +1021,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message16; | ||
invalid(list.includes(value), (_options$message16 = options.message) !== null && _options$message16 !== void 0 ? _options$message16 : `Number must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), (_options$message16 = options.message) !== null && _options$message16 !== void 0 ? _options$message16 : `Number must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -992,8 +1037,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message17; | ||
invalid(isValidNumber(value) && value > 0, (_options$message17 = options.message) !== null && _options$message17 !== void 0 ? _options$message17 : 'Number must be positive.', path, value); | ||
invalid(isValidNumber(value) && value > 0, (_options$message17 = options.message) !== null && _options$message17 !== void 0 ? _options$message17 : `Number must be positive, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1027,4 +1070,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1056,4 +1097,2 @@ if (isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1080,4 +1119,2 @@ var _options$message19; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1110,8 +1147,9 @@ if (!isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message20; | ||
invalid(Object.keys(value).length === size, (_options$message20 = options.message) !== null && _options$message20 !== void 0 ? _options$message20 : size === 1 ? `Object must have ${size} property.` : `Object must have ${size} properties.`, path, value); | ||
const { | ||
length | ||
} = Object.keys(value); | ||
invalid(length === size, (_options$message20 = options.message) !== null && _options$message20 !== void 0 ? _options$message20 : size === 1 ? `Object must have ${size} property, received ${length}.` : `Object must have ${size} properties, received ${length}.`, path, value); | ||
} | ||
@@ -1151,4 +1189,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1164,11 +1200,18 @@ if (value) { | ||
const collectionError = new ValidationError('The following validations have failed:', path, value); | ||
const currentValidateOptions = { ...validateOptions, | ||
currentObject: value | ||
}; | ||
Object.keys(schemas).forEach(prop => { | ||
const key = prop; | ||
const schema = schemas[key]; | ||
tryAndCollect(() => { | ||
shape[key] = schema.validate(value[key], path ? `${path}.${key}` : String(key), { ...validateOptions, | ||
currentObject: value | ||
}); | ||
}, collectionError, validateOptions.collectErrors); // Delete the prop and mark it as known | ||
const currentPath = path ? `${path}.${key}` : String(key); | ||
if (schema.state().required) { | ||
invalid(key in value && value[key] !== undefined, 'Field is required and must be defined.', currentPath, undefined); | ||
} | ||
collectErrors(collectionError, () => { | ||
shape[key] = schema.validate(value[key], currentPath, currentValidateOptions); | ||
}); // Delete the prop and mark it as known | ||
delete unknown[key]; | ||
@@ -1212,5 +1255,2 @@ }); | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1235,5 +1275,2 @@ var _options$index, _options$message21; | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1298,4 +1335,2 @@ var _options$message22; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1320,8 +1355,6 @@ var _options$message23; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message24; | ||
invalid(list.includes(value), (_options$message24 = options.message) !== null && _options$message24 !== void 0 ? _options$message24 : `String must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), (_options$message24 = options.message) !== null && _options$message24 !== void 0 ? _options$message24 : `String must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1338,8 +1371,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message25; | ||
invalid(value === value.toLocaleLowerCase(), (_options$message25 = options.message) !== null && _options$message25 !== void 0 ? _options$message25 : 'String must be lower cased.', path, value); | ||
invalid(value === value.toLocaleLowerCase(), (_options$message25 = options.message) !== null && _options$message25 !== void 0 ? _options$message25 : `String must be lower cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1356,8 +1387,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message26; | ||
invalid(value === value.toLocaleUpperCase(), (_options$message26 = options.message) !== null && _options$message26 !== void 0 ? _options$message26 : 'String must be upper cased.', path, value); | ||
invalid(value === value.toLocaleUpperCase(), (_options$message26 = options.message) !== null && _options$message26 !== void 0 ? _options$message26 : `String must be upper cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1378,8 +1407,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message27; | ||
invalid(value.length === size, (_options$message27 = options.message) !== null && _options$message27 !== void 0 ? _options$message27 : `String length must be ${size}.`, path, value); | ||
invalid(value.length === size, (_options$message27 = options.message) !== null && _options$message27 !== void 0 ? _options$message27 : `String length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -1416,6 +1443,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items.`, path, value); | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items, received ${value.length}.`, path, value); | ||
return itemsSchemas.map((item, i) => item.validate(value[i], `${path}[${i}]`, validateOptions)); | ||
@@ -1431,14 +1456,2 @@ } | ||
}); | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
/** | ||
@@ -1448,3 +1461,2 @@ * Require field value to be one of a specific schema type. | ||
function of(state, schemas) { | ||
@@ -1457,4 +1469,2 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1472,3 +1482,3 @@ let nextValue = value; | ||
return tryAndCollect(() => { | ||
return collectErrors(collectionError, () => { | ||
if (valueType === schemaType || valueType === 'object/shape' && schemaType === 'object' || valueType === 'object/shape' && schemaType === 'shape' || valueType === 'array/tuple' && schemaType === 'array' || valueType === 'array/tuple' && schemaType === 'tuple' || schemaType === 'custom') { | ||
@@ -1481,3 +1491,3 @@ // Dont pass path so its not included in the error message | ||
return false; | ||
}, collectionError, validateOptions.collectErrors); | ||
}); | ||
}); | ||
@@ -1489,3 +1499,3 @@ | ||
} else { | ||
invalid(false, `Value must be one of: ${allowedValues}.`, path, value); | ||
invalid(false, `Received ${valueType} but value must be one of: ${allowedValues}.`, path, value); | ||
} | ||
@@ -1514,6 +1524,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Array.isArray(value), 'Must be an array.', path, value); | ||
invalid(Array.isArray(value), `Must be an array, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1533,10 +1541,14 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a plain object.', path, value); | ||
invalid(isObject(value), `Must be a plain object, received ${typeOf(value)}.`, path, value); | ||
} | ||
}]); | ||
} // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
} // All schemas need a default value to operate correctly, | ||
// but functions are a weird one. We want to verify that | ||
// "this value is a function" without needing to return | ||
// a default value (predicates, etc), but also sometimes | ||
// return a default value when undefined is passed | ||
// (option objects, etc). So by default (pun intended), | ||
// this schema's default value is `undefined`. | ||
@@ -1551,7 +1563,4 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'function', 'Must be a function.', path, value); | ||
invalid(typeof value === 'function', `Must be a function, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1571,6 +1580,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a schema.', path, value); | ||
// Dont use `isSchema` and rely on the shape below | ||
invalid(isObject(value), `Must be a schema, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1580,5 +1588,6 @@ | ||
return shape.of({ | ||
schema: func().notNullable().required(), | ||
type: func().notNullable().required(), | ||
validate: func().notNullable().required() | ||
schema: func().notNullable().notUndefinable(), | ||
state: func().notNullable().notUndefinable(), | ||
type: func().notNullable().notUndefinable(), | ||
validate: func().notNullable().notUndefinable() | ||
}); | ||
@@ -1602,6 +1611,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'boolean', 'Must be a boolean.', path, value); | ||
invalid(typeof value === 'boolean', `Must be a boolean, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1630,7 +1637,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
const time = createDate(value); | ||
invalid(isValidDate(time), 'Must be a string, number, or `Date` that resolves to a valid date.', path, value); | ||
invalid(isValidDate(time), `Must be a string, number, or \`Date\` that resolves to a valid date, received ${typeOf(value)}.`, path, value); | ||
return time; | ||
@@ -1650,6 +1655,10 @@ } | ||
}, [state => ({ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
let valueType = typeOf(value); | ||
validate(value, path) { | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? 'Must be a class instance.' : `Must be an instance of ${state.type}.`, path, value); | ||
if (valueType === 'class') { | ||
valueType = prettyValue(value); | ||
} | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? `Must be a class instance, received ${valueType}.` : `Must be an instance of \`${state.type}\`, received ${valueType}.`, path, value); | ||
} | ||
@@ -1671,6 +1680,2 @@ | ||
}, [{ | ||
// Avoid recursion by returning early and using the provided default value | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1702,6 +1707,4 @@ const schema = factory(value); | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'number', 'Must be a number.', path, value); | ||
invalid(typeof value === 'number', `Must be a number, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1726,4 +1729,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1735,3 +1736,3 @@ if (value === undefined) { | ||
invalid(isObject(value), 'Must be a shaped object.', path, value); | ||
invalid(isObject(value), `Must be a shaped object, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1756,6 +1757,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'string', 'Must be a string.', path, value); | ||
invalid(typeof value === 'string', `Must be a string, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1775,4 +1774,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1784,3 +1781,3 @@ if (value === undefined) { | ||
invalid(Array.isArray(value), 'Must be a tuple.', path, value); | ||
invalid(Array.isArray(value), `Must be a tuple, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1825,2 +1822,6 @@ } | ||
try { | ||
if (value == null) { | ||
throw new Error('Avoid null/undefined'); | ||
} | ||
schema.validate(value); | ||
@@ -1858,5 +1859,3 @@ } catch { | ||
return schema.validate(struct, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '', { | ||
collectErrors: true, | ||
...validateOptions, | ||
return schema.validate(struct, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '', { ...validateOptions, | ||
currentObject: object, | ||
@@ -1863,0 +1862,0 @@ rootObject: object |
@@ -31,6 +31,4 @@ // Bundled with Packemon: https://packemon.dev | ||
const key = pathKey(path); | ||
const valueLabel = prettyValue(value); | ||
const typeLabel = key.includes('[') ? 'member' : 'field'; | ||
const label = valueLabel ? `Invalid ${typeLabel} "${key}" with value ${valueLabel}.` : `Invalid ${typeLabel} "${key}".`; | ||
this.message = `${label} ${this.message}`; | ||
const type = key.includes('[') ? 'member' : 'field'; | ||
this.message = `Invalid ${type} "${key}". ${this.message}`; | ||
} | ||
@@ -59,3 +57,3 @@ } | ||
function isSchema(value) { | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
return isObject(value) && typeof value.schema === 'function' && typeof value.state === 'function' && typeof value.type === 'function' && typeof value.validate === 'function'; | ||
} | ||
@@ -167,2 +165,6 @@ | ||
function prettyValue(value) { | ||
if (value instanceof Date) { | ||
return value.toLocaleDateString(); | ||
} | ||
switch (typeof value) { | ||
@@ -194,3 +196,3 @@ case 'string': | ||
function tryAndCollect(validator, validError, collectErrors) { | ||
function collectErrors(collectionError, validator) { | ||
let result = false; | ||
@@ -205,6 +207,4 @@ | ||
} catch (error) { | ||
if (error instanceof Error && collectErrors) { | ||
validError.addError(error); | ||
} else { | ||
throw error; | ||
if (error instanceof Error) { | ||
collectionError.addError(error); | ||
} | ||
@@ -216,20 +216,19 @@ } | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
function extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}) { | ||
return typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
} | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
@@ -244,3 +243,2 @@ /** | ||
function validate(state, validators, initialValue, path = '', { | ||
collectErrors = true, | ||
currentObject = {}, | ||
@@ -254,4 +252,8 @@ rootObject = currentObject | ||
if (value === undefined) { | ||
value = typeof defaultValue === 'function' ? defaultValue(path, currentObject, rootObject) : defaultValue; | ||
invalid(!state.required, 'Field is required and must be defined.', path, undefined); | ||
if (!state.undefinable) { | ||
value = extractDefaultValue(defaultValue, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
} | ||
} else { | ||
@@ -272,25 +274,16 @@ if (process.env.NODE_ENV !== "production" && metadata.deprecatedMessage) { | ||
const optimalError = new OptimalError(); | ||
validators.forEach(test => { | ||
if (test.skipIfNull && value === null || test.skipIfOptional && !state.required && value === state.defaultValue || state.never) { | ||
if (!test.dontSkipIfNull && state.nullable && value === null || !test.dontSkipIfUndefined && state.undefinable && value === undefined || state.never) { | ||
return; | ||
} | ||
tryAndCollect(() => { | ||
const result = test.validate(value, path, { | ||
collectErrors, | ||
currentObject, | ||
rootObject | ||
}); | ||
const result = test.validate(value, path, { | ||
currentObject, | ||
rootObject | ||
}); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}, optimalError, collectErrors); | ||
if (result !== undefined) { | ||
value = result; | ||
} | ||
}); | ||
if (optimalError.errors.length > 0) { | ||
throw optimalError; | ||
} | ||
return value; | ||
@@ -311,3 +304,4 @@ } | ||
required: false, | ||
type | ||
type, | ||
undefinable: false | ||
}; | ||
@@ -331,2 +325,6 @@ const validators = []; | ||
state() { | ||
return state; | ||
}, | ||
type() { | ||
@@ -336,5 +334,15 @@ return state.type; | ||
// @ts-expect-error Ignore null/undefined | ||
validate(value, path, options) { | ||
const result = validate(state, validators, value, path, options); | ||
return cast && result !== null ? cast(result) : result; | ||
if (state.nullable && result === null) { | ||
return null; | ||
} | ||
if (state.undefinable && result === undefined) { | ||
return undefined; | ||
} | ||
return cast ? cast(result) : result; | ||
} | ||
@@ -364,4 +372,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -388,4 +394,2 @@ var _options$message; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -416,8 +420,6 @@ if (!Array.isArray(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message2; | ||
invalid(value.length === size, (_options$message2 = options.message) !== null && _options$message2 !== void 0 ? _options$message2 : `Array length must be ${size}.`, path, value); | ||
invalid(value.length === size, (_options$message2 = options.message) !== null && _options$message2 !== void 0 ? _options$message2 : `Array length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -434,2 +436,4 @@ | ||
}); | ||
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ | ||
/** | ||
@@ -442,6 +446,9 @@ * Require this field to only be false. | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
var _options$message3; | ||
invalid(!value, (_options$message3 = options.message) !== null && _options$message3 !== void 0 ? _options$message3 : 'May only be `false`.', path, value); | ||
invalid(value === false, (_options$message3 = options.message) !== null && _options$message3 !== void 0 ? _options$message3 : 'May only be `false`.', path, value); | ||
} | ||
@@ -459,6 +466,9 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
var _options$message4; | ||
invalid(value, (_options$message4 = options.message) !== null && _options$message4 !== void 0 ? _options$message4 : 'May only be `true`.', path, value); | ||
invalid(value === true, (_options$message4 = options.message) !== null && _options$message4 !== void 0 ? _options$message4 : 'May only be `true`.', path, value); | ||
} | ||
@@ -487,6 +497,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of "${state.type}".`, path, value); | ||
invalid(typeof ref === 'function' && (value instanceof ref || loose && isObject(value) && instanceOf(value, ref)), `Must be an instance of \`${state.type}\`.`, path, value); | ||
} | ||
@@ -501,6 +509,27 @@ | ||
}); | ||
class OptimalError extends ValidationError { | ||
constructor() { | ||
super(''); | ||
_defineProperty(this, "file", ''); | ||
_defineProperty(this, "schema", ''); | ||
this.name = 'OptimalError'; | ||
} | ||
addError(error) { | ||
const validError = error instanceof ValidationError ? error : new ValidationError(error.message); | ||
this.errors.push(validError); // Avoid indenting at this level | ||
this.message = `${this.message}\n${error.message}`.trim(); | ||
} | ||
} | ||
/** | ||
* Map a list of field names that must be defined alongside this field. | ||
* Map a list of field names that must be defined alongside this field when in a shape/object. | ||
*/ | ||
function and(state, ...keys) { | ||
@@ -512,2 +541,5 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -517,3 +549,3 @@ currentObject | ||
const andKeys = [...new Set([pathKey(path), ...keys])].sort(); | ||
const undefs = andKeys.filter(key => (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) === undefined || (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) === null); // Only error once when one of the struct is defined | ||
const undefs = andKeys.filter(key => (currentObject === null || currentObject === void 0 ? void 0 : currentObject[key]) == null); // Only error once when one of the struct is defined | ||
@@ -546,2 +578,4 @@ if (undefs.length === andKeys.length) { | ||
invalid(false, error.message, path, value); | ||
} else if (error instanceof ValidationError || error instanceof OptimalError) { | ||
throw error; | ||
} | ||
@@ -575,18 +609,26 @@ } | ||
/** | ||
* Disallow null values. | ||
* Require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
function required(state) { | ||
state.required = true; | ||
} | ||
/** | ||
* Require this field to NOT be explicitly defined. | ||
* Dont require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
function notRequired(state) { | ||
function optional(state) { | ||
state.required = false; | ||
} | ||
/** | ||
* Disallow null values. | ||
*/ | ||
function notNullable(state) { | ||
state.nullable = false; | ||
} | ||
/** | ||
* Allow null values. | ||
@@ -612,4 +654,8 @@ */ | ||
return { | ||
validate(value, path) { | ||
invalid(value === defaultValue, `Value may only be "${defaultValue}".`, path, value); | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
const testValue = extractDefaultValue(defaultValue, path, validateOptions); | ||
invalid(value === testValue, `Value may only be "${testValue}".`, path, value); | ||
} | ||
@@ -620,3 +666,3 @@ | ||
/** | ||
* Map a list of field names that must have at least 1 defined. | ||
* Map a list of field names that must have at least 1 defined when in a shape/object. | ||
*/ | ||
@@ -631,2 +677,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -643,10 +692,18 @@ currentObject | ||
/** | ||
* Require this field to be explicitly defined. | ||
* Allow undefined values. | ||
*/ | ||
function required(state) { | ||
state.required = true; | ||
function undefinable(state) { | ||
state.undefinable = true; | ||
} | ||
/** | ||
* Disallow undefined values. | ||
*/ | ||
function notUndefinable(state) { | ||
state.undefinable = false; | ||
} | ||
/** | ||
* Validate with a specific schema when a condition is met. | ||
@@ -668,2 +725,5 @@ */ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
@@ -673,3 +733,3 @@ const passed = typeof condition === 'function' ? condition(value, validateOptions.currentObject, validateOptions.rootObject) : condition === value; | ||
if (passed) { | ||
return pass.validate(value, path); | ||
return pass.validate(value, path, validateOptions); | ||
} | ||
@@ -687,3 +747,3 @@ | ||
/** | ||
* Map a list of field names that must not be defined alongside this field. | ||
* Map a list of field names that must not be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -698,2 +758,5 @@ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { | ||
@@ -716,8 +779,10 @@ currentObject | ||
never: never, | ||
required: required, | ||
optional: optional, | ||
notNullable: notNullable, | ||
notRequired: notRequired, | ||
nullable: nullable, | ||
only: only, | ||
or: or, | ||
required: required, | ||
undefinable: undefinable, | ||
notUndefinable: notUndefinable, | ||
when: when, | ||
@@ -738,8 +803,6 @@ xor: xor | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message5; | ||
invalid(isValidDate(value) && value > afterDate, (_options$message5 = options.message) !== null && _options$message5 !== void 0 ? _options$message5 : `Date must come after ${afterDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value > afterDate, (_options$message5 = options.message) !== null && _options$message5 !== void 0 ? _options$message5 : `Date must come after ${prettyValue(afterDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -762,8 +825,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message6; | ||
invalid(isValidDate(value) && value < beforeDate, (_options$message6 = options.message) !== null && _options$message6 !== void 0 ? _options$message6 : `Date must come before ${beforeDate.toLocaleDateString()}.`, path, value); | ||
invalid(isValidDate(value) && value < beforeDate, (_options$message6 = options.message) !== null && _options$message6 !== void 0 ? _options$message6 : `Date must come before ${prettyValue(beforeDate)}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -791,8 +852,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message7; | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), (_options$message7 = options.message) !== null && _options$message7 !== void 0 ? _options$message7 : `Date must be between ${startDate.toLocaleDateString()} and ${endDate.toLocaleDateString()}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidDate(value) && (options.inclusive ? value >= startDate && value <= endDate : value > startDate && value < endDate), (_options$message7 = options.message) !== null && _options$message7 !== void 0 ? _options$message7 : `Date must be between ${prettyValue(startDate)} and ${prettyValue(endDate)}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -819,8 +878,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message8; | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), (_options$message8 = options.message) !== null && _options$message8 !== void 0 ? _options$message8 : `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}.`, path, value); | ||
invalid(isValidNumber(value) && (options.inclusive ? value >= min && value <= max : value > min && value < max), (_options$message8 = options.message) !== null && _options$message8 !== void 0 ? _options$message8 : `Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -837,8 +894,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message9; | ||
invalid(isValidNumber(value) && value % 1 !== 0, (_options$message9 = options.message) !== null && _options$message9 !== void 0 ? _options$message9 : 'Number must be a float.', path, value); | ||
invalid(isValidNumber(value) && value % 1 !== 0, (_options$message9 = options.message) !== null && _options$message9 !== void 0 ? _options$message9 : `Number must be a float, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -859,4 +914,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -866,7 +919,7 @@ if (options.inclusive) { | ||
invalid(isValidNumber(value) && value >= min, (_options$message10 = options.message) !== null && _options$message10 !== void 0 ? _options$message10 : `Number must be greater than or equal to ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value >= min, (_options$message10 = options.message) !== null && _options$message10 !== void 0 ? _options$message10 : `Number must be greater than or equal to ${min}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
var _options$message11; | ||
invalid(isValidNumber(value) && value > min, (_options$message11 = options.message) !== null && _options$message11 !== void 0 ? _options$message11 : `Number must be greater than ${min}.`, path, value); | ||
invalid(isValidNumber(value) && value > min, (_options$message11 = options.message) !== null && _options$message11 !== void 0 ? _options$message11 : `Number must be greater than ${min}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -894,8 +947,6 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message12; | ||
invalid(Number.isSafeInteger(value), (_options$message12 = options.message) !== null && _options$message12 !== void 0 ? _options$message12 : 'Number must be an integer.', path, value); | ||
invalid(Number.isSafeInteger(value), (_options$message12 = options.message) !== null && _options$message12 !== void 0 ? _options$message12 : `Number must be an integer, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -916,4 +967,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -923,7 +972,7 @@ if (options.inclusive) { | ||
invalid(isValidNumber(value) && value <= max, (_options$message13 = options.message) !== null && _options$message13 !== void 0 ? _options$message13 : `Number must be less than or equal to ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value <= max, (_options$message13 = options.message) !== null && _options$message13 !== void 0 ? _options$message13 : `Number must be less than or equal to ${max}, received ${prettyValue(value)}.`, path, value); | ||
} else { | ||
var _options$message14; | ||
invalid(isValidNumber(value) && value < max, (_options$message14 = options.message) !== null && _options$message14 !== void 0 ? _options$message14 : `Number must be less than ${max}.`, path, value); | ||
invalid(isValidNumber(value) && value < max, (_options$message14 = options.message) !== null && _options$message14 !== void 0 ? _options$message14 : `Number must be less than ${max}, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -951,8 +1000,6 @@ } | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message15; | ||
invalid(isValidNumber(value) && value < 0, (_options$message15 = options.message) !== null && _options$message15 !== void 0 ? _options$message15 : 'Number must be negative.', path, value); | ||
invalid(isValidNumber(value) && value < 0, (_options$message15 = options.message) !== null && _options$message15 !== void 0 ? _options$message15 : `Number must be negative, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -973,8 +1020,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message16; | ||
invalid(list.includes(value), (_options$message16 = options.message) !== null && _options$message16 !== void 0 ? _options$message16 : `Number must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), (_options$message16 = options.message) !== null && _options$message16 !== void 0 ? _options$message16 : `Number must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -991,8 +1036,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message17; | ||
invalid(isValidNumber(value) && value > 0, (_options$message17 = options.message) !== null && _options$message17 !== void 0 ? _options$message17 : 'Number must be positive.', path, value); | ||
invalid(isValidNumber(value) && value > 0, (_options$message17 = options.message) !== null && _options$message17 !== void 0 ? _options$message17 : `Number must be positive, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1026,4 +1069,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1055,4 +1096,2 @@ if (isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1079,4 +1118,2 @@ var _options$message19; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1109,8 +1146,9 @@ if (!isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message20; | ||
invalid(Object.keys(value).length === size, (_options$message20 = options.message) !== null && _options$message20 !== void 0 ? _options$message20 : size === 1 ? `Object must have ${size} property.` : `Object must have ${size} properties.`, path, value); | ||
const _Object$keys = Object.keys(value), | ||
length = _Object$keys.length; | ||
invalid(length === size, (_options$message20 = options.message) !== null && _options$message20 !== void 0 ? _options$message20 : size === 1 ? `Object must have ${size} property, received ${length}.` : `Object must have ${size} properties, received ${length}.`, path, value); | ||
} | ||
@@ -1150,4 +1188,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1162,11 +1198,20 @@ if (value) { | ||
const collectionError = new ValidationError('The following validations have failed:', path, value); | ||
const currentValidateOptions = _objectSpread(_objectSpread({}, validateOptions), {}, { | ||
currentObject: value | ||
}); | ||
Object.keys(schemas).forEach(prop => { | ||
const key = prop; | ||
const schema = schemas[key]; | ||
tryAndCollect(() => { | ||
shape[key] = schema.validate(value[key], path ? `${path}.${key}` : String(key), _objectSpread(_objectSpread({}, validateOptions), {}, { | ||
currentObject: value | ||
})); | ||
}, collectionError, validateOptions.collectErrors); // Delete the prop and mark it as known | ||
const currentPath = path ? `${path}.${key}` : String(key); | ||
if (schema.state().required) { | ||
invalid(key in value && value[key] !== undefined, 'Field is required and must be defined.', currentPath, undefined); | ||
} | ||
collectErrors(collectionError, () => { | ||
shape[key] = schema.validate(value[key], currentPath, currentValidateOptions); | ||
}); // Delete the prop and mark it as known | ||
delete unknown[key]; | ||
@@ -1210,5 +1255,2 @@ }); | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1233,5 +1275,2 @@ var _options$index, _options$message21; | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -1292,4 +1331,2 @@ var _options$message22; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1314,8 +1351,6 @@ var _options$message23; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message24; | ||
invalid(list.includes(value), (_options$message24 = options.message) !== null && _options$message24 !== void 0 ? _options$message24 : `String must be one of: ${list.join(', ')}`, path, value); | ||
invalid(list.includes(value), (_options$message24 = options.message) !== null && _options$message24 !== void 0 ? _options$message24 : `String must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1332,8 +1367,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message25; | ||
invalid(value === value.toLocaleLowerCase(), (_options$message25 = options.message) !== null && _options$message25 !== void 0 ? _options$message25 : 'String must be lower cased.', path, value); | ||
invalid(value === value.toLocaleLowerCase(), (_options$message25 = options.message) !== null && _options$message25 !== void 0 ? _options$message25 : `String must be lower cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1350,8 +1383,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message26; | ||
invalid(value === value.toLocaleUpperCase(), (_options$message26 = options.message) !== null && _options$message26 !== void 0 ? _options$message26 : 'String must be upper cased.', path, value); | ||
invalid(value === value.toLocaleUpperCase(), (_options$message26 = options.message) !== null && _options$message26 !== void 0 ? _options$message26 : `String must be upper cased, received ${prettyValue(value)}.`, path, value); | ||
} | ||
@@ -1372,8 +1403,6 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
var _options$message27; | ||
invalid(value.length === size, (_options$message27 = options.message) !== null && _options$message27 !== void 0 ? _options$message27 : `String length must be ${size}.`, path, value); | ||
invalid(value.length === size, (_options$message27 = options.message) !== null && _options$message27 !== void 0 ? _options$message27 : `String length must be ${size}, received ${value.length}.`, path, value); | ||
} | ||
@@ -1410,6 +1439,4 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items.`, path, value); | ||
invalid(Array.isArray(value) && value.length <= itemsSchemas.length, `Value must be a tuple with ${itemsSchemas.length} items, received ${value.length}.`, path, value); | ||
return itemsSchemas.map((item, i) => item.validate(value[i], `${path}[${i}]`, validateOptions)); | ||
@@ -1425,14 +1452,2 @@ } | ||
}); | ||
function typeOf(value) { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
/** | ||
@@ -1442,3 +1457,2 @@ * Require field value to be one of a specific schema type. | ||
function of(state, schemas) { | ||
@@ -1451,4 +1465,2 @@ if (process.env.NODE_ENV !== "production") { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1466,3 +1478,3 @@ let nextValue = value; | ||
return tryAndCollect(() => { | ||
return collectErrors(collectionError, () => { | ||
if (valueType === schemaType || valueType === 'object/shape' && schemaType === 'object' || valueType === 'object/shape' && schemaType === 'shape' || valueType === 'array/tuple' && schemaType === 'array' || valueType === 'array/tuple' && schemaType === 'tuple' || schemaType === 'custom') { | ||
@@ -1475,3 +1487,3 @@ // Dont pass path so its not included in the error message | ||
return false; | ||
}, collectionError, validateOptions.collectErrors); | ||
}); | ||
}); | ||
@@ -1483,3 +1495,3 @@ | ||
} else { | ||
invalid(false, `Value must be one of: ${allowedValues}.`, path, value); | ||
invalid(false, `Received ${valueType} but value must be one of: ${allowedValues}.`, path, value); | ||
} | ||
@@ -1506,6 +1518,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Array.isArray(value), 'Must be an array.', path, value); | ||
invalid(Array.isArray(value), `Must be an array, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1523,10 +1533,14 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a plain object.', path, value); | ||
invalid(isObject(value), `Must be a plain object, received ${typeOf(value)}.`, path, value); | ||
} | ||
}]); | ||
} // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
} // All schemas need a default value to operate correctly, | ||
// but functions are a weird one. We want to verify that | ||
// "this value is a function" without needing to return | ||
// a default value (predicates, etc), but also sometimes | ||
// return a default value when undefined is passed | ||
// (option objects, etc). So by default (pun intended), | ||
// this schema's default value is `undefined`. | ||
@@ -1540,7 +1554,4 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'function', 'Must be a function.', path, value); | ||
invalid(typeof value === 'function', `Must be a function, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1558,6 +1569,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a schema.', path, value); | ||
// Dont use `isSchema` and rely on the shape below | ||
invalid(isObject(value), `Must be a schema, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1567,5 +1577,6 @@ | ||
return shape.of({ | ||
schema: func().notNullable().required(), | ||
type: func().notNullable().required(), | ||
validate: func().notNullable().required() | ||
schema: func().notNullable().notUndefinable(), | ||
state: func().notNullable().notUndefinable(), | ||
type: func().notNullable().notUndefinable(), | ||
validate: func().notNullable().notUndefinable() | ||
}); | ||
@@ -1587,6 +1598,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'boolean', 'Must be a boolean.', path, value); | ||
invalid(typeof value === 'boolean', `Must be a boolean, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1612,7 +1621,5 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
const time = createDate(value); | ||
invalid(isValidDate(time), 'Must be a string, number, or `Date` that resolves to a valid date.', path, value); | ||
invalid(isValidDate(time), `Must be a string, number, or \`Date\` that resolves to a valid date, received ${typeOf(value)}.`, path, value); | ||
return time; | ||
@@ -1630,6 +1637,10 @@ } | ||
}, [state => ({ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
let valueType = typeOf(value); | ||
validate(value, path) { | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? 'Must be a class instance.' : `Must be an instance of ${state.type}.`, path, value); | ||
if (valueType === 'class') { | ||
valueType = prettyValue(value); | ||
} | ||
invalid(isObject(value) && value.constructor !== Object, state.type === 'class' ? `Must be a class instance, received ${valueType}.` : `Must be an instance of \`${state.type}\`, received ${valueType}.`, path, value); | ||
} | ||
@@ -1650,6 +1661,2 @@ | ||
}, [{ | ||
// Avoid recursion by returning early and using the provided default value | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path, validateOptions) { | ||
@@ -1679,6 +1686,4 @@ const schema = factory(value); | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'number', 'Must be a number.', path, value); | ||
invalid(typeof value === 'number', `Must be a number, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1701,4 +1706,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1710,3 +1713,3 @@ if (value === undefined) { | ||
invalid(isObject(value), 'Must be a shaped object.', path, value); | ||
invalid(isObject(value), `Must be a shaped object, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1729,6 +1732,4 @@ } | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'string', 'Must be a string.', path, value); | ||
invalid(typeof value === 'string', `Must be a string, received ${typeOf(value)}.`, path, value); | ||
} | ||
@@ -1746,4 +1747,2 @@ | ||
}, [{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -1755,3 +1754,3 @@ if (value === undefined) { | ||
invalid(Array.isArray(value), 'Must be a tuple.', path, value); | ||
invalid(Array.isArray(value), `Must be a tuple, received ${typeOf(value)}.`, path, value); | ||
return value; | ||
@@ -1794,2 +1793,6 @@ } | ||
try { | ||
if (value == null) { | ||
throw new Error('Avoid null/undefined'); | ||
} | ||
schema.validate(value); | ||
@@ -1827,5 +1830,3 @@ } catch { | ||
return schema.validate(struct, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '', _objectSpread(_objectSpread({ | ||
collectErrors: true | ||
}, validateOptions), {}, { | ||
return schema.validate(struct, (_options$prefix = options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '', _objectSpread(_objectSpread({}, validateOptions), {}, { | ||
currentObject: object, | ||
@@ -1832,0 +1833,0 @@ rootObject: object |
{ | ||
"name": "optimal", | ||
"type": "module", | ||
"version": "5.0.0-alpha.4", | ||
"version": "5.0.0-alpha.5", | ||
"description": "A system for building and validating defined object structures with schemas.", | ||
@@ -57,3 +57,3 @@ "main": "./lib/node/index.js", | ||
}, | ||
"gitHead": "d96228ccbc4dedda3f51667e9833563ed55ea26f" | ||
"gitHead": "9c36f5d65c767a36b1c29a5c052b885552dc6d21" | ||
} |
@@ -8,2 +8,6 @@ import { AnySchema, InferSchemaType, Predicate } from './types'; | ||
try { | ||
if (value == null) { | ||
throw new Error('Avoid null/undefined'); | ||
} | ||
schema.validate(value); | ||
@@ -10,0 +14,0 @@ } catch { |
@@ -1,3 +0,2 @@ | ||
import { invalid, tryAndCollect } from './helpers'; | ||
import { OptimalError } from './OptimalError'; | ||
import { extractDefaultValue, invalid } from './helpers'; | ||
import { | ||
@@ -7,3 +6,2 @@ AnySchema, | ||
CriteriaFactory, | ||
DefaultValueInitializer, | ||
InferSchemaType, | ||
@@ -26,10 +24,5 @@ Schema, | ||
path: string = '', | ||
{ | ||
collectErrors = true, | ||
currentObject = {}, | ||
rootObject = currentObject, | ||
}: SchemaValidateOptions = {}, | ||
): T | null { | ||
{ currentObject = {}, rootObject = currentObject }: SchemaValidateOptions = {}, | ||
): T | null | undefined { | ||
const { defaultValue, metadata } = state; | ||
let value: unknown = initialValue; | ||
@@ -39,8 +32,5 @@ | ||
if (value === undefined) { | ||
value = | ||
typeof defaultValue === 'function' | ||
? (defaultValue as DefaultValueInitializer<T>)(path, currentObject, rootObject) | ||
: defaultValue; | ||
invalid(!state.required, 'Field is required and must be defined.', path, undefined); | ||
if (!state.undefinable) { | ||
value = extractDefaultValue(defaultValue, path, { currentObject, rootObject }); | ||
} | ||
} else { | ||
@@ -61,8 +51,6 @@ if (__DEV__ && metadata.deprecatedMessage) { | ||
// Run validations and produce a new value | ||
const optimalError = new OptimalError(); | ||
validators.forEach((test) => { | ||
if ( | ||
(test.skipIfNull && value === null) || | ||
(test.skipIfOptional && !state.required && value === state.defaultValue) || | ||
(!test.dontSkipIfNull && state.nullable && value === null) || | ||
(!test.dontSkipIfUndefined && state.undefinable && value === undefined) || | ||
state.never | ||
@@ -73,23 +61,12 @@ ) { | ||
tryAndCollect( | ||
() => { | ||
const result = test.validate(value as T, path, { | ||
collectErrors, | ||
currentObject, | ||
rootObject, | ||
}); | ||
const result = test.validate(value as T, path, { | ||
currentObject, | ||
rootObject, | ||
}); | ||
if (result !== undefined) { | ||
value = result as T; | ||
} | ||
}, | ||
optimalError, | ||
collectErrors, | ||
); | ||
if (result !== undefined) { | ||
value = result as T; | ||
} | ||
}); | ||
if (optimalError.errors.length > 0) { | ||
throw optimalError; | ||
} | ||
return value as T; | ||
@@ -109,2 +86,3 @@ } | ||
type, | ||
undefinable: false, | ||
}; | ||
@@ -130,9 +108,21 @@ | ||
}, | ||
state() { | ||
return state; | ||
}, | ||
type() { | ||
return state.type; | ||
}, | ||
// @ts-expect-error Ignore null/undefined | ||
validate(value, path, options) { | ||
const result = validate(state, validators, value, path, options)!; | ||
const result = validate(state, validators, value, path, options); | ||
return cast && result !== null ? cast(result) : result; | ||
if (state.nullable && result === null) { | ||
return null; | ||
} | ||
if (state.undefinable && result === undefined) { | ||
return undefined; | ||
} | ||
return cast ? cast(result) : result; | ||
}, | ||
@@ -139,0 +129,0 @@ }; |
@@ -9,3 +9,2 @@ import { invalid, invariant, isSchema } from '../helpers'; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -27,3 +26,2 @@ invalid(value.length > 0, options.message ?? 'Array cannot be empty.', path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -56,7 +54,6 @@ if (!Array.isArray(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
value.length === size, | ||
options.message ?? `Array length must be ${size}.`, | ||
options.message ?? `Array length must be ${size}, received ${value.length}.`, | ||
path, | ||
@@ -63,0 +60,0 @@ value, |
@@ -0,1 +1,3 @@ | ||
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ | ||
import { invalid } from '../helpers'; | ||
@@ -11,4 +13,6 @@ import { Criteria, Options, SchemaState } from '../types'; | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
invalid(!value, options.message ?? 'May only be `false`.', path, value); | ||
invalid(value === false, options.message ?? 'May only be `false`.', path, value); | ||
}, | ||
@@ -25,6 +29,8 @@ }; | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path) { | ||
invalid(value, options.message ?? 'May only be `true`.', path, value); | ||
invalid(value === true, options.message ?? 'May only be `true`.', path, value); | ||
}, | ||
}; | ||
} |
@@ -17,3 +17,2 @@ import { instanceOf, invalid, invariant, isObject } from '../helpers'; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -23,3 +22,3 @@ invalid( | ||
(value instanceof ref || (loose && isObject(value) && instanceOf(value, ref))), | ||
`Must be an instance of "${state.type}".`, | ||
`Must be an instance of \`${state.type}\`.`, | ||
path, | ||
@@ -26,0 +25,0 @@ value, |
@@ -1,6 +0,15 @@ | ||
import { invalid, invariant, isSchema, isValidString, pathKey } from '../helpers'; | ||
import { | ||
extractDefaultValue, | ||
invalid, | ||
invariant, | ||
isSchema, | ||
isValidString, | ||
pathKey, | ||
} from '../helpers'; | ||
import { OptimalError } from '../OptimalError'; | ||
import { Criteria, CriteriaValidator, Schema, SchemaState, ValueComparator } from '../types'; | ||
import { ValidationError } from '../ValidationError'; | ||
/** | ||
* Map a list of field names that must be defined alongside this field. | ||
* Map a list of field names that must be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -11,7 +20,7 @@ export function and<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T> { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { currentObject }) { | ||
const andKeys = [...new Set([pathKey(path), ...keys])].sort(); | ||
const undefs = andKeys.filter( | ||
(key) => currentObject?.[key] === undefined || currentObject?.[key] === null, | ||
); | ||
const undefs = andKeys.filter((key) => currentObject?.[key] == null); | ||
@@ -41,2 +50,4 @@ // Only error once when one of the struct is defined | ||
invalid(false, error.message, path, value); | ||
} else if (error instanceof ValidationError || error instanceof OptimalError) { | ||
throw error; | ||
} | ||
@@ -66,12 +77,12 @@ } | ||
/** | ||
* Disallow null values. | ||
* Require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
export function notNullable<T>(state: SchemaState<T>) { | ||
state.nullable = false; | ||
export function required<T>(state: SchemaState<T>) { | ||
state.required = true; | ||
} | ||
/** | ||
* Require this field to NOT be explicitly defined. | ||
* Dont require this field to be explicitly defined when in a shape/object. | ||
*/ | ||
export function notRequired<T>(state: SchemaState<T>) { | ||
export function optional<T>(state: SchemaState<T>) { | ||
state.required = false; | ||
@@ -81,2 +92,9 @@ } | ||
/** | ||
* Disallow null values. | ||
*/ | ||
export function notNullable<T>(state: SchemaState<T>) { | ||
state.nullable = false; | ||
} | ||
/** | ||
* Allow null values. | ||
@@ -100,4 +118,8 @@ */ | ||
return { | ||
validate(value, path) { | ||
invalid(value === defaultValue, `Value may only be "${defaultValue}".`, path, value); | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
const testValue = extractDefaultValue(defaultValue, path, validateOptions); | ||
invalid(value === testValue, `Value may only be "${testValue}".`, path, value); | ||
}, | ||
@@ -108,3 +130,3 @@ }; | ||
/** | ||
* Map a list of field names that must have at least 1 defined. | ||
* Map a list of field names that must have at least 1 defined when in a shape/object. | ||
*/ | ||
@@ -115,2 +137,4 @@ export function or<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T> { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { currentObject }) { | ||
@@ -131,9 +155,16 @@ const orKeys = [...new Set([pathKey(path), ...keys])].sort(); | ||
/** | ||
* Require this field to be explicitly defined. | ||
* Allow undefined values. | ||
*/ | ||
export function required<T>(state: SchemaState<T>) { | ||
state.required = true; | ||
export function undefinable<T>(state: SchemaState<T>) { | ||
state.undefinable = true; | ||
} | ||
/** | ||
* Disallow undefined values. | ||
*/ | ||
export function notUndefinable<T>(state: SchemaState<T>) { | ||
state.undefinable = false; | ||
} | ||
/** | ||
* Validate with a specific schema when a condition is met. | ||
@@ -154,2 +185,4 @@ */ | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, validateOptions) { | ||
@@ -166,3 +199,3 @@ const passed = | ||
if (passed) { | ||
return pass.validate(value, path); | ||
return pass.validate(value, path, validateOptions); | ||
} | ||
@@ -180,3 +213,3 @@ | ||
/** | ||
* Map a list of field names that must not be defined alongside this field. | ||
* Map a list of field names that must not be defined alongside this field when in a shape/object. | ||
*/ | ||
@@ -187,2 +220,4 @@ export function xor<T>(state: SchemaState<T>, ...keys: string[]): Criteria<T> { | ||
return { | ||
dontSkipIfNull: true, | ||
dontSkipIfUndefined: true, | ||
validate(value, path, { currentObject }) { | ||
@@ -189,0 +224,0 @@ const xorKeys = [...new Set([pathKey(path), ...keys])].sort(); |
@@ -1,2 +0,2 @@ | ||
import { createDate, invalid, invariant, isValidDate } from '../helpers'; | ||
import { createDate, invalid, invariant, isValidDate, prettyValue } from '../helpers'; | ||
import { Criteria, InclusiveOptions, MaybeDate, Options, SchemaState } from '../types'; | ||
@@ -17,7 +17,7 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
isValidDate(value) && value > afterDate, | ||
options.message ?? `Date must come after ${afterDate.toLocaleDateString()}.`, | ||
options.message ?? | ||
`Date must come after ${prettyValue(afterDate)}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -43,7 +43,7 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
isValidDate(value) && value < beforeDate, | ||
options.message ?? `Date must come before ${beforeDate.toLocaleDateString()}.`, | ||
options.message ?? | ||
`Date must come before ${prettyValue(beforeDate)}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -72,3 +72,2 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -81,5 +80,5 @@ invalid( | ||
options.message ?? | ||
`Date must be between ${startDate.toLocaleDateString()} and ${endDate.toLocaleDateString()}${ | ||
`Date must be between ${prettyValue(startDate)} and ${prettyValue(endDate)}${ | ||
options.inclusive ? ' inclusive' : '' | ||
}.`, | ||
}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -86,0 +85,0 @@ value, |
@@ -1,2 +0,2 @@ | ||
import { invalid, invariant, isValidNumber } from '../helpers'; | ||
import { invalid, invariant, isValidNumber, prettyValue } from '../helpers'; | ||
import { Criteria, InclusiveOptions, Options, SchemaState } from '../types'; | ||
@@ -19,3 +19,2 @@ | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -26,3 +25,5 @@ invalid( | ||
options.message ?? | ||
`Number must be between ${min} and ${max}${options.inclusive ? ' inclusive' : ''}.`, | ||
`Number must be between ${min} and ${max}${ | ||
options.inclusive ? ' inclusive' : '' | ||
}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -40,7 +41,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
isValidNumber(value) && value % 1 !== 0, | ||
options.message ?? 'Number must be a float.', | ||
options.message ?? `Number must be a float, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -64,3 +64,2 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -70,3 +69,4 @@ if (options.inclusive) { | ||
isValidNumber(value) && value >= min, | ||
options.message ?? `Number must be greater than or equal to ${min}.`, | ||
options.message ?? | ||
`Number must be greater than or equal to ${min}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -78,3 +78,3 @@ value, | ||
isValidNumber(value) && value > min, | ||
options.message ?? `Number must be greater than ${min}.`, | ||
options.message ?? `Number must be greater than ${min}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -104,7 +104,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
Number.isSafeInteger(value), | ||
options.message ?? 'Number must be an integer.', | ||
options.message ?? `Number must be an integer, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -128,3 +127,2 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -134,3 +132,4 @@ if (options.inclusive) { | ||
isValidNumber(value) && value <= max, | ||
options.message ?? `Number must be less than or equal to ${max}.`, | ||
options.message ?? | ||
`Number must be less than or equal to ${max}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -142,3 +141,3 @@ value, | ||
isValidNumber(value) && value < max, | ||
options.message ?? `Number must be less than ${max}.`, | ||
options.message ?? `Number must be less than ${max}, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -168,7 +167,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
isValidNumber(value) && value < 0, | ||
options.message ?? 'Number must be negative.', | ||
options.message ?? `Number must be negative, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -195,7 +193,7 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
list.includes(value), | ||
options.message ?? `Number must be one of: ${list.join(', ')}`, | ||
options.message ?? | ||
`Number must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, | ||
path, | ||
@@ -213,7 +211,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
isValidNumber(value) && value > 0, | ||
options.message ?? 'Number must be positive.', | ||
options.message ?? `Number must be positive, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -220,0 +217,0 @@ value, |
@@ -19,3 +19,2 @@ import { invalid, invariant, isObject, isSchema } from '../helpers'; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -51,3 +50,2 @@ if (isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -77,3 +75,2 @@ invalid( | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -112,10 +109,11 @@ if (!isObject(value)) { | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
const { length } = Object.keys(value); | ||
invalid( | ||
Object.keys(value).length === size, | ||
length === size, | ||
options.message ?? | ||
(size === 1 | ||
? `Object must have ${size} property.` | ||
: `Object must have ${size} properties.`), | ||
? `Object must have ${size} property, received ${length}.` | ||
: `Object must have ${size} properties, received ${length}.`), | ||
path, | ||
@@ -122,0 +120,0 @@ value, |
@@ -1,2 +0,2 @@ | ||
import { invalid, invariant, isObject, isSchema, logUnknown, tryAndCollect } from '../helpers'; | ||
import { collectErrors, invalid, invariant, isObject, isSchema, logUnknown } from '../helpers'; | ||
import { Blueprint, Criteria, Schema, SchemaState, UnknownObject } from '../types'; | ||
@@ -30,3 +30,2 @@ import { ValidationError } from '../ValidationError'; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -45,2 +44,6 @@ if (value) { | ||
); | ||
const currentValidateOptions = { | ||
...validateOptions, | ||
currentObject: value as UnknownObject, | ||
}; | ||
@@ -50,14 +53,17 @@ Object.keys(schemas).forEach((prop) => { | ||
const schema = schemas[key]; | ||
const currentPath = path ? `${path}.${key}` : String(key); | ||
tryAndCollect( | ||
() => { | ||
shape[key] = schema.validate(value[key], path ? `${path}.${key}` : String(key), { | ||
...validateOptions, | ||
currentObject: value as UnknownObject, | ||
}); | ||
}, | ||
collectionError, | ||
validateOptions.collectErrors, | ||
); | ||
if (schema.state().required) { | ||
invalid( | ||
key in value && value[key] !== undefined, | ||
'Field is required and must be defined.', | ||
currentPath, | ||
undefined, | ||
); | ||
} | ||
collectErrors(collectionError, () => { | ||
shape[key] = schema.validate(value[key], currentPath, currentValidateOptions); | ||
}); | ||
// Delete the prop and mark it as known | ||
@@ -64,0 +70,0 @@ delete unknown[key]; |
@@ -1,2 +0,2 @@ | ||
import { invalid, invariant, isValidString } from '../helpers'; | ||
import { invalid, invariant, isValidString, prettyValue } from '../helpers'; | ||
import { Criteria, Options, SchemaState } from '../types'; | ||
@@ -15,4 +15,2 @@ | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -40,4 +38,2 @@ invalid( | ||
return { | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
@@ -99,3 +95,2 @@ invalid( | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -121,7 +116,7 @@ invalid(isValidString(value), options.message ?? 'String cannot be empty.', path, value); | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
list.includes(value), | ||
options.message ?? `String must be one of: ${list.join(', ')}`, | ||
options.message ?? | ||
`String must be one of: ${list.join(', ')}. Received ${prettyValue(value)}.`, | ||
path, | ||
@@ -139,7 +134,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
value === value.toLocaleLowerCase(), | ||
options.message ?? 'String must be lower cased.', | ||
options.message ?? `String must be lower cased, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -157,7 +151,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
value === value.toLocaleUpperCase(), | ||
options.message ?? 'String must be upper cased.', | ||
options.message ?? `String must be upper cased, received ${prettyValue(value)}.`, | ||
path, | ||
@@ -181,7 +174,6 @@ value, | ||
return { | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid( | ||
value.length === size, | ||
options.message ?? `String length must be ${size}.`, | ||
options.message ?? `String length must be ${size}, received ${value.length}.`, | ||
path, | ||
@@ -188,0 +180,0 @@ value, |
@@ -32,7 +32,6 @@ import { invalid, invariant, isSchema } from '../helpers'; | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
invalid( | ||
Array.isArray(value) && value.length <= itemsSchemas.length, | ||
`Value must be a tuple with ${itemsSchemas.length} items.`, | ||
`Value must be a tuple with ${itemsSchemas.length} items, received ${value.length}.`, | ||
path, | ||
@@ -39,0 +38,0 @@ value, |
@@ -1,17 +0,5 @@ | ||
import { invalid, invariant, isObject, isSchema, tryAndCollect } from '../helpers'; | ||
import { collectErrors, invalid, invariant, isSchema, typeOf } from '../helpers'; | ||
import { Criteria, Schema, SchemaState } from '../types'; | ||
import { ValidationError } from '../ValidationError'; | ||
function typeOf(value: unknown): string { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} | ||
/** | ||
@@ -29,3 +17,2 @@ * Require field value to be one of a specific schema type. | ||
return { | ||
skipIfNull: true, | ||
validate(value, path, validateOptions) { | ||
@@ -48,23 +35,19 @@ let nextValue: unknown = value; | ||
return tryAndCollect( | ||
() => { | ||
if ( | ||
valueType === schemaType || | ||
(valueType === 'object/shape' && schemaType === 'object') || | ||
(valueType === 'object/shape' && schemaType === 'shape') || | ||
(valueType === 'array/tuple' && schemaType === 'array') || | ||
(valueType === 'array/tuple' && schemaType === 'tuple') || | ||
schemaType === 'custom' | ||
) { | ||
// Dont pass path so its not included in the error message | ||
nextValue = schema.validate(value, '', validateOptions); | ||
return collectErrors(collectionError, () => { | ||
if ( | ||
valueType === schemaType || | ||
(valueType === 'object/shape' && schemaType === 'object') || | ||
(valueType === 'object/shape' && schemaType === 'shape') || | ||
(valueType === 'array/tuple' && schemaType === 'array') || | ||
(valueType === 'array/tuple' && schemaType === 'tuple') || | ||
schemaType === 'custom' | ||
) { | ||
// Dont pass path so its not included in the error message | ||
nextValue = schema.validate(value, '', validateOptions); | ||
return true; | ||
} | ||
return true; | ||
} | ||
return false; | ||
}, | ||
collectionError, | ||
validateOptions.collectErrors, | ||
); | ||
return false; | ||
}); | ||
}); | ||
@@ -76,3 +59,8 @@ | ||
} else { | ||
invalid(false, `Value must be one of: ${allowedValues}.`, path, value); | ||
invalid( | ||
false, | ||
`Received ${valueType} but value must be one of: ${allowedValues}.`, | ||
path, | ||
value, | ||
); | ||
} | ||
@@ -79,0 +67,0 @@ } |
@@ -1,2 +0,9 @@ | ||
import { Constructor, Schema, UnknownObject } from './types'; | ||
import { | ||
Constructor, | ||
DefaultValue, | ||
DefaultValueInitializer, | ||
Schema, | ||
SchemaValidateOptions, | ||
UnknownObject, | ||
} from './types'; | ||
import { ValidationError } from './ValidationError'; | ||
@@ -12,2 +19,3 @@ | ||
typeof (value as UnknownObject).schema === 'function' && | ||
typeof (value as UnknownObject).state === 'function' && | ||
typeof (value as UnknownObject).type === 'function' && | ||
@@ -139,2 +147,6 @@ typeof (value as UnknownObject).validate === 'function' | ||
export function prettyValue(value: unknown): string | null { | ||
if (value instanceof Date) { | ||
return value.toLocaleDateString(); | ||
} | ||
switch (typeof value) { | ||
@@ -165,6 +177,5 @@ case 'string': | ||
export function tryAndCollect( | ||
export function collectErrors( | ||
collectionError: ValidationError, | ||
validator: () => boolean | void, | ||
validError: ValidationError, | ||
collectErrors?: boolean, | ||
): boolean { | ||
@@ -180,6 +191,6 @@ let result = false; | ||
} catch (error: unknown) { | ||
if (error instanceof Error && collectErrors) { | ||
validError.addError(error); | ||
if (error instanceof Error) { | ||
collectionError.addError(error); | ||
} else { | ||
throw error; | ||
// How? | ||
} | ||
@@ -190,1 +201,23 @@ } | ||
} | ||
export function extractDefaultValue<T>( | ||
defaultValue: DefaultValue<T>, | ||
path: string, | ||
{ currentObject, rootObject }: SchemaValidateOptions, | ||
) { | ||
return typeof defaultValue === 'function' | ||
? (defaultValue as DefaultValueInitializer<T>)(path, currentObject!, rootObject!) | ||
: defaultValue; | ||
} | ||
export function typeOf(value: unknown): string { | ||
if (Array.isArray(value)) { | ||
return 'array/tuple'; | ||
} | ||
if (isObject(value)) { | ||
return value.constructor === Object ? 'object/shape' : 'class'; | ||
} | ||
return typeof value; | ||
} |
@@ -44,3 +44,2 @@ import { isObject } from './helpers'; | ||
return schema.validate(struct, options.prefix ?? '', { | ||
collectErrors: true, | ||
...validateOptions, | ||
@@ -47,0 +46,0 @@ currentObject: object, |
import { createSchema } from '../createSchema'; | ||
import { arrayCriteria, commonCriteria } from '../criteria'; | ||
import { createArray, invalid } from '../helpers'; | ||
import { ArrayCriterias, CommonCriterias, DefaultValue, InferNullable, Schema } from '../types'; | ||
import { createArray, invalid, typeOf } from '../helpers'; | ||
import { | ||
ArrayCriterias, | ||
CommonCriterias, | ||
DefaultValue, | ||
InferNullable, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
} from '../types'; | ||
@@ -11,5 +19,7 @@ export interface ArraySchema<T = unknown[]> | ||
never: () => ArraySchema<never>; | ||
notNullable: () => ArraySchema<NonNullable<T>>; | ||
notNullable: () => ArraySchema<NotNull<T>>; | ||
notUndefinable: () => ArraySchema<NotUndefined<T>>; | ||
nullable: () => ArraySchema<T | null>; | ||
of: <V>(schema: Schema<V>) => ArraySchema<InferNullable<T, V[]>>; | ||
undefinable: () => ArraySchema<T | undefined>; | ||
} | ||
@@ -27,5 +37,9 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(Array.isArray(value), 'Must be an array.', path, value); | ||
invalid( | ||
Array.isArray(value), | ||
`Must be an array, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -32,0 +46,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { booleanCriteria, commonCriteria } from '../criteria'; | ||
import { invalid } from '../helpers'; | ||
import { CommonCriterias, DefaultValue, Options, Schema } from '../types'; | ||
import { invalid, typeOf } from '../helpers'; | ||
import { CommonCriterias, DefaultValue, NotNull, NotUndefined, Options, Schema } from '../types'; | ||
export interface BooleanSchema<T = boolean> extends Schema<T>, CommonCriterias<BooleanSchema<T>> { | ||
never: () => BooleanSchema<never>; | ||
notNullable: () => BooleanSchema<NonNullable<T>>; | ||
notNullable: () => BooleanSchema<NotNull<T>>; | ||
notUndefinable: () => BooleanSchema<NotUndefined<T>>; | ||
nullable: () => BooleanSchema<T | null>; | ||
onlyFalse: (options?: Options) => BooleanSchema<false>; | ||
onlyTrue: (options?: Options) => BooleanSchema<true>; | ||
undefinable: () => BooleanSchema<T | undefined>; | ||
} | ||
@@ -24,5 +26,9 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'boolean', 'Must be a boolean.', path, value); | ||
invalid( | ||
typeof value === 'boolean', | ||
`Must be a boolean, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -29,0 +35,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria } from '../criteria'; | ||
import { CommonCriterias, CriteriaValidator, DefaultValue, Schema } from '../types'; | ||
import { | ||
CommonCriterias, | ||
CriteriaValidator, | ||
DefaultValue, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
} from '../types'; | ||
export interface CustomSchema<T> extends Schema<T>, CommonCriterias<CustomSchema<T>> { | ||
never: () => CustomSchema<never>; | ||
notNullable: () => CustomSchema<NonNullable<T>>; | ||
notNullable: () => CustomSchema<NotNull<T>>; | ||
notUndefinable: () => CustomSchema<NotUndefined<T>>; | ||
nullable: () => CustomSchema<T | null>; | ||
undefinable: () => CustomSchema<T | undefined>; | ||
} | ||
export function custom<T>(validator: CriteriaValidator<T>, defaultValue?: DefaultValue<T>) { | ||
export function custom<T>(validator: CriteriaValidator<T>, defaultValue: DefaultValue<T>) { | ||
return createSchema<CustomSchema<T>>({ | ||
@@ -13,0 +22,0 @@ api: { ...commonCriteria }, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, dateCriteria } from '../criteria'; | ||
import { createDate, invalid, isValidDate } from '../helpers'; | ||
import { CommonCriterias, DateCriterias, DefaultValue, Schema } from '../types'; | ||
import { createDate, invalid, isValidDate, typeOf } from '../helpers'; | ||
import { | ||
CommonCriterias, | ||
DateCriterias, | ||
DefaultValue, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
} from '../types'; | ||
@@ -11,4 +18,6 @@ export interface DateSchema<T = Date> | ||
never: () => DateSchema<never>; | ||
notNullable: () => DateSchema<NonNullable<T>>; | ||
notNullable: () => DateSchema<NotNull<T>>; | ||
notUndefinable: () => DateSchema<NotUndefined<T>>; | ||
nullable: () => DateSchema<T | null>; | ||
undefinable: () => DateSchema<T | undefined>; | ||
} | ||
@@ -26,3 +35,2 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -33,3 +41,5 @@ const time = createDate(value); | ||
isValidDate(time), | ||
'Must be a string, number, or `Date` that resolves to a valid date.', | ||
`Must be a string, number, or \`Date\` that resolves to a valid date, received ${typeOf( | ||
value, | ||
)}.`, | ||
path, | ||
@@ -36,0 +46,0 @@ value, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria } from '../criteria'; | ||
import { invalid } from '../helpers'; | ||
import { AnyFunction,CommonCriterias, DefaultValue, Schema } from '../types'; | ||
import { invalid, typeOf } from '../helpers'; | ||
import { | ||
AnyFunction, | ||
CommonCriterias, | ||
DefaultValueInitializer, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
} from '../types'; | ||
@@ -10,9 +17,18 @@ export interface FunctionSchema<T = AnyFunction> | ||
never: () => FunctionSchema<never>; | ||
notNullable: () => FunctionSchema<NonNullable<T>>; | ||
notNullable: () => FunctionSchema<NotNull<T>>; | ||
notUndefinable: () => FunctionSchema<NotUndefined<T>>; | ||
nullable: () => FunctionSchema<T | null>; | ||
undefinable: () => FunctionSchema<T | undefined>; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function func<T extends (...args: any[]) => any = AnyFunction>( | ||
defaultValue?: DefaultValue<T>, | ||
// All schemas need a default value to operate correctly, | ||
// but functions are a weird one. We want to verify that | ||
// "this value is a function" without needing to return | ||
// a default value (predicates, etc), but also sometimes | ||
// return a default value when undefined is passed | ||
// (option objects, etc). So by default (pun intended), | ||
// this schema's default value is `undefined`. | ||
export function func<T extends AnyFunction = AnyFunction>( | ||
defaultValue?: DefaultValueInitializer<T>, | ||
) { | ||
@@ -27,6 +43,9 @@ return createSchema<FunctionSchema<T>>( | ||
{ | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'function', 'Must be a function.', path, value); | ||
invalid( | ||
typeof value === 'function', | ||
`Must be a function, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -33,0 +52,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { classCriteria, commonCriteria } from '../criteria'; | ||
import { invalid, isObject } from '../helpers'; | ||
import { CommonCriterias, Constructor, InferNullable, Schema } from '../types'; | ||
import { invalid, isObject, prettyValue, typeOf } from '../helpers'; | ||
import { | ||
CommonCriterias, | ||
Constructor, | ||
InferNullable, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
} from '../types'; | ||
export interface InstanceSchema<T> extends Schema<T>, CommonCriterias<InstanceSchema<T>> { | ||
never: () => InstanceSchema<never>; | ||
notNullable: () => InstanceSchema<NonNullable<T>>; | ||
notNullable: () => InstanceSchema<NotNull<T>>; | ||
notUndefinable: () => InstanceSchema<NotUndefined<T>>; | ||
nullable: () => InstanceSchema<T | null>; | ||
of: <C>(ref: Constructor<C>, loose?: boolean) => InstanceSchema<InferNullable<T, C>>; | ||
undefinable: () => InstanceSchema<T | undefined>; | ||
} | ||
@@ -22,9 +31,14 @@ | ||
(state) => ({ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
let valueType = typeOf(value); | ||
if (valueType === 'class') { | ||
valueType = prettyValue(value)!; | ||
} | ||
invalid( | ||
isObject(value) && value.constructor !== Object, | ||
state.type === 'class' | ||
? 'Must be a class instance.' | ||
: `Must be an instance of ${state.type}.`, | ||
? `Must be a class instance, received ${valueType}.` | ||
: `Must be an instance of \`${state.type}\`, received ${valueType}.`, | ||
path, | ||
@@ -31,0 +45,0 @@ value, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria } from '../criteria'; | ||
import { invariant, isSchema } from '../helpers'; | ||
import { DefaultValue, Schema } from '../types'; | ||
import { DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface LazySchema<T = boolean> extends Schema<T> { | ||
never: () => LazySchema<never>; | ||
notNullable: () => LazySchema<NonNullable<T>>; | ||
notNullable: () => LazySchema<NotNull<T>>; | ||
notUndefinable: () => LazySchema<NotUndefined<T>>; | ||
nullable: () => LazySchema<T | null>; | ||
undefinable: () => LazySchema<T | undefined>; | ||
} | ||
@@ -26,5 +28,2 @@ | ||
{ | ||
// Avoid recursion by returning early and using the provided default value | ||
skipIfNull: true, | ||
skipIfOptional: true, | ||
validate(value, path, validateOptions) { | ||
@@ -31,0 +30,0 @@ const schema = factory(value); |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, numberCriteria } from '../criteria'; | ||
import { invalid } from '../helpers'; | ||
import { invalid, typeOf } from '../helpers'; | ||
import { | ||
@@ -8,2 +8,4 @@ CommonCriterias, | ||
InferNullable, | ||
NotNull, | ||
NotUndefined, | ||
NumberCriterias, | ||
@@ -19,3 +21,4 @@ Options, | ||
never: () => NumberSchema<never>; | ||
notNullable: () => NumberSchema<NonNullable<T>>; | ||
notNullable: () => NumberSchema<NotNull<T>>; | ||
notUndefinable: () => NumberSchema<NotUndefined<T>>; | ||
nullable: () => NumberSchema<T | null>; | ||
@@ -26,2 +29,3 @@ oneOf: <I extends number = number>( | ||
) => NumberSchema<InferNullable<T, I>>; | ||
undefinable: () => NumberSchema<T | undefined>; | ||
} | ||
@@ -43,5 +47,9 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'number', 'Must be a number.', path, value); | ||
invalid( | ||
typeof value === 'number', | ||
`Must be a number, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -48,0 +56,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, objectCriteria } from '../criteria'; | ||
import { createObject, invalid, isObject } from '../helpers'; | ||
import { createObject, invalid, isObject, typeOf } from '../helpers'; | ||
import { | ||
@@ -8,2 +8,4 @@ CommonCriterias, | ||
InferNullable, | ||
NotNull, | ||
NotUndefined, | ||
ObjectCriterias, | ||
@@ -21,3 +23,4 @@ Options, | ||
never: () => ObjectSchema<never>; | ||
notNullable: () => ObjectSchema<NonNullable<T>>; | ||
notNullable: () => ObjectSchema<NotNull<T>>; | ||
notUndefinable: () => ObjectSchema<NotUndefined<T>>; | ||
nullable: () => ObjectSchema<T | null>; | ||
@@ -27,2 +30,3 @@ of: <V, K extends PropertyKey = keyof T>( | ||
) => ObjectSchema<InferNullable<T, Record<K, V>>>; | ||
undefinable: () => ObjectSchema<T | undefined>; | ||
} | ||
@@ -42,5 +46,9 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a plain object.', path, value); | ||
invalid( | ||
isObject(value), | ||
`Must be a plain object, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -47,0 +55,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, shapeCriteria } from '../criteria'; | ||
import { createObject, invalid, isObject } from '../helpers'; | ||
import { createObject, invalid, isObject, typeOf } from '../helpers'; | ||
import { AnySchema } from '../types'; | ||
@@ -18,5 +18,5 @@ import { func } from './func'; | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(isObject(value), 'Must be a schema.', path, value); | ||
// Dont use `isSchema` and rely on the shape below | ||
invalid(isObject(value), `Must be a schema, received ${typeOf(value)}.`, path, value); | ||
}, | ||
@@ -28,6 +28,7 @@ }, | ||
return shape.of({ | ||
schema: func<T['schema']>().notNullable().required(), | ||
type: func<T['type']>().notNullable().required(), | ||
validate: func<T['validate']>().notNullable().required(), | ||
schema: func<T['schema']>().notNullable().notUndefinable(), | ||
state: func<T['state']>().notNullable().notUndefinable(), | ||
type: func<T['type']>().notNullable().notUndefinable(), | ||
validate: func<T['validate']>().notNullable().notUndefinable(), | ||
}) as unknown as ShapeSchema<T>; | ||
} |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, shapeCriteria } from '../criteria'; | ||
import { createObject, invalid, isObject } from '../helpers'; | ||
import { Blueprint, CommonCriterias, Schema, ShapeCriterias } from '../types'; | ||
import { createObject, invalid, isObject, typeOf } from '../helpers'; | ||
import { | ||
Blueprint, | ||
CommonCriterias, | ||
NotNull, | ||
NotUndefined, | ||
Schema, | ||
ShapeCriterias, | ||
} from '../types'; | ||
@@ -11,6 +18,8 @@ export interface ShapeSchema<T> | ||
never: () => ShapeSchema<never>; | ||
notNullable: () => ShapeSchema<NonNullable<T>>; | ||
notNullable: () => ShapeSchema<NotNull<T>>; | ||
notUndefinable: () => ShapeSchema<NotUndefined<T>>; | ||
nullable: () => ShapeSchema<T | null>; | ||
/** @internal */ | ||
of: <S extends object>(schema: Blueprint<S>) => ShapeSchema<S>; | ||
undefinable: () => ShapeSchema<T | undefined>; | ||
} | ||
@@ -27,3 +36,2 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -35,3 +43,8 @@ if (value === undefined) { | ||
invalid(isObject(value), 'Must be a shaped object.', path, value); | ||
invalid( | ||
isObject(value), | ||
`Must be a shaped object, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
@@ -38,0 +51,0 @@ return value; |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, stringCriteria } from '../criteria'; | ||
import { invalid } from '../helpers'; | ||
import { invalid, typeOf } from '../helpers'; | ||
import { | ||
@@ -8,2 +8,4 @@ CommonCriterias, | ||
InferNullable, | ||
NotNull, | ||
NotUndefined, | ||
Options, | ||
@@ -19,3 +21,4 @@ Schema, | ||
never: () => StringSchema<never>; | ||
notNullable: () => StringSchema<NonNullable<T>>; | ||
notNullable: () => StringSchema<NotNull<T>>; | ||
notUndefinable: () => StringSchema<NotUndefined<T>>; | ||
nullable: () => StringSchema<T | null>; | ||
@@ -26,2 +29,3 @@ oneOf: <I extends string = string>( | ||
) => StringSchema<InferNullable<T, I>>; | ||
undefinable: () => StringSchema<T | undefined>; | ||
} | ||
@@ -45,5 +49,9 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
invalid(typeof value === 'string', 'Must be a string.', path, value); | ||
invalid( | ||
typeof value === 'string', | ||
`Must be a string, received ${typeOf(value)}.`, | ||
path, | ||
value, | ||
); | ||
}, | ||
@@ -50,0 +58,0 @@ }, |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, tupleCriteria } from '../criteria'; | ||
import { InferTupleItems } from '../criteria/tuples'; | ||
import { createArray, invalid } from '../helpers'; | ||
import { CommonCriterias, Schema } from '../types'; | ||
import { createArray, invalid, typeOf } from '../helpers'; | ||
import { CommonCriterias, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface TupleSchema<T> extends Schema<Required<T>>, CommonCriterias<TupleSchema<T>> { | ||
never: () => TupleSchema<never>; | ||
notNullable: () => TupleSchema<NonNullable<T>>; | ||
notNullable: () => TupleSchema<NotNull<T>>; | ||
notUndefinable: () => TupleSchema<NotUndefined<T>>; | ||
nullable: () => TupleSchema<T | null>; | ||
/** @internal */ | ||
of: <I extends unknown[]>(schemas: InferTupleItems<I>) => TupleSchema<I>; | ||
undefinable: () => TupleSchema<T | undefined>; | ||
} | ||
@@ -27,3 +29,2 @@ | ||
{ | ||
skipIfNull: true, | ||
validate(value, path) { | ||
@@ -35,3 +36,3 @@ if (value === undefined) { | ||
invalid(Array.isArray(value), 'Must be a tuple.', path, value); | ||
invalid(Array.isArray(value), `Must be a tuple, received ${typeOf(value)}.`, path, value); | ||
@@ -38,0 +39,0 @@ return value; |
import { createSchema } from '../createSchema'; | ||
import { commonCriteria, unionCriteria } from '../criteria'; | ||
import { AnySchema, CommonCriterias, DefaultValue, Schema } from '../types'; | ||
import { AnySchema, CommonCriterias, DefaultValue, NotNull, NotUndefined, Schema } from '../types'; | ||
export interface UnionSchema<T> extends Schema<T>, CommonCriterias<UnionSchema<T>> { | ||
never: () => UnionSchema<never>; | ||
notNullable: () => UnionSchema<NonNullable<T>>; | ||
notNullable: () => UnionSchema<NotNull<T>>; | ||
notUndefinable: () => UnionSchema<NotUndefined<T>>; | ||
nullable: () => UnionSchema<T | null>; | ||
// Distribute these types in the future. Currently breaks on nulls... | ||
of: (schemas: AnySchema[]) => UnionSchema<T>; | ||
undefinable: () => UnionSchema<T | undefined>; | ||
} | ||
@@ -12,0 +14,0 @@ |
@@ -46,4 +46,4 @@ export type Primitive = bigint | boolean | number | string | symbol | null | undefined; | ||
export interface Criteria<Input> { | ||
skipIfNull?: boolean; | ||
skipIfOptional?: boolean; | ||
dontSkipIfNull?: boolean; | ||
dontSkipIfUndefined?: boolean; | ||
validate: CriteriaValidator<Input>; | ||
@@ -61,4 +61,4 @@ } | ||
deprecate: (message: string) => S; | ||
notRequired: () => S; | ||
only: () => S; | ||
optional: () => S; | ||
or: (...keys: string[]) => S; | ||
@@ -72,2 +72,4 @@ required: () => S; | ||
// nullable: () => S; | ||
// notUndefinable: () => S; | ||
// undefinable: () => S; | ||
} | ||
@@ -131,3 +133,2 @@ | ||
export interface SchemaValidateOptions { | ||
collectErrors?: boolean; | ||
currentObject?: UnknownObject; | ||
@@ -139,2 +140,3 @@ rootObject?: UnknownObject; | ||
schema: () => string; | ||
state: () => SchemaState<Output>; | ||
type: () => string; | ||
@@ -151,2 +153,3 @@ validate: (value: unknown, path?: string, options?: SchemaValidateOptions) => Output; | ||
type: string; | ||
undefinable: boolean; | ||
} | ||
@@ -187,2 +190,6 @@ | ||
export type NotNull<T> = T extends null ? never : T; | ||
export type NotUndefined<T> = T extends undefined ? never : T; | ||
// Any is required for generics to be typed correctly for consumers | ||
@@ -189,0 +196,0 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ |
@@ -1,2 +0,2 @@ | ||
import { pathKey, prettyValue } from './helpers'; | ||
import { pathKey } from './helpers'; | ||
@@ -19,9 +19,5 @@ export class ValidationError extends Error { | ||
const key = pathKey(path); | ||
const valueLabel = prettyValue(value); | ||
const typeLabel = key.includes('[') ? 'member' : 'field'; | ||
const label = valueLabel | ||
? `Invalid ${typeLabel} "${key}" with value ${valueLabel}.` | ||
: `Invalid ${typeLabel} "${key}".`; | ||
const type = key.includes('[') ? 'member' : 'field'; | ||
this.message = `${label} ${this.message}`; | ||
this.message = `Invalid ${type} "${key}". ${this.message}`; | ||
} | ||
@@ -28,0 +24,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
371304
7163