Concepto clave
En arquitecturas complejas, la validación de datos es una capa crítica que garantiza la integridad del sistema. Una librería de validación tipada en TypeScript va más allá de simples comprobaciones: utiliza tipos genéricos para inferir la estructura de los datos validados, creando un flujo de tipos seguro desde la entrada hasta el uso interno. Imagina un control de aduanas digital: no solo revisa pasaportes (validación), sino que también actualiza automáticamente los permisos del viajero (tipado) en el sistema.
El núcleo de este patrón es la composición de validadores. En lugar de funciones aisladas, construyes validadores reutilizables que se combinan como piezas de Lego, donde cada pieza aporta su lógica de validación y su contrato de tipos. Esto permite crear esquemas complejos para objetos anidados, arrays condicionales o uniones discriminadas, manteniendo el tipado preciso en cada paso.
"La magia no está en validar, sino en que TypeScript sepa exactamente qué forma tiene el dato después de pasar la validación."
Cómo funciona en la práctica
Vamos a construir la base de nuestra librería. Empezamos definiendo un tipo genérico para un validador:
type Validator = (input: unknown) => input is T;Este tipo dice: "dame algo desconocido y te digo si es de tipo T". Pero queremos más: queremos transformar. Creamos un validador que también refine el tipo:
function string(): Validator {
return (input: unknown): input is string => typeof input === 'string';
}Ahora, la clave: combinadores. Un combinador toma validadores y devuelve uno nuevo:
function object<T extends Record<string, Validator<any>>>(schema: T): Validator<{ [K in keyof T]: InferType<T[K]> }> {
return (input: unknown): input is any => {
if (typeof input !== 'object' || input === null) return false;
for (const key in schema) {
if (!schema[key](input[key])) return false;
}
return true;
};
}Nota cómo InferType (que definiremos) extrae el tipo del validador. Si usamos object({ name: string(), age: number() }), TypeScript inferirá que el resultado es { name: string, age: number }.
Caso de estudio
Implementemos un validador para un formulario de usuario en una aplicación empresarial. Necesitamos validar:
- Nombre: string, mínimo 2 caracteres
- Email: string con formato de email
- Edad: number opcional entre 18 y 100
- Roles: array de strings, al menos uno seleccionado
Primero, creamos validadores básicos con tipos:
const minLength = (length: number): Validator =>
(input): input is string => typeof input === 'string' && input.length >= length;
const email = (): Validator =>
(input): input is string => typeof input === 'string' && /^[^@]+@[^@]+\.[^@]+$/.test(input);
const optional = (validator: Validator): Validator =>
(input): input is T | undefined => input === undefined || validator(input);Ahora, componemos el esquema completo:
const userSchema = object({
name: compose(string(), minLength(2)),
email: compose(string(), email()),
age: optional(compose(number(), (n): n is number => n >= 18 && n <= 100)),
roles: array(string())
});Al validar const data = { name: "Ana", email: "[email protected]", roles: ["admin"] };, si pasa, TypeScript sabe que data es { name: string, email: string, age?: number, roles: string[] } con age opcional.
Errores comunes
- No inferir tipos correctamente en combinadores: Si tu función
objectdevuelveValidator<any>, pierdes todo el tipado. Usa tipos genéricos anidados yinferpara extraer tipos de validadores. - Validar en tiempo de ejecución pero no en compilación: Asegúrate de que los esquemas sean tipos, no solo valores. Por ejemplo,
schema: Record<string, Validator<any>>permite cualquier validador, peroT extends Record<string, Validator<any>>conserva las claves. - Olvidar casos límite: ¿Qué pasa con
null,undefinedo arrays vacíos? Define políticas claras (ej.,optionalpara opcionales) y documenta el comportamiento. - No proporcionar mensajes de error útiles: En nivel avanzado, agrega un sistema de errores tipado que capture qué validador falló y por qué, sin romper la inferencia de tipos.
- Abusar de
asen implementaciones: Evita afirmaciones de tipo (as) dentro de validadores; usa guardias de tipo (input is T) para mantener la seguridad.
Checklist de dominio
- Puedo crear un validador genérico que infiera el tipo de salida correctamente.
- Sé implementar combinadores como
object,array,unionque preserven el tipado. - He construido un esquema para un objeto anidado de 3 niveles con validaciones condicionales.
- Puedo explicar la diferencia entre
Validator<T>y una función que devuelveboolean. - He integrado mensajes de error tipados en mi librería sin perder inferencia.
- Sé cómo testear validadores tanto en tipo como en ejecución.
- Puedo optimizar la librería para evitar checks redundantes en objetos grandes.
Implementa una librería de validación tipada para esquemas de API REST
En este ejercicio, construirás una librería de validación que pueda manejar respuestas de API REST típicas, con tipos seguros. Sigue estos pasos:
- Define los tipos base: Crea un tipo
Validatorque sea una guardia de tipo. ImplementaInferTypeque extraiga el tipoUdeValidator. - Implementa validadores primitivos: Escribe funciones para
string(),number(),boolean(), yliteral(value)(ej.,literal('success')). Asegúrate de que devuelvanValidator. - Crea combinadores: Implementa
object(schema)para objetos,array(validator)para arrays, yoptional(validator)para campos opcionales. Usa genéricos para inferir los tipos resultantes. - Añade validaciones complejas: Agrega
union(validators)para uniones (ej.,string | number) yintersection(validators)para intersecciones. Prueba con un esquema que incluya ambos. - Construye un esquema real: Valida una respuesta de API como
{ status: 'ok', data: { users: Array<{ id: number, name: string }> }, error?: string }. Escribe tests en TypeScript que verifiquen el tipado y la validación.
Entrega el código en un repositorio o archivo, con comentarios que expliquen las decisiones de diseño.
Pistas- Usa tipos condicionales como `T extends Validator ? U : never` para InferType.
- En `object`, itera sobre las claves del esquema con un bucle `for...in` y aplica cada validador.
- Para `union`, prueba cada validador en orden hasta que uno tenga éxito; asegura que el tipo inferido sea la unión de los tipos.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.