RFC - Réflexions sur la validation #2178
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Avec cette PR, je voudrais ouvrir le débat sur la maniètre dont est faite la validation (et tout ce qui va autour) dans notre code.
L'idée ici est de réfléchir à une meilleure manière de valider nos données, et de s'assurer de manière générale qu'on a des données typées et correctement formattées dans l'ensemble de notre application. Ce n'est donc pas uniquement la validation et les schémas Yup
Voici ce que j'identifie comme pain points dans notre fonctionnement actuel (il n'y a pas d'ordre particulier, les numéros sont là pour les référencer plus tard) :
.when(x.when(x.when()))
imbriqués. C'est souvent difficile de comprendre vraiment quand un champ est required ou pas et quelles règles s'appliquentIl me semble que globalement chaque resolver devrait commencer par quelque chose comme ça:
On commence toujours par appliquer
flatten()
sur l'input au lieu de parfois le faire tardivement. On pourrait même imaginer que ca soit fait dans une étape de preprocess, genre un middleware chargé deflatten
automatiquement les inputs. C'est sans douteoverkill, mais c'est l'idée: essayons de toujours travailler avec un seul et même type en back (c'est le point 1 plus haut).Ensuite, on ne fait plus de la validation mais du parsing. C'est le "parse don't validate" dont on entend parler de temps en temps et qui me semble important. C'est à dire que notre moulinette de validation doit avoir la capacité de modifier nos données d'entrées (
result=parse(input)
vsvalidate(input)
). Concrètement, je pense qu'on devrait mettre beaucoup plus de choses dans cette moulinette pour y centraliser validation + remplissage auto + valeurs par défauts. Même pourquoi pas adaptation de l'objet vers un objet compatible Prisma.Ex:
Ce schéma peut ainsi centraliser toutes les règles métiers associées au type, donner de meilleurs défauts (eg false vs null), et aller aussi loin que l'on veut dans le fait de sortir un type "Prisma compatible". Et à noter ce type de sortie est strictement typé, en prenant en compte les transformation appliquées. Ca permet je crois de résoudre les points 2 & 3.
Pour le point 4, je pense qu'il faudrait bannir l'utilisation de
.when()
qui est problématique en terme de lisibilité du schéma. Zod n'a volontairement pas ajouté cette méthode. De même il n'y a pas de contexte de validation directement intégré à Zod. A nous de construire le schéma avec notre contexte. Ces contraintes me semblent saines et peuvent à mon sens permettre un code plus clair et lisible.C'est avec ces contraintes et en écrivant le schéma différemment qu'on rejoint le point 5: la colocation des règles de validation et des règles de vérouillage de signature.
Pour faire ça, on peut commencer par déclarer sur le schéma tous les fields qui peuvent être optionnels, en optionnel. Plus de
requiredIf
,.when(x then nullable())
. Tout est nullable s'il faut pouvoir être dans un cas nullable (nullish
probablement, c'est à dire soitnull
soitundefined
).Ensuite, sur les r_gles d42dition on peut passer de ça:
A ça:
L'intérêt que je vois à ça c'est la colocation de données liées, et une maitenabilité il me semble bien meilleure. En un coup d'oeil, je sais quand mon champ est required, par quoi il est vérouillé, quand il doit être vide, et même ses relations métiers avec les autres champs.
Ces règles sont automatiquement appliquées au schéma global avec une implémentation comme ce qui a été fait dans le fichier
validate.ts
. L'idée est qu'on prend un schéma, des règles et un contexte de validation, on mélange tout ça et on obtient un parsing + validation adapté à notre situation.Voilà pour l'état de ma réflexion. Je serais intéressé par vous outputs. L'idée n'est pas de se lancer dans un tel refactor tant qu'on ne s'est pas mis d'accord sur 1 l'intérêt de ce changement, et 2 la manière de le mener.
A vos commentaires / avis / jet de tomates 🍅 !