Antecedentes
Habiéndose reunido en numerosos lugares en el desarrollo de Javascript con situaciones en las que era necesario validar los valores, quedó claro que era necesario resolver este problema de alguna manera. Para este propósito, se estableció la siguiente tarea:
Desarrolle una biblioteca que permita:
- validar tipos de datos ;
- establecer valores predeterminados en lugar de campos o elementos no válidos;
- eliminar partes inválidas de un objeto o matriz;
- Recibe un mensaje de error
La base de la cual será:
- Fácil de aprender
- Legibilidad del código recibido.
- Facilidad de modificación de código
Para lograr estos objetivos, se ha desarrollado una biblioteca de validación de cuarteto .
Ladrillos básicos de validación
En el corazón de la mayoría de los sistemas que están diseñados para ser aplicables en una amplia gama de tareas están los elementos más simples: acciones, datos y algoritmos. Además de los métodos de su composición , para ensamblar algo más complicado a partir de elementos simples para resolver problemas más complejos.
Validador
La biblioteca de cuarteto se basa en el concepto de un validador . Los validadores en esta biblioteca son funciones de la siguiente forma
function validator( value: any, { key: string|int, parent: any }, { key: string|int, parent: any }, ... ): boolean
Hay varias cosas en esta definición que deberían describirse con más detalle:
function(...): boolean
- dice que el validador - calcula el resultado de la validación, y el resultado de la validación es un valor booleano - verdadero o falso , respectivamente válido o no válido
value: any
: indica que el validador: calcula el resultado de validar un valor , que puede ser cualquier valor de JavaScript. El validador asigna el valor validado a válido o inválido.
{ key: string|int, parent: any }, ...
- indica que el valor validado puede estar en diferentes contextos según el nivel de anidamiento del valor. Vamos a mostrarlo con ejemplos.
Valor de ejemplo sin contexto
const value = 4;
Valor de ejemplo en un contexto de matriz
Valor de ejemplo en el contexto de un objeto
const obj = { a: 1, b: 2, c: value, d: 8 }
Dado que las estructuras en un objeto pueden tener un mayor anidamiento, tiene sentido hablar sobre una variedad de contextos
const arrOfObj = [{ a: 1, b: 2, c: value, d: 8 },
Y así sucesivamente.
Sobre la similitud con los métodos de matrizEsta definición de un validador debería recordarle la definición de funciones que se pasan como argumento a los métodos de matriz, tales como: mapa, filtro, algunos, todos , etc.
- El primer argumento para estas funciones es un elemento de matriz.
- El segundo argumento es el índice del elemento.
- El tercer argumento es la matriz misma.
El validador en este caso es una función más generalizada: toma no solo el índice del elemento en la matriz y la matriz, sino también el índice de la matriz, en su elemento primario y su elemento primario, y así sucesivamente.
¿Qué debemos construir una casa?
Los ladrillos descritos anteriormente no se destacan entre otras "soluciones de piedra" que se encuentran en la "playa" de la muleta de JavaScript. Por lo tanto, construyamos a partir de ellos, algo más coherente e interesante. Para esto tenemos una composición .
¿Cómo construir un rascacielos de validación de objetos?
De acuerdo, sería conveniente validar los objetos de tal manera que la descripción de la validación coincida con la descripción del objeto. Para esto utilizaremos la composición de objetos de validadores . Se ve así:
Como puede ver, a partir de diferentes ladrillos de validación definidos para campos específicos, podemos ensamblar un validador de objetos, un "pequeño edificio" en el que todavía es bastante estrecho, pero mejor que sin él. Para esto, utilizamos el compositor de validadores v
. Cada vez, al encontrar el objeto literal v
en el lugar del validador, lo considerará como una composición de objeto, convirtiéndolo en un validador de objetos de acuerdo con sus campos.
A veces no podemos describir todos los campos . Por ejemplo, cuando un objeto es un diccionario de datos:
const quartet = require('quartet') const v = quartet() const isStringValidator = name => typeof name === 'string' const keyValueValidator = (value, { key }) => value.length === 1 && key.length === 1 const dictionarySchema= { dictionaryName: isStringValidator, ...v.rest(keyValueValidator) } const compositeObjValidator = v(dictionarySchema) const obj = { dictionaryName: 'next letter', b: 'c', c: 'd' } const isObjValid = compositeObjValidator(obj) console.log(isObjValid)
¿Cómo reutilizar las soluciones de construcción?
Como vimos anteriormente, existe la necesidad de reutilizar validadores simples. En estos ejemplos, ya teníamos que usar el "validador de tipo de cadena" dos veces.
Para acortar el registro y aumentar su legibilidad, la biblioteca de cuarteto utiliza sinónimos de validadores de cadena. Cada vez que un validador compositor encuentra una cadena en el lugar donde debería estar el validador, busca en el diccionario su validador y lo utiliza .
Por defecto, los validadores más comunes ya están definidos en la biblioteca.
Considere los siguientes ejemplos:
v('number')(1)
y muchos otros descritos en la documentación .
Cada arco tiene su propio tipo de ladrillos?
El validador compositor (función v
) también es una fábrica de validadores. En el sentido de que contiene muchos métodos útiles que devuelven
- validadores de funciones
- valores que el compositor percibirá como esquemas para crear validadores
Por ejemplo, veamos la validación de la matriz: la mayoría de las veces consiste en verificar el tipo de matriz y verificar todos sus elementos. Usaremos el v.arrayOf(elementValidator)
para esto. Por ejemplo, tome una matriz de puntos con nombres.
const a = [ {x: 1, y: 1, name: 'A'}, {x: 2, y: 1, name: 'B'}, {x: -1, y: 2, name: 'C'}, {x: 1, y: 3, name: 'D'}, ]
Dado que una matriz de puntos es una matriz de objetos, tiene sentido usar la composición de objetos para validar los elementos de la matriz.
const namedPointSchema = { x: 'number',
Ahora, utilizando el método de fábrica v.arrayOf
, cree un validador para toda la matriz.
const isArrayValid = v.arrayOf({ x: 'number', y: 'number', name: 'string' })
Veamos cómo funciona este validador:
isArrayValid(0)
Este es solo uno de los métodos de fábrica, cada uno de los cuales se describe en la documentación.
Como viste anteriormente, v.rest
también v.rest
un método de fábrica que devuelve una composición de objeto que verifica todos los campos no especificados en la composición del objeto. Eso significa que se puede incrustar en otra composición de objeto utilizando el spread-operator
.
Citemos como ejemplo el uso de varios de ellos:
¿Ser o no ser?
A menudo sucede que los datos válidos toman varias formas, por ejemplo:
id
puede ser un número o puede ser una cadena.- El objeto de
point
puede o no contener algunas coordenadas, dependiendo de la dimensión. - Y muchos otros casos.
Para organizar la validación de variantes, se proporciona un tipo separado de composición: composición de variantes. Está representado por una serie de validadores de posibles opciones. Un objeto se considera válido cuando al menos uno de los validadores informa su validez.
Considere un ejemplo con validación de identificador:
const isValidId = v([ v.and('not-empty', 'string'),
Ejemplo de validación de punto:
const isPointValid = v([ {
Por lo tanto, cada vez que un compositor ve una matriz, la considerará una composición de los elementos validadores de esta matriz de tal manera que cuando uno de ellos considere que el valor es válido, el cálculo de la validación se detendrá y el valor será reconocido como válido.
Como vemos, el compositor considera no solo la función de validación como un validador, sino también todo lo que puede conducir a una función de validación.
Tipo de validador | Ejemplo | Según lo percibido por el compositor |
---|
función de validación | x => typeof x === 'bigint' | acabo de invocar los valores necesarios |
composición de objetos | { a: 'number' } | crea una función de validación para un objeto basada en los validadores de campo especificados |
Composición variante | ['number', 'string'] | Crea una función de validación para validar un valor con al menos una de las opciones |
Resultados de llamadas al método de fábrica | v.enum('male', 'female') | La mayoría de los métodos de fábrica devuelven funciones de validación (con la excepción de v.rest , que devuelve la composición del objeto), por lo que se tratan como funciones de validación regulares |
Todas estas opciones del validador son válidas y se pueden usar en cualquier lugar dentro del esquema donde debería estar el validador.
Como resultado, el esquema de operación siempre es así: v(schema)
devuelve una función de validación. A continuación, esta función de validación se llama a valores específicos:
v(schema)(value[, ...parents])
¿Has tenido algún accidente en el sitio de construcción?
- Todavía no, ni uno
- ¡Lo harán!
Ocurre que los datos no son válidos y necesitamos poder determinar la causa del no válido.
Para esto, la biblioteca de cuarteto proporciona un mecanismo de explicación . Consiste en el hecho de que, en el caso de que el validador, ya sea interno o externo, detecte la validez de los datos verificados, debe enviar una nota explicativa .
Para estos fines, v
el segundo argumento del compositor de validadores v
. Agrega el efecto secundario de enviar una nota explicativa a la matriz v.explanation
en caso de datos no válidos.
Ejemplo, validemos una matriz y queramos averiguar los números de todos los elementos que no son válidos y su valor:
Como puede ver, la elección de la explicación depende de la tarea. A veces ni siquiera es necesario.
A veces necesitamos hacer algo con campos no válidos. En tales casos, tiene sentido usar el nombre del campo no válido como explicación :
const objSchema = { a: v('number', 'a'), b: v('number', 'b'), c: v('string', 'c') } const isObjValid = v(objSchema) let invalidObj = { a: 1, b: '1', c: 3 } isObjValid(invalidObj)
Con este mecanismo de explicación, puede implementar cualquier comportamiento asociado con los resultados de la validación.
Una explicación puede ser cualquier cosa:
- un objeto que contiene la información necesaria;
- función que corrige el error. (
getExplanation => function(invalid): valid
); - nombre del campo no válido o índice del elemento no válido;
- código de error
- y todo eso es suficiente para tu imaginación.
¿Qué hacer cuando las cosas no se están construyendo?
Corregir errores de validación no es una tarea rara. Para estos fines, la biblioteca utiliza validadores con un efecto secundario que recuerda el lugar del error y cómo solucionarlo.
v.default(validator, value)
: devuelve un validador que recuerda un valor no válido y, en el momento de llamar a v.fix
, establece el valor predeterminadov.filter(validator)
: devuelve un validador que recuerda un valor no válido y, en el momento de llamar a v.fix
, elimina este valor del padrev.addFix(validator, fixFunc)
: devuelve un validador que recuerda un valor no válido y, en el momento de llamar a v.fix
, llama a fixFunc con parámetros (valor, {clave, padre}, ...). fixFunc
- debe mutar a uno de los socios - para cambiar el valor
const toPositive = (negativeValue, { key, parent }) => { parent[key] = -negativeValue } const objSchema = { a: v.default('number', 1), b: v.filter('string', ''), c: v.default('array', []), d: v.default('number', invalidValue => Number(invalidValue)),
Las tareas aún son útiles.
También hay métodos de utilidad para acciones de validación en esta biblioteca:
Método | Resultado |
---|
v.throwError | Si no es válido, arroja un TypeError con el mensaje dado. |
v.omitInvalidItems | Devuelve una nueva matriz (u objeto de diccionario) sin elementos no válidos (campos). |
v.omitInvalidProps | Devuelve un nuevo objeto sin campos no válidos, de acuerdo con el validador de objeto especificado. |
v.validOr | Devuelve el valor si es válido; de lo contrario, lo reemplaza con el valor predeterminado especificado. |
v.example | Comprueba si los valores dados se ajustan al esquema. Si no encajan, se produce un error. Sirve como documentación y pruebas de circuito. |
Resultados
Las tareas se resolvieron de las siguientes maneras:
Desafío | Solución |
---|
Validación de tipo de datos | Validadores con nombre predeterminados. |
Valores por defecto | v.default |
Eliminar partes inválidas | v.filter , v.omitInvalidItems y v.omitInvalidProps . |
Fácil de aprender | Validadores simples, formas simples de componerlos en validadores complejos. |
Legibilidad de código | Uno de los objetivos de la biblioteca era comparar los esquemas de validación. |
objetos validados |
Facilidad de modificación | Habiendo dominado los elementos de las composiciones y usando sus propias funciones de validación, cambiar el código es bastante simple. |
Mensaje de error | Explicación en forma de mensaje de error. O cálculo de código de error basado en explicaciones. |
Epílogo
Esta solución fue diseñada para crear rápida y convenientemente funciones de validación con la capacidad de incrustar funciones de validación personalizadas. Por lo tanto, si hay alguna, cualquier corrección, crítica, opciones de mejora de quienes leen este artículo son bienvenidas. Gracias por su atencion