validator is readygit add .

This commit is contained in:
2025-02-03 23:49:42 +03:00
parent e555186537
commit 97a5cdf654
4 changed files with 202 additions and 1029 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,23 @@
import { Result } from "@shared/utils/result.ts"; import { Result } from "@shared/utils/result.ts";
import { Schema } from "@shared/utils/validator.ts";
class ClientApi<Req, Res> { class ApiRoute<
constructor(path: string, method: string) {} Path extends string,
validate(res: Response): ResultAsync<Res> { ReqSchema extends Schema<any>,
const body = await res.json(); ResSchema extends Schema<any>,
> {
constructor(
public readonly path: Path,
public readonly reqSchema: ReqSchema,
public readonly resSchema: ResSchema,
) {
} }
} }
class ServerApi<Req, Res> { export type ExtractRouteParams<T extends string> = T extends string
} ? T extends `${infer _Start}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${infer _Start}:${infer Param}` ? Param
: T extends `${infer _Start}*` ? "restOfThePath"
: never
: never;

View File

@ -124,7 +124,7 @@ export class Ok<T, E> implements IResult<T, E> {
} }
mapErr<U>(fn: (err: E) => U): Result<T, U> { mapErr<U>(fn: (err: E) => U): Result<T, U> {
return new Ok<T, U>(this.value); return ok<T, U>(this.value);
} }
mapErrAsync<U>(fn: (err: E) => Promise<U>): ResultAsync<T, U> { mapErrAsync<U>(fn: (err: E) => Promise<U>): ResultAsync<T, U> {
@ -236,8 +236,7 @@ export class Err<T, E> implements IResult<T, E> {
return errAsync(this.error); return errAsync(this.error);
} }
mapErr<U>(fn: (err: E) => U): Result<T, U> { mapErr<U>(fn: (err: E) => U): Result<T, U> {
const mappedError = fn(this.error); return new Err<T, U>(fn(this.error));
return new Err<T, U>(mappedError);
} }
mapErrAsync<U>(fn: (err: E) => Promise<U>): ResultAsync<T, U> { mapErrAsync<U>(fn: (err: E) => Promise<U>): ResultAsync<T, U> {
return ResultAsync.fromPromise( return ResultAsync.fromPromise(

View File

@ -1,6 +1,6 @@
import { err, Result } from "@shared/utils/result.ts"; import { err, Result } from "@shared/utils/result.ts";
import { ok } from "@shared/utils/index.ts"; import { ok } from "@shared/utils/index.ts";
import { None, none, Option, some } from "@shared/utils/option.ts";
// ── Error Types ───────────────────────────────────────────────────── // ── Error Types ─────────────────────────────────────────────────────
type ValidationErrorDetail = type ValidationErrorDetail =
| { | {
@ -973,6 +973,180 @@ class NullishSchema<S extends Schema<any>>
} }
} }
class ResultSchema<T extends Schema<any>, E extends Schema<any>>
extends BaseSchema<Result<InferSchemaType<T>, InferSchemaType<E>>> {
constructor(
private readonly okSchema: T,
private readonly errSchema: E,
) {
super();
}
protected override validateInput(
input: unknown,
): Result<
Result<InferSchemaType<T>, InferSchemaType<E>>,
SchemaValidationError
> {
return BaseSchema.validatePrimitive<object>(input, "object").andThen(
(
obj,
): Result<
Result<InferSchemaType<T>, InferSchemaType<E>>,
SchemaValidationError
> => {
if ("tag" in obj) {
switch (obj.tag) {
case "ok": {
if ("value" in obj) {
return this.okSchema.parse(
obj.value,
).match(
(v) => ok(ok(v as InferSchemaType<T>)),
(e) =>
err(createValidationError(input, {
kind: "propertyValidation",
property: "value",
detail: e.detail,
})),
);
} else if (
BaseSchema.isNullishSchema(this.okSchema)
) {
return ok(
ok() as Result<
InferSchemaType<T>,
InferSchemaType<E>
>,
);
}
return err(createValidationError(input, {
kind: "missingProperties",
keys: ["value"],
msg: "If tag is set to 'ok', than result must contain a 'value' property",
}));
}
case "err": {
if (
"error" in obj
) {
return this.errSchema.parse(
obj.error,
).match(
(e) => ok(err(e as InferSchemaType<E>)),
(e) =>
err(createValidationError(input, {
kind: "propertyValidation",
property: "error",
detail: e.detail,
})),
);
} else if (
BaseSchema.isNullishSchema(this.errSchema)
) {
return ok(
err() as Result<
InferSchemaType<T>,
InferSchemaType<E>
>,
);
}
return err(createValidationError(input, {
kind: "missingProperties",
keys: ["error"],
msg: "If tag is set to 'err', than result must contain a 'error' property",
}));
}
default:
return err(createValidationError(input, {
kind: "propertyValidation",
property: "tag",
detail: {
kind: "typeMismatch",
expected: "'ok' or 'err'",
received: `'${obj.tag}'`,
},
}));
}
} else {
return err(createValidationError(input, {
kind: "missingProperties",
keys: ["tag"],
msg: "Result must contain a tag property",
}));
}
},
);
}
}
class OptionSchema<T extends Schema<any>>
extends BaseSchema<Option<InferSchemaType<T>>> {
constructor(
private readonly schema: T,
) {
super();
}
protected override validateInput(
input: unknown,
): Result<Option<InferSchemaType<T>>, SchemaValidationError> {
return BaseSchema.validatePrimitive<object>(input, "object").andThen(
(
obj,
): Result<Option<InferSchemaType<T>>, SchemaValidationError> => {
if ("tag" in obj) {
switch (obj.tag) {
case "some": {
if ("value" in obj) {
return this.schema.parse(
obj.value,
).match(
(v) => ok(some(v as InferSchemaType<T>)),
(e) =>
err(createValidationError(input, {
kind: "propertyValidation",
property: "value",
detail: e.detail,
})),
);
} else if (
BaseSchema.isNullishSchema(this.schema)
) {
return ok(some() as Option<InferSchemaType<T>>);
}
return err(createValidationError(input, {
kind: "missingProperties",
keys: ["value"],
msg: "If tag is set to 'some', than option must contain a 'value' property",
}));
}
case "none": {
return ok(none);
}
default:
return err(createValidationError(input, {
kind: "propertyValidation",
property: "tag",
detail: {
kind: "typeMismatch",
expected: "'some' or 'none'",
received: `'${obj.tag}'`,
},
}));
}
} else {
return err(createValidationError(input, {
kind: "missingProperties",
keys: ["tag"],
msg: "Option must contain a tag property",
}));
}
},
);
}
}
/* ── Helper Object for Schema Creation (z) ───────────────────────────────────── */ /* ── Helper Object for Schema Creation (z) ───────────────────────────────────── */
export const z = { export const z = {
@ -1023,13 +1197,11 @@ export const z = {
schema: S, schema: S,
msg?: string, msg?: string,
) => new NullishSchema<S>(schema, msg), ) => new NullishSchema<S>(schema, msg),
result: <T extends Schema<any>, E extends Schema<any>>(
okSchema: T,
errSchema: E,
) => new ResultSchema<T, E>(okSchema, errSchema),
option: <T extends Schema<any>>(
schema: T,
) => new OptionSchema<T>(schema),
}; };
const schema = z.obj({
string: z.string(),
number: z.number(),
});
type schemaType = typeof schema;
type test = InferSchemaType<typeof schema>;