Aller au contenu principal

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 null et afficher un composant MessageErreur ;
  • 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...

  1. 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
  2. ouvrir le générateur avec les réglages « Tous » et « Tous » et lire le nombre de questions annoncé
  3. exécuter npm test et 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 mainProblème
/partie?theme=pirates&niveau=1&nombre=3thème inexistant
/partie?theme=tous&niveau=tous&nombre=999beaucoup trop de questions
/partie?theme=tous&niveau=tous&nombre=-2nombre négatif
/partie?theme=tous&niveau=1e3&nombre=3notation scientifique
/partieparamè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=3 donne "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 Number et 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.
Valider à la frontière

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";.


Bonne pratique - Une limite explicite

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.


Bonne pratique - Refuser par défaut

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​


Bonne pratique - Vérifier la forme, puis la valeur

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 »).


Bonne pratique - Tout ou rien

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.


Bonne pratique - Tester les entrées hostiles

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.


Bonne pratique - Une erreur qui aide

« 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 PartieEnCours et ajoutez le type Props = { config: ConfigPartie } ;
  • ajoutez la fonction genererQuestions(config) qui filtre QUESTIONS_LOCALES selon la configuration, puis appelle preparerQuestions ;
  • remplacez nouvellesQuestions() par genererQuestions(config) ;
  • ajoutez le cas « aucune question ne correspond » (liste vide).

Bonne pratique - Un composant, un rôle

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) :

AdresseRésultat attendu
/partie?theme=reseaux&niveau=2&nombre=3une partie de 3 questions sur le thème Réseaux
/partie?theme=pirates&niveau=1&nombre=3message « Les réglages de la partie sont invalides. »
/partie?theme=tous&niveau=tous&nombre=999le même message d'erreur
/partiele même message d'erreur

Bonne pratique - Essayer d'attaquer sa propre application

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​


Bonne pratique - Pas de secret dans l'historique

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​


Pourquoi faut-il valider les paramètres lus dans l'adresse ?


Quel est le type d'un paramètre renvoyé par useLocalSearchParams ?


Qu'est-ce qu'une liste blanche ?


Pourquoi tester avec /^\d+$/ avant de convertir avec Number ?


Que renvoie lireConfigPartie quand un des paramètres est invalide ?


Pourquoi séparer l'écran en partie.tsx et PartieEnCours.tsx ?



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​

NotionRésumé
Entrée non fiableUne adresse peut être modifiée à la main : on vérifie tout avant d'utiliser.
useLocalSearchParamsRenvoie les paramètres de l'adresse, de type string, string[] ou undefined.
Liste blancheOn accepte uniquement les valeurs prévues (CODES_THEMES, niveaux 1 à 3).
NombreFormat (/^\d+$/) puis valeur (1 à 20) : jamais Number() seul.
nullReprésente « invalide » : une seule valeur invalide suffit pour refuser la configuration.
Règle des hooksOn 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.