Contexte
Ayant rencontré à de nombreux endroits dans le développement Javascript des situations où il était nécessaire de valider des valeurs, il est devenu clair qu'il était nécessaire de résoudre ce problème. À cet effet, la tâche suivante a été définie:
Développer une bibliothèque qui permettra:
- valider les types de données ;
- définir des valeurs par défaut au lieu de champs ou d'éléments non valides;
- supprimer des parties non valides d'un objet ou d'un tableau;
- Recevoir un message d'erreur
Dont la base sera:
- Facile à apprendre
- Lisibilité du code reçu.
- Facilité de modification du code
Pour atteindre ces objectifs, une bibliothèque de validation de quatuor a été développée.
Briques de validation de base
Au cœur de la plupart des systèmes conçus pour être applicables à un large éventail de tâches se trouvent les éléments les plus simples: actions, données et algorithmes. Ainsi que les méthodes de leur composition - afin d'assembler quelque chose à partir des éléments les plus simples les plus complexes pour résoudre des problèmes plus complexes.
Validateur
La bibliothèque du quatuor est basée sur le concept d'un validateur . Les validateurs de cette bibliothèque sont des fonctions du formulaire suivant
function validator( value: any, { key: string|int, parent: any }, { key: string|int, parent: any }, ... ): boolean
Il y a plusieurs choses dans cette définition qui devraient être décrites plus en détail:
function(...): boolean
- dit que le validateur - calcule le résultat de la validation, et le résultat de la validation est une valeur booléenne - vrai ou faux , respectivement valide ou non valide
value: any
- indique que le validateur - calcule le résultat de la validation d'une valeur , qui peut être n'importe quelle valeur javascript. Le validateur attribue la valeur validée à valide ou non valide.
{ key: string|int, parent: any }, ...
- indique que la valeur validée peut être dans des contextes différents selon le niveau d'imbrication de la valeur. Montrons-le avec des exemples
Exemple de valeur sans aucun contexte
const value = 4;
Exemple de valeur dans un contexte de tableau
Exemple de valeur dans le contexte d'un objet
const obj = { a: 1, b: 2, c: value, d: 8 }
Étant donné que les structures d'un objet peuvent avoir une imbrication plus importante, il est logique de parler de divers contextes
const arrOfObj = [{ a: 1, b: 2, c: value, d: 8 },
Et ainsi de suite.
À propos de la similitude avec les méthodes de tableauCette définition d'un validateur devrait vous rappeler la définition des fonctions qui sont passées en argument aux méthodes de tableau, telles que: carte, filtre, certains, tous , etc.
- Le premier argument de ces fonctions est un élément de tableau.
- Le deuxième argument est l' indice de l'élément.
- Le troisième argument est le tableau lui-même.
Le validateur dans ce cas est une fonction plus généralisée - il prend non seulement l'index de l'élément dans le tableau et le tableau, mais aussi l'index du tableau - dans son parent et son parent, et ainsi de suite.
Que devons-nous construire une maison?
Les briques décrites ci-dessus ne se distinguent pas des autres "solutions de pierre" qui reposent sur la "plage" de la béquille javascript. Par conséquent, construisons à partir d'eux quelque chose de plus cohérent et intéressant. Pour cela, nous avons une composition .
Comment construire un gratte-ciel de validation d'objet?
D'accord, il serait pratique de valider les objets de telle manière que la description de la validation corresponde à la description de l'objet. Pour cela, nous utiliserons la composition d'objet des validateurs . Cela ressemble à ceci:
Comme vous pouvez le voir, à partir de différentes briques de validateur définies pour des champs spécifiques, nous pouvons assembler un validateur d'objet - un "petit bâtiment", qui est encore assez encombré - mais mieux que sans lui. Pour cela, nous utilisons le compositeur de validateurs v
. A chaque fois, rencontrant le littéral objet v
à la place du validateur, il le considérera comme une composition objet, le transformant en valideur objet dans ses domaines.
Parfois, nous ne pouvons pas décrire tous les domaines . Par exemple, lorsqu'un objet est un dictionnaire de données:
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)
Comment réutiliser les solutions de construction?
Comme nous l'avons vu ci-dessus, il est nécessaire de réutiliser des validateurs simples. Dans ces exemples, nous avons déjà dû utiliser le "validateur de type de chaîne" deux fois déjà.
Afin de raccourcir l'enregistrement et d'augmenter sa lisibilité, la bibliothèque de quatuors utilise des synonymes de chaînes de validateurs. Chaque fois qu'un compositeur de validateur rencontre une chaîne à l'endroit où le validateur doit être, il recherche le validateur dans le dictionnaire et l'utilise .
Par défaut, les valideurs les plus courants sont déjà définis dans la bibliothèque.
Considérez les exemples suivants:
v('number')(1)
et bien d'autres décrits dans la documentation .
Chaque arc a son propre type de briques?
Le compositeur de validateur (fonction v
) est également une fabrique de validateurs. En ce sens qu'il contient de nombreuses méthodes utiles qui renvoient
- validateurs de fonction
- valeurs que le compositeur percevra comme des schémas de création de validateurs
Par exemple, regardons la validation du tableau: le plus souvent, elle consiste à vérifier le type du tableau et à vérifier tous ses éléments. Nous utiliserons pour cela la méthode v.arrayOf(elementValidator)
. Par exemple, prenez un tableau de points avec des noms.
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'}, ]
Puisqu'un tableau de points est un tableau d'objets, il est judicieux d'utiliser la composition d'objets pour valider les éléments du tableau.
const namedPointSchema = { x: 'number',
Maintenant, en utilisant la méthode d'usine v.arrayOf
, créez un validateur pour l'ensemble du tableau.
const isArrayValid = v.arrayOf({ x: 'number', y: 'number', name: 'string' })
Voyons comment fonctionne ce validateur:
isArrayValid(0)
Ceci n'est qu'une des méthodes d'usine, chacune étant décrite dans la documentation.
Comme vous l'avez vu ci-dessus, v.rest
également une méthode d'usine qui renvoie une composition d'objet qui vérifie tous les champs non spécifiés dans la composition d'objet. Cela signifie qu'il peut être intégré dans une autre composition d'objet à l'aide de l' spread-operator
.
Citons comme exemple l'utilisation de plusieurs d'entre elles:
Être ou ne pas être?
Il arrive souvent que des données valides prennent diverses formes, par exemple:
id
peut être un nombre ou une chaîne.- L'objet
point
peut contenir ou non certaines coordonnées, selon la dimension. - Et bien d'autres cas.
Pour organiser la validation des variantes, un type de composition distinct est fourni - la composition des variantes. Il est représenté par un tableau de validateurs d'options possibles. Un objet est considéré comme valide quand au moins l'un des validateurs signale sa validité.
Prenons un exemple de validation d'identifiant:
const isValidId = v([ v.and('not-empty', 'string'),
Exemple de validation de point:
const isPointValid = v([ {
Ainsi, chaque fois qu'un compositeur voit un tableau, il le considérera comme une composition des éléments de validation de ce tableau de telle manière que lorsque l'un d'entre eux considère la valeur comme valide, le calcul de validation s'arrêtera et la valeur sera reconnue comme valide.
Comme on le voit, le compositeur considère non seulement la fonction validateur comme un validateur, mais aussi tout ce qui peut conduire à une fonction validateur.
Type de validateur | Exemple | Tel que perçu par le compositeur |
---|
fonction de validation | x => typeof x === 'bigint' | vient d'appeler sur les valeurs nécessaires |
composition d'objet | { a: 'number' } | crée une fonction de validation pour un objet en fonction des validateurs de champ spécifiés |
Composition des variantes | ['number', 'string'] | Crée une fonction de validation pour valider une valeur avec au moins une des options |
Résultats des appels de méthode d'usine | v.enum('male', 'female') | La plupart des méthodes d'usine renvoient des fonctions de validation (à l'exception de v.rest , qui retourne la composition des objets), elles sont donc traitées comme des fonctions de validation normales |
Toutes ces options de validateur sont valides et peuvent être utilisées n'importe où dans le schéma où le validateur doit être.
Par conséquent, le schéma de travail est toujours le suivant: v(schema)
renvoie une fonction de validation. Ensuite, cette fonction de validation est appelée sur des valeurs spécifiques:
v(schema)(value[, ...parents])
Avez-vous eu des accidents sur le chantier?
- Pas encore, pas un
- Ils le feront!
Il arrive que les données soient invalides et nous devons être en mesure de déterminer la cause de l'invalide.
Pour cela, la bibliothèque de quatuors fournit un mécanisme d' explication . Elle consiste en ce que dans le cas où le validateur, qu'il soit interne ou externe, détecte la validité des données vérifiées, il doit envoyer une note explicative .
À ces fins, le deuxième argument du compositeur de validateurs v
. Il ajoute l'effet secondaire de l'envoi d'une note explicative au tableau v.explanation
en cas de données non valides.
Par exemple, laissez-nous valider un tableau et souhaitons connaître le nombre de tous les éléments invalides et leur valeur:
Comme vous pouvez le voir, le choix de l'explication dépend de la tâche. Parfois, ce n'est même pas nécessaire.
Parfois, nous devons faire quelque chose avec des champs invalides. Dans de tels cas, il est logique d'utiliser le nom du champ non valide comme explication :
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)
Ayant ce mécanisme d'explication, vous pouvez implémenter tout comportement associé aux résultats de la validation.
Une explication peut être n'importe quoi:
- un objet contenant les informations nécessaires;
- fonction qui corrige l'erreur. (
getExplanation => function(invalid): valid
); - nom du champ non valide ou index de l'élément non valide;
- code d'erreur
- et tout cela suffit à votre imagination.
Que faire quand les choses ne se construisent pas?
La correction des erreurs de validation n'est pas une tâche rare. À ces fins, la bibliothèque utilise des validateurs avec un effet secondaire qui se souvient du lieu de l'erreur et de la façon de la corriger.
v.default(validator, value)
- retourne un validateur qui se souvient d'une valeur invalide, et au moment d'appeler v.fix
- définit la valeur par défautv.filter(validator)
- retourne un validateur qui se souvient d'une valeur invalide, et au moment d'appeler v.fix
- supprime cette valeur du parentv.addFix(validator, fixFunc)
- renvoie un validateur qui se souvient d'une valeur non valide, et au moment d'appeler v.fix
- appelle fixFunc avec des paramètres (valeur, {clé, parent}, ...). fixFunc
- doit muter l'un des partenaires - pour changer la valeur
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)),
Les tâches sont toujours utiles
Il existe également des méthodes utilitaires pour les actions de validation dans cette bibliothèque:
La méthode | Résultat |
---|
v.throwError | S'il n'est pas valide, lance une TypeError avec le message donné. |
v.omitInvalidItems | Renvoie un nouveau tableau (ou objet dictionnaire) sans éléments (champs) non valides. |
v.omitInvalidProps | Renvoie un nouvel objet sans champs non valides, selon le validateur d'objet spécifié. |
v.validOr | Renvoie la valeur si elle est valide, sinon elle la remplace par la valeur par défaut spécifiée. |
v.example | Vérifie si les valeurs données correspondent au schéma. S'ils ne correspondent pas, une erreur est générée. Sert de documentation et de test de circuit |
Résultats
Les tâches ont été résolues de la manière suivante:
Défi | Solution |
---|
Validation du type de données | Validateurs nommés par défaut. |
Valeurs par défaut | v.default |
Suppression de pièces invalides | v.filter , v.omitInvalidItems et v.omitInvalidProps . |
Facile à apprendre | Validateurs simples, façons simples de les composer en validateurs complexes. |
Lisibilité du code | L'un des objectifs de la bibliothèque était de comparer les schémas de validation eux-mêmes |
objets validés. |
Facilité de modification | Après avoir maîtrisé les éléments des compositions et utilisé vos propres fonctions de validation - changer le code est assez simple. |
Message d'erreur | Explication sous forme de message d'erreur. Ou calcul du code d'erreur basé sur des explications. |
Postface
Cette solution a été conçue pour créer rapidement et facilement des fonctions de validation avec la possibilité d'intégrer des fonctions de validation personnalisées. Par conséquent, s'il y en a, toutes les corrections, critiques, options d'amélioration de la part de ceux qui ont lu cet article sont les bienvenues. Merci de votre attention.