
En mi
publicación anterior, describí los puntos principales al desarrollar otra
biblioteca de código abierto . Olvidé mencionar una cosa más: si no le cuenta a nadie sobre la biblioteca, sea lo que sea, lo más probable es que nadie lo sepa.
Entonces, conozca
trava.js : validación jugosa para el beneficio del proyecto. Por cierto, hemos estado usando hierba durante más de seis meses, y pensé que era hora de informarle sobre los beneficios de usarla. Ya está seco, así que aguanta la respiración. Y adelante.
Concepto
A primera vista, parece que la validación es un tema trivial que no requiere atención especial. El valor es verdadero o no, lo que podría ser más simple:
function validate (value) {
Pero generalmente sería bueno saber qué salió mal exactamente:
function validate (value) { if (!check1(value)) return 'ERROR_1'; if (!check2(value)) return 'ERROR_2'; }
En realidad eso es todo, el problema está resuelto.
Si no fuera por un "pero".
Por la experiencia de desarrollar aplicaciones reales, se notó que el asunto no termina con la validación. Por lo general, estos datos también deben convertirse a un formato específico, por alguna razón que el serializador no admite, por ejemplo, fechas, conjuntos u otros tipos de datos personalizados. Dado que esto es principalmente JSON, en la práctica resulta que tienes que hacer un doble paso a través de la estructura de datos de entrada durante la validación y la transformación. La idea surgió, ¿por qué no combinar estas dos etapas en una sola? Una posible ventaja también sería la presencia de un esquema explícito de datos declarativos.
Para admitir la conversión de un valor a un formato específico, el validador debe poder devolver no solo un error, sino también un valor reducido. En el mundo js, varias opciones de interfaz son bastante comunes con posibles retornos de error.
- Probablemente el más común es el retorno de la tupla [error, datos]:
function validate (value) { if (!check1(value)) return ['ERROR_1']; if (!check2(value)) return ['ERROR_2']; return [null, value]; }
También hay una opción similar donde no se devuelve una matriz, sino el objeto {error, data} , pero no hay diferencias fundamentales. La ventaja de este enfoque es la obviedad, lo negativo es que ahora en todas partes necesita mantener este contrato. Para la validación, esto no causa inconvenientes, pero para las transformaciones esto es claramente superfluo.
- Use excepciones. Aunque en mi opinión un error de validación es una situación estándar en la aplicación, nada es excepcional. Honestamente, creo que las excepciones se usan mejor solo cuando algo realmente salió mal. Además, las excepciones se pueden invocar accidentalmente en los validadores, y es posible que no sepa en absoluto que fue un error en el código y no en el valor. La ventaja del enfoque es la simplificación de la interfaz: ahora siempre se devuelve el valor de la manera habitual y el error se arroja como una excepción.
- Hay una opción para poner un error en una variable global. Pero no sacaría al estado innecesariamente.
- Use un tipo separado para los errores. Parece la opción con excepciones, si les quitas el tipo de error, pero no lo descartes.
function validate (value) { if (!check1(value)) return new Trava.ValidationError({ code: 401 }); if (!check2(value)) return new Trava.ValidationError({ code: 405 }); return parseOrTransform(value);
Me decidí por la última opción, aunque esto también es un compromiso, pero en general no está mal.
Trava.ValidationError se propone como un tipo para el error, que hereda del
error estándar y agrega la capacidad de usar un tipo de datos arbitrario para informar un error. No es necesario usar
Trava.ValidationError , puede usar el
error estándar, pero no olvide que el mensaje de error es solo cadenas.
Para resumir, podemos decir que el validador es una función limpia y sincrónica que, además del valor, puede devolver un error. Extremadamente simple Y esta teoría funciona bien sin bibliotecas. En la práctica, los validadores se combinan en cadenas y jerarquías, y aquí la hierba definitivamente será útil.
Composición
Quizás la composición es el caso más común de trabajar con validadores. La implementación de la composición puede ser diferente. Por ejemplo, en las famosas
bibliotecas joi y
v8n, esto se hace a través de un objeto y una cadena de métodos:
Joi.string().alphanum().min(0).max(255)
Aunque se ve hermoso a primera vista, este enfoque tiene varios inconvenientes, y uno es fatal. Y aquí está la cosa. En mi experiencia, un validador siempre es una cosa para una aplicación específica, por lo que el enfoque principal en la biblioteca debe estar en la conveniencia de expandir los validadores y la integración con el enfoque existente, y no en el número de primitivas básicas, que, en mi opinión, solo agregan peso a la biblioteca, pero la mayoría no será utilizada. Tomemos, por ejemplo, el mismo validador para la cadena. Luego resulta que necesita recortar los espacios desde los extremos, luego, de repente, debe permitir el uso de caracteres especiales en un solo caso, y en algún lugar debe llevar a minúsculas, etc. De hecho, puede haber infinitas primitivas de este tipo, y no veo el punto de siquiera comenzar a agregarlas a la biblioteca. En mi opinión, el uso de objetos también es redundante y conduce a un aumento de la complejidad durante la expansión, aunque a primera vista parece facilitar la vida. Por ejemplo, c
joi no
es tan fácil
de escribir su validador .
Un enfoque funcional y una hierba aquí pueden ayudar. El mismo ejemplo de validación de un número especificado en el rango de 0 a 255:
La declaración de
verificación hace un validador de la verificación de la verdad (valor => verdadero / falso). Y
Compose encadena los validadores. Cuando se ejecuta, la cadena se interrumpe después del primer error. Lo importante es que las funciones ordinarias se usan en todas partes, que son muy simples de expandir y usar. Es esta facilidad de expansión, en mi opinión, que es una característica clave de una biblioteca de validación válida.
Tradicionalmente, un lugar separado en la validación se ocupa comprobando si hay
valores nulos e
indefinidos . Hay operadores auxiliares en la hierba para esto:
Hay varios operadores auxiliares más en el césped, y todos componen maravillosamente y sorprendentemente simplemente se expanden. Como funciones ordinarias :)
Jerarquía
Los tipos de datos simples se organizan en una jerarquía. Los casos más comunes son objetos y matrices. Hay operadores en la hierba que hacen que sea más fácil trabajar con ellos:
Al validar los objetos, se decidió enfatizar la gravedad de la definición: todas las claves son obligatorias de forma predeterminada (incluidas en
Obligatorio ). Las claves no especificadas en el validador se descartan.
Algunas soluciones
jsonschema ,
cuarteto prefieren describir validadores en forma de datos, por ejemplo {x: 'número', y: 'número'}, pero esto conlleva las mismas dificultades al expandirse. Una ventaja significativa de este enfoque es la posibilidad de serialización e intercambio de circuitos, lo cual es imposible con las funciones. Sin embargo, esto se puede implementar fácilmente sobre la interfaz funcional. ¡No es necesario ocultar funciones detrás de las líneas! Las funciones ya tienen nombres y eso es todo lo que se necesita.
Para facilitar el uso dentro de los validadores, se pueden omitir los operadores
Compose y
Keys ; también es conveniente envolver el validador raíz en
Trava :
const pointValidator = Trava({
Si llama a
Trava con el segundo argumento, el valor de retorno será el resultado de aplicar el validador:
const point = Trava({ x: [numberValidator, Trava.Check(v => v > 180)], y: [numberValidator, Trava.Check(v => v < 180)], },
Hasta ahora, el soporte se ha implementado solo para matrices y objetos, como básicamente envenena a JSON y eso es suficiente. ¡Solicitudes de extracción para Wellcome!
Contexto
Al usar el validador como último parámetro, puede pasar el contexto al que se podrá acceder desde todos los validadores llamados como el último parámetro. Personalmente, esta oportunidad aún no me ha resultado útil, pero es posible.
Para algunos validadores que pueden devolver un error, es posible definir un mensaje de error en diferentes niveles. Un ejemplo:
const pos = Trava.Check(v => v > 0); pos(-1);
Anular para un solo caso:
const pos = Trava.Check(v => v > 0, " "); pos(-1);
Anulación para todos los casos:
Trava.Check.ErrorMessage = " "; pos(-1);
Además, para una configuración más detallada, puede transferir una función en el lugar del error que debería devolver un error y se llamará con los parámetros del validador.
Caso de uso
Principalmente envenenamos a JSON en el backend junto con koa. La interfaz también se sienta lentamente. Es conveniente tener validadores comunes en ambos extremos. Y ahora mostraré un caso de uso casi real. Suponga que desea implementar una API para crear y actualizar datos de pacientes.
common / errors.jsconst trava = require ('trava');
function ValidationError (ctx, params) {
if (params instanceof Error) {
params = trava.ValidationError.extractData (params);
}
ctx.body = {
código: 'VALIDATION_ERROR',
params
};
ctx.status = HttpStatus.BAD_REQUEST;
}
Aunque el ejemplo es muy simple, no se puede llamar simplificado. En una aplicación real, solo los validadores serán complicados. También puede realizar la validación en el middleware: el validador se aplica completamente al contexto o al cuerpo de la solicitud.
En el proceso de trabajar y usar la validación, llegamos a la conclusión de que los validadores síncronos simples y los mensajes de error simples son suficientes. De hecho, llegamos a la conclusión de que usamos solo dos mensajes: "REQUERIDO" e "NO VÁLIDO", que se localizan en la interfaz junto con las indicaciones de los campos. Otras verificaciones que requieren acciones adicionales (por ejemplo, en el registro para verificar que dicho correo ya existe) están fuera del alcance de la validación. En cualquier caso, la hierba no se trata de este caso.
En conclusión
En este breve artículo, describí casi toda la funcionalidad de la biblioteca, fuera del alcance del artículo hay varios ayudantes que simplifican la vida. Solicito detalles en github
github.com/uNmAnNeR/travajs .
Necesitábamos una herramienta que se pueda personalizar tanto como sea posible, en la que no haya nada superfluo, pero al mismo tiempo hay todo lo necesario para el trabajo diario. Y creo que en general esto se logró, espero que alguien también haga la vida más fácil. Estaré encantado de deseos y sugerencias.
A la salud.