Lire et valider les paramètres d'une adresse
Ne jamais faire confiance à une adresse : lire les paramètres, les vérifier un par un, et afficher une erreur claire s'ils sont faux.
Objectifs de la séance
- lire les paramètres de l'adresse avec
useLocalSearchParams; - comprendre pourquoi un paramètre d'adresse est une entrée non fiable ;
- valider par liste blanche, avec une expression régulière pour les nombres ;
- représenter « invalide » par
nullet afficher un composantMessageErreur; - découper un écran en deux composants pour respecter la règle des hooks.
Petite vérification
Avant de commencer, vérifiez si vous savez...
- ouvrir le générateur, choisir le thème « Réseaux et Wi-Fi » et le niveau « Expert », et lire le nombre de questions annonc é ainsi que les puces désactivées
- ouvrir le générateur avec les réglages « Tous » et « Tous » et lire le nombre de questions annoncé
- exécuter
npm testet lire le nombre de tests réussis
📌 Réponse attendue
Notions théoriques
Une adresse n'est pas fiable
Avec le générateur, l'adresse de la partie est /partie?theme=reseaux&niveau=2&nombre=3. Mais le joueur n'est pas
obligé de passer par le générateur : sur le Web, il peut taper n'importe quoi dans la barre d'adresse, ou envoyer un
lien piégé à un ami :
| Adresse tapée à la main | Problème |
|---|---|
/partie?theme=pirates&niveau=1&nombre=3 | thème inexistant |
/partie?theme=tous&niveau=tous&nombre=999 | beaucoup trop de questions |
/partie?theme=tous&niveau=tous&nombre=-2 | nombre négatif |
/partie?theme=tous&niveau=1e3&nombre=3 | notation scientifique |
/partie | paramètres absents |
C'est un principe général de sécurité : toute donnée qui vient de l'extérieur (adresse, formulaire, réseau, fichier) est non fiable. On ne l'utilise qu'après l'avoir vérifiée.
Lire les paramètres : useLocalSearchParams
const parametres = useLocalSearchParams<{
theme?: string;
niveau?: string;
nombre?: string;
}>();
Ce hook d'Expo Router renvoie les paramètres de l'adresse courante. Pour chacun, la valeur est de type
string | string[] | undefined :
- une chaîne :
?nombre=3donne"3"; - un tableau de chaînes :
?nombre=3&nombre=4(le même paramètre répété) ; undefined: le paramètre est absent.
La fonction premiereValeur prend la première valeur si c'est un tableau :
function premiereValeur(parametre: ParametreBrut): string | undefined {
return Array.isArray(parametre) ? parametre[0] : parametre;
}
La liste blanche
Pour un thème ou un niveau, on n'essaie pas de « deviner » : on accepte uniquement les valeurs prévues. C'est une liste blanche (allowlist) : tout ce qui n'est pas explicitement autorisé est refusé.
function lireNiveau(texte: string | undefined): FiltreNiveau | null {
switch (texte) {
case "tous":
return "tous";
case "1":
return 1;
case "2":
return 2;
case "3":
return 3;
default:
return null;
}
}
Le default renvoie null. Ici, null signifie « invalide » (alors que "tous" est une valeur valide).
Valider un nombre
Un nombre contenu dans un texte est plus délicat qu'il n'y paraît. Number() est trop tolérant : Number("1e3") vaut
1000, Number("") vaut 0, Number("3.5") vaut 3,5. On commence donc par vérifier que le texte ne contient que des
chiffres, avec une expression régulière :
function lireNombre(texte: string | undefined): number | null {
// /^\d+$/ : uniquement des chiffres (refuse "", "3.5", "-2", "1e3"...)
if (texte === undefined || !/^\d+$/.test(texte)) {
return null;
}
const nombre = Number(texte);
if (nombre < 1 || nombre > NOMBRE_MAX_QUESTIONS) {
return null;
}
return nombre;
}
/^\d+$/: du début (^) à la fin ($), un ou plusieurs (+) chiffres (\d) ;- ensuite seulement, on convertit avec
Numberet on vérifie les bornes (de 1 àNOMBRE_MAX_QUESTIONS, soit 20).
Une fonction pure qui renvoie null
lireConfigPartie assemble les trois lectures. Si un seul paramètre est invalide, elle renvoie null. Elle est
pure : elle se teste facilement avec npm test, sans lancer l'application.
Découper l'écran : la règle des hooks
Une fois la configuration validée, l'écran de jeu a besoin de useReducer et useChronometre. Or on ne peut pas écrire :
// IMPOSSIBLE : un hook ne peut pas être appelé après un return conditionnel
if (config === null) {
return <MessageErreur ... />;
}
const [etat, dispatch] = useReducer(...); // appelé seulement parfois : interdit
C'est la règle des hooks (séance sur useState) : toujours appelés, dans le même ordre. La solution est de
découper l'écran en deux composants :
partie.tsx: lit et valide les paramètres, affiche l'erreur ou<PartieEnCours config={config} />;PartieEnCours.tsx: le jeu, qui reçoit une configuration déjà validée et n'a plus à douter.
On valide les données là où elles entrent dans l'application (ici l'écran qui lit l'adresse). Le reste du code
reçoit des données propres et typées (ConfigPartie) : inutile de revérifier partout.
TP pour réfléchir et résoudre des problèmes
Votre mission : l'écran partie doit lire les réglages du générateur, les valider, jouer avec ces réglages, ou
afficher une erreur. Vous êtes sur la branche feature/navigation.
Étape 1 — Les constantes de validation
Dans src/constants/generateur.ts, ajoutez la liste des codes de thèmes connus et le nombre maximum de questions.
N'oubliez pas de modifier l'importation de la première ligne : import type { CodeTheme, FiltreNiveau } from "@/types/quiz";.
Sans limite maximale, ?nombre=1000000 demanderait un million de questions. Une constante nommée rend la règle visible
et facile à changer.
Étape 2 — Valider le thème
Créez src/logic/parametresPartie.ts. Attention : les importations de valeurs y sont en chemin relatif avec l'extension
(import { CODES_THEMES, NOMBRE_MAX_QUESTIONS } from "../constants/generateur.ts";) pour que npm test fonctionne.
On ne dit pas « refuser si le texte est bizarre » : on dit « accepter si le texte est dans la liste ». Tout ce qui n'a pas été prévu est refusé, y compris les cas auxquels vous n'avez pas pensé.
Étape 3 — Valider le nombre de questions
D'abord le format (que des chiffres), ensuite la valeur (entre 1 et 20). Convertir d'abord avec Number et
tester ensuite laisserait passer "1e3", "" ou " 5".
Étape 4 — Assembler la configuration
La fonction lireConfigPartie lit les trois paramètres et renvoie la configuration, ou null.
Terminez le fichier avec les types ParametreBrut et ParametresBruts, la fonction premiereValeur et la fonction
lireNiveau (voir « Une solution »).
Une configuration partiellement valide est dangereuse : mieux vaut tout refuser que corriger en silence (« 999 devient 20 ») et cacher le problème.
Étape 5 — Tester les cas piégés
Créez tests/parametresPartie.test.ts. Chaque adresse piégée du tableau de la partie théorique devient un test.
Lancez npm test : tests 36 et pass 36.
Pour une fonction de validation, les tests les plus utiles sont les valeurs mauvaises : vide, négative, trop grande, décimale, notation scientifique, texte. Un test qui échoue vous montre exactement la valeur qui passe à tort.
Étape 6 — Un composant pour afficher les erreurs
Créez src/components/MessageErreur.tsx : un message sur fond rouge clair, avec un bouton facultatif.
Remarquez accessibilityRole="alert" sur la View : les lecteurs d'écran annoncent le message.
« Les réglages de la partie sont invalides » et un bouton « Retour au générateur » valent mieux qu'un écran blanc. Le joueur comprend ce qui s'est passé et sait quoi faire.
Étape 7 — Déplacer le jeu dans PartieEnCours
Créez src/components/PartieEnCours.tsx : copiez-y tout le contenu actuel de src/app/partie.tsx, puis :
- renommez la fonction en
PartieEnCourset ajoutez le typeProps = { config: ConfigPartie }; - ajoutez la fonction
genererQuestions(config)qui filtreQUESTIONS_LOCALESselon la configuration, puis appellepreparerQuestions; - remplacez
nouvellesQuestions()pargenererQuestions(config); - ajoutez le cas « aucune question ne correspond » (liste vide).
PartieEnCours ne sait pas d'où viennent ses réglages (adresse, test, autre écran) : il reçoit simplement une
config fiable. Cela le rend facile à comprendre, à tester et à réutiliser.
Étape 8 — Valider dans l'écran partie
Remplacez tout le contenu de src/app/partie.tsx : il lit les paramètres, les valide, et choisit ce qu'il affiche.
Testez maintenant dans le navigateur, en tapant des adresses à la main (le port est celui de npx expo start) :
| Adresse | Résultat attendu |
|---|---|
/partie?theme=reseaux&niveau=2&nombre=3 | une partie de 3 questions sur le thème Réseaux |
/partie?theme=pirates&niveau=1&nombre=3 | message « Les réglages de la partie sont invalides. » |
/partie?theme=tous&niveau=tous&nombre=999 | le même message d'erreur |
/partie | le même message d'erreur |
Tapez des adresses absurdes. Si un cas fait planter l'application, c'est un bug à corriger, et c'est mieux de le découvrir vous-même.
Étape 9 — Enregistrer votre travail
Avant chaque git add -A, relisez git status. À partir du tutoriel sur Supabase, des fichiers sensibles (.env.local)
existeront : le .gitignore les protège, mais seule votre vigilance vérifie qu'ils ne sont pas ajoutés.
📌 Une solution
Test de mémorisation/compréhension
Exercices complémentaires
Exercice A : le tableau des essais
Essayez dans le navigateur cinq adresses de votre invention (par exemple nombre=0, niveau=4, theme=) et notez, pour
chacune, si l'application affiche une partie ou l'erreur. Les résultats correspondent-ils à ce que prévoit lireConfigPartie ?
Exercice B : un test pour le niveau
Dans tests/parametresPartie.test.ts, ajoutez un test qui vérifie que niveau: "0" est refusé (résultat null). Pourquoi ce
cas est-il refusé par lireNiveau ?
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Entrée non fiable | Une adresse peut être modifiée à la main : on vérifie tout avant d'utiliser. |
useLocalSearchParams | Renvoie les paramètres de l'adresse, de type string, string[] ou undefined. |
| Liste blanche | On accepte uniquement les valeurs prévues (CODES_THEMES, niveaux 1 à 3). |
| Nombre | Format (/^\d+$/) puis valeur (1 à 20) : jamais Number() seul. |
null | Représente « invalide » : une seule valeur invalide suffit pour refuser la configuration. |
| Règle des hooks | On découpe l'écran : partie.tsx valide, PartieEnCours joue. |
Aperçu de la prochaine séance
Quand la partie se termine, l'écran affiche encore le résultat dans le même écran, avec un bouton « Rejouer » qui
complique le reducer. Dans la prochaine séance, vous allez créer un écran de résultat dédié, et comprendre la
différence entre router.push et router.replace : un joueur qui appuie sur « retour » ne doit pas revenir dans une partie finie.