schema.ts en TypeScript
Un validador de esquemas mínimo donde el tipo parseado se infiere del esquema.
type Validator<T> = {
readonly _type?: T;
parse(input: unknown, path?: string): T;
};
type Infer<V> = V extends Validator<infer T> ? T : never;
class ValidationError extends Error {
constructor(readonly path: string, message: string) {
super(`${path || "value"}: ${message}`);
this.name = "ValidationError";
}
}
const string = (): Validator<string> => ({
parse(input, path = "") {
if (typeof input !== "string") {
throw new ValidationError(path, "expected a string");
}
return input;
},
});
const number = (): Validator<number> => ({
parse(input, path = "") {
if (typeof input !== "number" || Number.isNaN(input)) {
throw new ValidationError(path, "expected a number");
}
return input;
},
});
const optional = <T>(inner: Validator<T>): Validator<T | undefined> => ({
parse(input, path = "") {
return input === undefined ? undefined : inner.parse(input, path);
},
});
const array = <T>(inner: Validator<T>): Validator<T[]> => ({
parse(input, path = "") {
if (!Array.isArray(input)) {
throw new ValidationError(path, "expected an array");
}
return input.map((item, i) => inner.parse(item, `${path}[${i}]`));
},
});
const object = <Shape extends Record<string, Validator<unknown>>>(
shape: Shape,
): Validator<{ [K in keyof Shape]: Infer<Shape[K]> }> => ({
parse(input, path = "") {
if (typeof input !== "object" || input === null) {
throw new ValidationError(path, "expected an object");
}
const source = input as Record<string, unknown>;
const out = {} as { [K in keyof Shape]: Infer<Shape[K]> };
for (const key of Object.keys(shape) as (keyof Shape & string)[]) {
out[key] = shape[key]!.parse(
source[key],
path ? `${path}.${key}` : key,
) as Infer<Shape[typeof key]>;
}
return out;
},
});
const runSchema = object({
lang: string(),
tpm: number(),
stars: optional(number()),
tags: array(string()),
});
type Run = Infer<typeof runSchema>;
const parsed: Run = runSchema.parse({
lang: "ts",
tpm: 98,
tags: ["types", "generics"],
});
console.log(parsed.lang.toUpperCase(), parsed.tags.length, parsed.stars);
try {
runSchema.parse({ lang: "ts", tpm: "fast", tags: [] });
} catch (error) {
console.log(error instanceof ValidationError, (error as Error).message);
}
Cómo funciona
- Cada validador carga en un campo fantasma el tipo que produce.
Infer<typeof schema>lee ese tipo de vuelta.- Una sola definición sirve al chequeo en ejecución y al tipo estático.
Palabras clave y builtins usados aquí
ArrayNumberObjectRunShapeTValidatorarrayascatchclassconstconstructorextendsforifisNaNnevernumberofoptionalreadonlyreturnstringsuperthisthrowtrytypeunknown
El intento, en números
- Líneas
- 87
- Caracteres a escribir
- 2169
- Tokens
- 658
- Ritmo de tres estrellas
- 110 tpm
Al ritmo de tres estrellas de 110 tokens por minuto, este intento toma unos 359 segundos.
Paso 1 de 1 en Bis; paso 19 de 19 en Clases y patrones.