|
|
|
|
@ -3,23 +3,39 @@ import { none, Option, some } from "@shared/utils/option.ts";
|
|
|
|
|
|
|
|
|
|
class ParseError extends Error {
|
|
|
|
|
type = "ParseError";
|
|
|
|
|
|
|
|
|
|
public trace: NestedArray<string> = [];
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
public readonly input: any,
|
|
|
|
|
public input: any,
|
|
|
|
|
trace: NestedArray<string> | string,
|
|
|
|
|
public readonly msg: string,
|
|
|
|
|
) {
|
|
|
|
|
super(msg);
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(trace)) {
|
|
|
|
|
this.trace = trace;
|
|
|
|
|
} else {
|
|
|
|
|
this.trace = [trace];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stackParseErr(trace: string, input: any): ParseError {
|
|
|
|
|
this.trace = [trace, this.trace];
|
|
|
|
|
this.input = input;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pe(input: unknown, msg: string) {
|
|
|
|
|
return new ParseError(input, msg);
|
|
|
|
|
function pe(input: unknown, trace: NestedArray<string>, msg: string) {
|
|
|
|
|
return new ParseError(input, trace, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Schema<T> {
|
|
|
|
|
parse(input: unknown): Result<T, ParseError>;
|
|
|
|
|
checkIfValid(input: unknown): boolean;
|
|
|
|
|
nullable(): NullableSchema<this>;
|
|
|
|
|
option(): OptionSchema<T>;
|
|
|
|
|
nullable(): NullableSchema<Schema<T>>;
|
|
|
|
|
option(): OptionSchema<Schema<T>>;
|
|
|
|
|
or<S extends Schema<any>[]>(...schema: S): UnionSchema<[this, ...S]>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -47,7 +63,7 @@ export abstract class BaseSchema<T> implements Schema<T> {
|
|
|
|
|
return this.parse(input).isOk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nullable(): NullableSchema<this> {
|
|
|
|
|
nullable(): NullableSchema<Schema<T>> {
|
|
|
|
|
return new NullableSchema(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -55,27 +71,14 @@ export abstract class BaseSchema<T> implements Schema<T> {
|
|
|
|
|
return new UnionSchema(this, ...schema);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
option(): OptionSchema<T> {
|
|
|
|
|
option(): OptionSchema<Schema<T>> {
|
|
|
|
|
return new OptionSchema(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abstract parse(input: unknown): Result<T, ParseError>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export abstract class PrimitiveSchema<
|
|
|
|
|
T extends
|
|
|
|
|
| string
|
|
|
|
|
| number
|
|
|
|
|
| boolean
|
|
|
|
|
| bigint
|
|
|
|
|
| undefined
|
|
|
|
|
| null
|
|
|
|
|
| object
|
|
|
|
|
| void
|
|
|
|
|
| any
|
|
|
|
|
| unknown
|
|
|
|
|
| never,
|
|
|
|
|
> extends BaseSchema<T> {
|
|
|
|
|
export abstract class PrimitiveSchema<T> extends BaseSchema<T> {
|
|
|
|
|
protected abstract initialCheck(input: unknown): Result<T, ParseError>;
|
|
|
|
|
|
|
|
|
|
protected checkPrimitive<U = T>(
|
|
|
|
|
@ -130,42 +133,53 @@ export class StringSchema extends PrimitiveSchema<string> {
|
|
|
|
|
|
|
|
|
|
public max(
|
|
|
|
|
length: number,
|
|
|
|
|
msg: string = `String length must be at most ${length} characters long`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `String length must be at most ${length} characters long`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input.length < length ? pe(input, msg) : undefined
|
|
|
|
|
input.length <= length ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public min(
|
|
|
|
|
length: number,
|
|
|
|
|
msg: string =
|
|
|
|
|
`String length must be at least ${length} characters long`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace =
|
|
|
|
|
`String length must be at least ${length} characters long`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input.length < length ? pe(input, msg) : undefined
|
|
|
|
|
input.length >= length ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public regex(
|
|
|
|
|
pattern: RegExp,
|
|
|
|
|
msg: string = `String must match the patter ${String(pattern)}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `String length must match the pattern ${String(pattern)}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
pattern.test(input) ? undefined : pe(input, msg)
|
|
|
|
|
pattern.test(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public email(
|
|
|
|
|
msg: string = `String must match a valid email address`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
return this.regex(StringSchema.emailRegex, msg);
|
|
|
|
|
const trace = `String must be a valid email address`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
StringSchema.emailRegex.test(input)
|
|
|
|
|
? undefined
|
|
|
|
|
: pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ip(
|
|
|
|
|
msg: string = `String must match a valid ip address`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
return this.regex(StringSchema.ipRegex, msg);
|
|
|
|
|
const trace = `String must be a valid ip address`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
StringSchema.ipRegex.test(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -176,76 +190,116 @@ export class NumberSchema extends PrimitiveSchema<number> {
|
|
|
|
|
return this.checkPrimitive(input, "number");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gt(num: number, msg: string = `Number must be greater than ${num}`): this {
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input <= num ? pe(input, msg) : undefined
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gte(
|
|
|
|
|
public gt(
|
|
|
|
|
num: number,
|
|
|
|
|
msg: string = `Number must be greater than or equal to ${num}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be greates than ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < num ? pe(input, msg) : undefined
|
|
|
|
|
input > num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lt(num: number, msg: string = `Number must be less than ${num}`): this {
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input >= num ? pe(input, msg) : undefined
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lte(
|
|
|
|
|
public gte(
|
|
|
|
|
num: number,
|
|
|
|
|
msg: string = `Number must be less than or equal to ${num}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be greates than or equal to ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input > num ? pe(input, msg) : undefined
|
|
|
|
|
input >= num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int(msg: string = "Number must be an integer"): this {
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isInteger(input) ? pe(input, msg) : undefined
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
positive(msg: string = "Number must be positive"): this {
|
|
|
|
|
return this.gte(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonnegative(msg: string = "Number must be nonnegative"): this {
|
|
|
|
|
return this.gt(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
negative(msg: string = "Number must be negative"): this {
|
|
|
|
|
return this.lt(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonpositive(msg: string = "Number must be nonpositive"): this {
|
|
|
|
|
return this.lte(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finite(msg: string = "Number must be finite"): this {
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isFinite(input) ? undefined : pe(input, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
safe(msg: string = "Number must be a safe integer"): this {
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isSafeInteger(input) ? undefined : pe(input, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multipleOf(
|
|
|
|
|
public lt(
|
|
|
|
|
num: number,
|
|
|
|
|
msg: string = `Number must be a multiple of ${num}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be less than ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input % num ? undefined : pe(input, msg)
|
|
|
|
|
input < num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public lte(
|
|
|
|
|
num: number,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be less than or equal to ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input <= num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be an integer`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isInteger(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public positive(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be positive`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input > 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public nonnegative(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be nonnegative`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input >= 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public negative(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be negative`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public nonpositive(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be nonpositive`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public finite(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be finite`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isFinite(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public safe(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be a safe integer`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isSafeInteger(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public multipleOf(
|
|
|
|
|
num: number,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Number must be a multiple of ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input % num === 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -257,82 +311,116 @@ export class BigintSchema extends PrimitiveSchema<bigint> {
|
|
|
|
|
return this.checkPrimitive(input, "bigint");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gt(
|
|
|
|
|
num: bigint | number,
|
|
|
|
|
msg: string = `Bigint must be greater than ${num}`,
|
|
|
|
|
public gt(
|
|
|
|
|
num: number | bigint,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be greates than ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input <= num ? pe(input, msg) : undefined
|
|
|
|
|
input > num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gte(
|
|
|
|
|
num: bigint | number,
|
|
|
|
|
msg: string = `Bigint must be greater than or equal to ${num}`,
|
|
|
|
|
public gte(
|
|
|
|
|
num: number | bigint,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be greates than or equal to ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < num ? pe(input, msg) : undefined
|
|
|
|
|
input >= num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lt(
|
|
|
|
|
num: bigint | number,
|
|
|
|
|
msg: string = `Bigint must be less than ${num}`,
|
|
|
|
|
public lt(
|
|
|
|
|
num: number | bigint,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be less than ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input >= num ? pe(input, msg) : undefined
|
|
|
|
|
input < num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lte(
|
|
|
|
|
num: bigint | number,
|
|
|
|
|
msg: string = `Bigint must be less than or equal to ${num}`,
|
|
|
|
|
public lte(
|
|
|
|
|
num: number | bigint,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be less than or equal to ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input > num ? pe(input, msg) : undefined
|
|
|
|
|
input <= num ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int(msg: string = "Bigint must be an integer"): this {
|
|
|
|
|
public int(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be an integer`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isInteger(input) ? pe(input, msg) : undefined
|
|
|
|
|
Number.isInteger(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
positive(msg: string = "Bigint must be positive"): this {
|
|
|
|
|
return this.gte(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonnegative(msg: string = "Bigint must be nonnegative"): this {
|
|
|
|
|
return this.gt(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
negative(msg: string = "Bigint must be negative"): this {
|
|
|
|
|
return this.lt(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nonpositive(msg: string = "Bigint must be nonpositive"): this {
|
|
|
|
|
return this.lte(0, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finite(msg: string = "Bigint must be finite"): this {
|
|
|
|
|
public positive(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be positive`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isFinite(input) ? undefined : pe(input, msg)
|
|
|
|
|
input > 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
safe(msg: string = "Bigint must be a safe integer"): this {
|
|
|
|
|
public nonnegative(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be nonnegative`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isSafeInteger(input) ? undefined : pe(input, msg)
|
|
|
|
|
input >= 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multipleOf(
|
|
|
|
|
public negative(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be negative`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public nonpositive(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be nonpositive`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input < 0 ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public finite(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be finite`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isFinite(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public safe(
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be a safe integer`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
Number.isSafeInteger(input) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public multipleOf(
|
|
|
|
|
num: bigint,
|
|
|
|
|
msg: string = `Bigint must be a multiple of ${num}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
): this {
|
|
|
|
|
const trace = `Bigint must be a multiple of ${num}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input % num ? undefined : pe(input, msg)
|
|
|
|
|
input % num === BigInt(0) ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -362,21 +450,23 @@ export class DateSchema extends PrimitiveSchema<object> {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
min(
|
|
|
|
|
public min(
|
|
|
|
|
date: Date,
|
|
|
|
|
msg: string = `Date must be after ${date.toLocaleString()}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
) {
|
|
|
|
|
const trace = `Date must be after ${date.toLocaleString()}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input <= date ? pe(input, msg) : undefined
|
|
|
|
|
input >= date ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
max(
|
|
|
|
|
public max(
|
|
|
|
|
date: Date,
|
|
|
|
|
msg: string = `Date must be before ${date.toLocaleString()}`,
|
|
|
|
|
msg?: string,
|
|
|
|
|
) {
|
|
|
|
|
const trace = `Date must be before ${date.toLocaleString()}`;
|
|
|
|
|
return this.addCheck((input) =>
|
|
|
|
|
input >= date ? pe(input, msg) : undefined
|
|
|
|
|
input <= date ? undefined : pe(input, trace, msg)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -413,7 +503,7 @@ class VoidSchema extends PrimitiveSchema<void> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AnySchema extends PrimitiveSchema<any> {
|
|
|
|
|
protected override initialCheck(input: unknown): Result<any, ParseError> {
|
|
|
|
|
protected override initialCheck(input: any): Result<any, ParseError> {
|
|
|
|
|
return ok(input);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -426,8 +516,6 @@ class UnknownSchema extends PrimitiveSchema<unknown> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type InferSchema<S> = S extends Schema<infer T> ? T : never;
|
|
|
|
|
|
|
|
|
|
class ObjectSchema<O extends Record<string, Schema<any>>>
|
|
|
|
|
extends PrimitiveSchema<{ [K in keyof O]: InferSchema<O[K]> }> {
|
|
|
|
|
private strict: boolean = false;
|
|
|
|
|
@ -453,9 +541,9 @@ class ObjectSchema<O extends Record<string, Schema<any>>>
|
|
|
|
|
|
|
|
|
|
if (checkResult.isErr()) {
|
|
|
|
|
return err(
|
|
|
|
|
pe(
|
|
|
|
|
checkResult.error.stackParseErr(
|
|
|
|
|
`Failed to parse '${key}' attribute`,
|
|
|
|
|
input,
|
|
|
|
|
`Failed to parse '${key}' attribute: ${checkResult.error.msg}`,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -507,21 +595,60 @@ class LiteralSchema<L extends string> extends PrimitiveSchema<L> {
|
|
|
|
|
type InferSchemaUnion<S extends Schema<any>[]> = S[number] extends
|
|
|
|
|
Schema<infer U> ? U : never;
|
|
|
|
|
|
|
|
|
|
type TypeOfString =
|
|
|
|
|
| "string"
|
|
|
|
|
| "number"
|
|
|
|
|
| "bigint"
|
|
|
|
|
| "boolean"
|
|
|
|
|
| "symbol"
|
|
|
|
|
| "undefined"
|
|
|
|
|
| "object"
|
|
|
|
|
| "function";
|
|
|
|
|
|
|
|
|
|
class UnionSchema<S extends Schema<any>[]>
|
|
|
|
|
extends PrimitiveSchema<InferSchemaUnion<S>> {
|
|
|
|
|
private readonly schemas: S;
|
|
|
|
|
private static readonly schemasTypes: Partial<
|
|
|
|
|
Record<string, TypeOfString>
|
|
|
|
|
> = {
|
|
|
|
|
StringSchema: "string",
|
|
|
|
|
LiteralSchema: "string",
|
|
|
|
|
NumberSchema: "number",
|
|
|
|
|
BigintSchema: "bigint",
|
|
|
|
|
BooleanSchema: "boolean",
|
|
|
|
|
UndefinedSchema: "undefined",
|
|
|
|
|
VoidSchema: "undefined",
|
|
|
|
|
};
|
|
|
|
|
private readonly primitiveTypesMap: Map<TypeOfString, Schema<any>[]> =
|
|
|
|
|
new Map();
|
|
|
|
|
private readonly othersTypes: Schema<any>[] = [];
|
|
|
|
|
|
|
|
|
|
constructor(...schemas: S) {
|
|
|
|
|
super();
|
|
|
|
|
this.schemas = schemas;
|
|
|
|
|
|
|
|
|
|
for (const schema of schemas) {
|
|
|
|
|
const type = UnionSchema.schemasTypes[schema.constructor.name];
|
|
|
|
|
|
|
|
|
|
if (type !== undefined) {
|
|
|
|
|
if (!this.primitiveTypesMap.has(type)) {
|
|
|
|
|
this.primitiveTypesMap.set(type, []);
|
|
|
|
|
}
|
|
|
|
|
const schemasForType = this.primitiveTypesMap.get(type);
|
|
|
|
|
schemasForType?.push(schema);
|
|
|
|
|
} else {
|
|
|
|
|
this.othersTypes.push(schema);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override initialCheck(
|
|
|
|
|
input: unknown,
|
|
|
|
|
): Result<InferSchemaUnion<S>, ParseError> {
|
|
|
|
|
const schemas = this.primitiveTypesMap.get(typeof input) ||
|
|
|
|
|
this.othersTypes;
|
|
|
|
|
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
|
|
|
|
|
for (const schema of this.schemas) {
|
|
|
|
|
for (const schema of schemas) {
|
|
|
|
|
const checkResult = schema.parse(input);
|
|
|
|
|
|
|
|
|
|
if (checkResult.isOk()) {
|
|
|
|
|
@ -529,17 +656,27 @@ class UnionSchema<S extends Schema<any>[]>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errors.push(
|
|
|
|
|
`${schema.constructor.name} - ${checkResult.error.msg}`,
|
|
|
|
|
`${schema.constructor.name} - ${
|
|
|
|
|
checkResult.error.trace.join("\n")
|
|
|
|
|
}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const type = typeof input;
|
|
|
|
|
return err(
|
|
|
|
|
pe(
|
|
|
|
|
input,
|
|
|
|
|
[
|
|
|
|
|
"No matching schema found for a union:",
|
|
|
|
|
`UnionSchema (${
|
|
|
|
|
this.primitiveTypesMap.keys().toArray().join(" | ")
|
|
|
|
|
}${
|
|
|
|
|
this.othersTypes.length > 0
|
|
|
|
|
? "object"
|
|
|
|
|
: ""
|
|
|
|
|
}) - failed to parse input as any of the schemas:`,
|
|
|
|
|
errors.join("\n"),
|
|
|
|
|
].join("\n"),
|
|
|
|
|
"Failed to match union",
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -565,7 +702,7 @@ class ArraySchema<S extends Schema<any>>
|
|
|
|
|
return err(
|
|
|
|
|
pe(
|
|
|
|
|
input,
|
|
|
|
|
`Element at index ${i} does not conform to schema:\n${r.error.msg}`,
|
|
|
|
|
`Array. Failed to parse element at index ${i}:\n${r.error.trace}`,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -612,10 +749,11 @@ class ResultSchema<T, E> extends PrimitiveSchema<Result<T, E>> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class OptionSchema<T> extends PrimitiveSchema<Option<T>> {
|
|
|
|
|
class OptionSchema<S extends Schema<any>>
|
|
|
|
|
extends PrimitiveSchema<Option<InferSchema<S>>> {
|
|
|
|
|
private schema;
|
|
|
|
|
|
|
|
|
|
constructor(private readonly valueSchema: Schema<T>) {
|
|
|
|
|
constructor(private readonly valueSchema: S) {
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
this.schema = new UnionSchema(
|
|
|
|
|
@ -706,6 +844,12 @@ class Validator {
|
|
|
|
|
|
|
|
|
|
const v = new Validator();
|
|
|
|
|
|
|
|
|
|
const r = v.array(v.union(v.string(), v.number().gt(5)));
|
|
|
|
|
const r = v.string().max(4, "too long").or(v.number());
|
|
|
|
|
|
|
|
|
|
console.log(r.parse(["5", true]));
|
|
|
|
|
const res = r.parse(some("11234"));
|
|
|
|
|
|
|
|
|
|
console.log(res);
|
|
|
|
|
|
|
|
|
|
type InferSchema<S> = S extends Schema<infer T> ? T : never;
|
|
|
|
|
|
|
|
|
|
type NestedArray<T> = T | NestedArray<T>[];
|
|
|
|
|
|