working on validator
This commit is contained in:
@ -3,23 +3,39 @@ import { none, Option, some } from "@shared/utils/option.ts";
|
|||||||
|
|
||||||
class ParseError extends Error {
|
class ParseError extends Error {
|
||||||
type = "ParseError";
|
type = "ParseError";
|
||||||
|
|
||||||
|
public trace: NestedArray<string> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly input: any,
|
public input: any,
|
||||||
|
trace: NestedArray<string> | string,
|
||||||
public readonly msg: string,
|
public readonly msg: string,
|
||||||
) {
|
) {
|
||||||
super(msg);
|
super(msg);
|
||||||
|
|
||||||
|
if (Array.isArray(trace)) {
|
||||||
|
this.trace = trace;
|
||||||
|
} else {
|
||||||
|
this.trace = [trace];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pe(input: unknown, msg: string) {
|
stackParseErr(trace: string, input: any): ParseError {
|
||||||
return new ParseError(input, msg);
|
this.trace = [trace, this.trace];
|
||||||
|
this.input = input;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pe(input: unknown, trace: NestedArray<string>, msg: string) {
|
||||||
|
return new ParseError(input, trace, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Schema<T> {
|
export interface Schema<T> {
|
||||||
parse(input: unknown): Result<T, ParseError>;
|
parse(input: unknown): Result<T, ParseError>;
|
||||||
checkIfValid(input: unknown): boolean;
|
checkIfValid(input: unknown): boolean;
|
||||||
nullable(): NullableSchema<this>;
|
nullable(): NullableSchema<Schema<T>>;
|
||||||
option(): OptionSchema<T>;
|
option(): OptionSchema<Schema<T>>;
|
||||||
or<S extends Schema<any>[]>(...schema: S): UnionSchema<[this, ...S]>;
|
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();
|
return this.parse(input).isOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
nullable(): NullableSchema<this> {
|
nullable(): NullableSchema<Schema<T>> {
|
||||||
return new NullableSchema(this);
|
return new NullableSchema(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,27 +71,14 @@ export abstract class BaseSchema<T> implements Schema<T> {
|
|||||||
return new UnionSchema(this, ...schema);
|
return new UnionSchema(this, ...schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
option(): OptionSchema<T> {
|
option(): OptionSchema<Schema<T>> {
|
||||||
return new OptionSchema(this);
|
return new OptionSchema(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract parse(input: unknown): Result<T, ParseError>;
|
abstract parse(input: unknown): Result<T, ParseError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class PrimitiveSchema<
|
export abstract class PrimitiveSchema<T> extends BaseSchema<T> {
|
||||||
T extends
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| bigint
|
|
||||||
| undefined
|
|
||||||
| null
|
|
||||||
| object
|
|
||||||
| void
|
|
||||||
| any
|
|
||||||
| unknown
|
|
||||||
| never,
|
|
||||||
> extends BaseSchema<T> {
|
|
||||||
protected abstract initialCheck(input: unknown): Result<T, ParseError>;
|
protected abstract initialCheck(input: unknown): Result<T, ParseError>;
|
||||||
|
|
||||||
protected checkPrimitive<U = T>(
|
protected checkPrimitive<U = T>(
|
||||||
@ -130,42 +133,53 @@ export class StringSchema extends PrimitiveSchema<string> {
|
|||||||
|
|
||||||
public max(
|
public max(
|
||||||
length: number,
|
length: number,
|
||||||
msg: string = `String length must be at most ${length} characters long`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `String length must be at most ${length} characters long`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input.length < length ? pe(input, msg) : undefined
|
input.length <= length ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public min(
|
public min(
|
||||||
length: number,
|
length: number,
|
||||||
msg: string =
|
msg?: string,
|
||||||
`String length must be at least ${length} characters long`,
|
|
||||||
): this {
|
): this {
|
||||||
|
const trace =
|
||||||
|
`String length must be at least ${length} characters long`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input.length < length ? pe(input, msg) : undefined
|
input.length >= length ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public regex(
|
public regex(
|
||||||
pattern: RegExp,
|
pattern: RegExp,
|
||||||
msg: string = `String must match the patter ${String(pattern)}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `String length must match the pattern ${String(pattern)}`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
pattern.test(input) ? undefined : pe(input, msg)
|
pattern.test(input) ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public email(
|
public email(
|
||||||
msg: string = `String must match a valid email address`,
|
msg?: string,
|
||||||
): this {
|
): 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(
|
public ip(
|
||||||
msg: string = `String must match a valid ip address`,
|
msg?: string,
|
||||||
): this {
|
): 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");
|
return this.checkPrimitive(input, "number");
|
||||||
}
|
}
|
||||||
|
|
||||||
gt(num: number, msg: string = `Number must be greater than ${num}`): this {
|
public gt(
|
||||||
return this.addCheck((input) =>
|
|
||||||
input <= num ? pe(input, msg) : undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
gte(
|
|
||||||
num: number,
|
num: number,
|
||||||
msg: string = `Number must be greater than or equal to ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Number must be greates than ${num}`;
|
||||||
return this.addCheck((input) =>
|
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 {
|
public gte(
|
||||||
return this.addCheck((input) =>
|
|
||||||
input >= num ? pe(input, msg) : undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
lte(
|
|
||||||
num: number,
|
num: number,
|
||||||
msg: string = `Number must be less than or equal to ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Number must be greates than or equal to ${num}`;
|
||||||
return this.addCheck((input) =>
|
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 {
|
public lt(
|
||||||
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(
|
|
||||||
num: number,
|
num: number,
|
||||||
msg: string = `Number must be a multiple of ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Number must be less than ${num}`;
|
||||||
return this.addCheck((input) =>
|
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");
|
return this.checkPrimitive(input, "bigint");
|
||||||
}
|
}
|
||||||
|
|
||||||
gt(
|
public gt(
|
||||||
num: bigint | number,
|
num: number | bigint,
|
||||||
msg: string = `Bigint must be greater than ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Bigint must be greates than ${num}`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input <= num ? pe(input, msg) : undefined
|
input > num ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
gte(
|
public gte(
|
||||||
num: bigint | number,
|
num: number | bigint,
|
||||||
msg: string = `Bigint must be greater than or equal to ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Bigint must be greates than or equal to ${num}`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input < num ? pe(input, msg) : undefined
|
input >= num ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
lt(
|
public lt(
|
||||||
num: bigint | number,
|
num: number | bigint,
|
||||||
msg: string = `Bigint must be less than ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Bigint must be less than ${num}`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input >= num ? pe(input, msg) : undefined
|
input < num ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
lte(
|
public lte(
|
||||||
num: bigint | number,
|
num: number | bigint,
|
||||||
msg: string = `Bigint must be less than or equal to ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Bigint must be less than or equal to ${num}`;
|
||||||
return this.addCheck((input) =>
|
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) =>
|
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 {
|
public positive(
|
||||||
return this.gte(0, msg);
|
msg?: string,
|
||||||
}
|
): this {
|
||||||
|
const trace = `Bigint must be positive`;
|
||||||
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 {
|
|
||||||
return this.addCheck((input) =>
|
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) =>
|
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,
|
num: bigint,
|
||||||
msg: string = `Bigint must be a multiple of ${num}`,
|
msg?: string,
|
||||||
): this {
|
): this {
|
||||||
|
const trace = `Bigint must be a multiple of ${num}`;
|
||||||
return this.addCheck((input) =>
|
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,
|
date: Date,
|
||||||
msg: string = `Date must be after ${date.toLocaleString()}`,
|
msg?: string,
|
||||||
) {
|
) {
|
||||||
|
const trace = `Date must be after ${date.toLocaleString()}`;
|
||||||
return this.addCheck((input) =>
|
return this.addCheck((input) =>
|
||||||
input <= date ? pe(input, msg) : undefined
|
input >= date ? undefined : pe(input, trace, msg)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
max(
|
public max(
|
||||||
date: Date,
|
date: Date,
|
||||||
msg: string = `Date must be before ${date.toLocaleString()}`,
|
msg?: string,
|
||||||
) {
|
) {
|
||||||
|
const trace = `Date must be before ${date.toLocaleString()}`;
|
||||||
return this.addCheck((input) =>
|
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> {
|
class AnySchema extends PrimitiveSchema<any> {
|
||||||
protected override initialCheck(input: unknown): Result<any, ParseError> {
|
protected override initialCheck(input: any): Result<any, ParseError> {
|
||||||
return ok(input);
|
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>>>
|
class ObjectSchema<O extends Record<string, Schema<any>>>
|
||||||
extends PrimitiveSchema<{ [K in keyof O]: InferSchema<O[K]> }> {
|
extends PrimitiveSchema<{ [K in keyof O]: InferSchema<O[K]> }> {
|
||||||
private strict: boolean = false;
|
private strict: boolean = false;
|
||||||
@ -453,9 +541,9 @@ class ObjectSchema<O extends Record<string, Schema<any>>>
|
|||||||
|
|
||||||
if (checkResult.isErr()) {
|
if (checkResult.isErr()) {
|
||||||
return err(
|
return err(
|
||||||
pe(
|
checkResult.error.stackParseErr(
|
||||||
|
`Failed to parse '${key}' attribute`,
|
||||||
input,
|
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
|
type InferSchemaUnion<S extends Schema<any>[]> = S[number] extends
|
||||||
Schema<infer U> ? U : never;
|
Schema<infer U> ? U : never;
|
||||||
|
|
||||||
|
type TypeOfString =
|
||||||
|
| "string"
|
||||||
|
| "number"
|
||||||
|
| "bigint"
|
||||||
|
| "boolean"
|
||||||
|
| "symbol"
|
||||||
|
| "undefined"
|
||||||
|
| "object"
|
||||||
|
| "function";
|
||||||
|
|
||||||
class UnionSchema<S extends Schema<any>[]>
|
class UnionSchema<S extends Schema<any>[]>
|
||||||
extends PrimitiveSchema<InferSchemaUnion<S>> {
|
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) {
|
constructor(...schemas: S) {
|
||||||
super();
|
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(
|
protected override initialCheck(
|
||||||
input: unknown,
|
input: unknown,
|
||||||
): Result<InferSchemaUnion<S>, ParseError> {
|
): Result<InferSchemaUnion<S>, ParseError> {
|
||||||
|
const schemas = this.primitiveTypesMap.get(typeof input) ||
|
||||||
|
this.othersTypes;
|
||||||
|
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
for (const schema of this.schemas) {
|
for (const schema of schemas) {
|
||||||
const checkResult = schema.parse(input);
|
const checkResult = schema.parse(input);
|
||||||
|
|
||||||
if (checkResult.isOk()) {
|
if (checkResult.isOk()) {
|
||||||
@ -529,17 +656,27 @@ class UnionSchema<S extends Schema<any>[]>
|
|||||||
}
|
}
|
||||||
|
|
||||||
errors.push(
|
errors.push(
|
||||||
`${schema.constructor.name} - ${checkResult.error.msg}`,
|
`${schema.constructor.name} - ${
|
||||||
|
checkResult.error.trace.join("\n")
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const type = typeof input;
|
||||||
return err(
|
return err(
|
||||||
pe(
|
pe(
|
||||||
input,
|
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"),
|
errors.join("\n"),
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
|
"Failed to match union",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -565,7 +702,7 @@ class ArraySchema<S extends Schema<any>>
|
|||||||
return err(
|
return err(
|
||||||
pe(
|
pe(
|
||||||
input,
|
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;
|
private schema;
|
||||||
|
|
||||||
constructor(private readonly valueSchema: Schema<T>) {
|
constructor(private readonly valueSchema: S) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.schema = new UnionSchema(
|
this.schema = new UnionSchema(
|
||||||
@ -706,6 +844,12 @@ class Validator {
|
|||||||
|
|
||||||
const v = new 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>[];
|
||||||
|
|||||||
@ -247,7 +247,7 @@ class UnionSchema<U extends Schema[]> {
|
|||||||
return err(
|
return err(
|
||||||
pe(
|
pe(
|
||||||
input,
|
input,
|
||||||
`Failed to parse as one of the types: ${
|
`Union. Failed to parse a any of the schemas: ${
|
||||||
this.schemas.map((s) => s.constructor.name).join(", ")
|
this.schemas.map((s) => s.constructor.name).join(", ")
|
||||||
}`,
|
}`,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -146,7 +146,7 @@ interface IOption<T> {
|
|||||||
* @template `T` The type of the value inside the `Some<T>`.
|
* @template `T` The type of the value inside the `Some<T>`.
|
||||||
*/
|
*/
|
||||||
export class Some<T> implements IOption<T> {
|
export class Some<T> implements IOption<T> {
|
||||||
public readonly tag = "Some";
|
public readonly tag = "some";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new `Some<T>` instance.
|
* Creates a new `Some<T>` instance.
|
||||||
@ -240,7 +240,7 @@ export class Some<T> implements IOption<T> {
|
|||||||
* @template `T` The type that would be inside the Option, but it is not used because this is a None.
|
* @template `T` The type that would be inside the Option, but it is not used because this is a None.
|
||||||
*/
|
*/
|
||||||
export class None<T> implements IOption<T> {
|
export class None<T> implements IOption<T> {
|
||||||
public readonly tag = "None";
|
public readonly tag = "none";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new `None` instance.
|
* Creates a new `None` instance.
|
||||||
|
|||||||
@ -39,7 +39,7 @@ interface IResult<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Ok<T, E> implements IResult<T, E> {
|
export class Ok<T, E> implements IResult<T, E> {
|
||||||
public readonly tag = "Ok";
|
public readonly tag = "ok";
|
||||||
|
|
||||||
constructor(public readonly value: T) {
|
constructor(public readonly value: T) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@ -182,7 +182,7 @@ export class Ok<T, E> implements IResult<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Err<T, E> implements IResult<T, E> {
|
export class Err<T, E> implements IResult<T, E> {
|
||||||
public readonly tag = "Err";
|
public readonly tag = "err";
|
||||||
|
|
||||||
constructor(public readonly error: E) {
|
constructor(public readonly error: E) {
|
||||||
this.error = error;
|
this.error = error;
|
||||||
|
|||||||
Reference in New Issue
Block a user