Aller au contenu

💶 Formulaire sous Rust

Un exemple avec egui :

formulaire avec validation simple

⚙️ Dépendances (Cargo.toml) :

[dependencies]
   eframe = "0.36.1"

💻 Compilation / lancement :

cargo run

Objectif :

Montrer, côté desktop, la même logique que les exercices PHP/PDO côté serveur : collecter des champs, les valider, afficher des messages d'erreur ciblés, puis "traiter" les données seulement si tout est correct, ici pas de base de données : on se contente d'afficher le résultat validé, mais la structure (validation -> erreurs -> traitement) est la même.

💻 Le code main.rs :

use eframe::egui;

// -----------------------------------------------------------------
// 1. État de l'application.
//    - les champs bruts, tels que tapés par l'utilisateur (String)
//    - un indicateur pour savoir si on a déjà tenté une validation
//      (pour n'afficher les erreurs qu'après un premier essai,
//      comme on le ferait en PHP après un $_POST)
//    - le résultat, une fois validé (Option : rien tant que ce
//      n'est pas valide)
// -----------------------------------------------------------------
struct MonFormulaire {
    nom: String,
    email: String,
    age: String, // saisi en texte, converti/validé ensuite

    tentative_soumission: bool,
    resultat_valide: Option<Personne>,
}

// La donnée "propre", une fois la validation passée.
// Comparable à ce que vous obtiendriez après un filter_var() /
// intval() côté PHP.
#[derive(Debug)]
struct Personne {
    nom: String,
    email: String,
    age: u8,
}

impl Default for MonFormulaire {
    fn default() -> Self {
        Self {
            nom: String::new(),
            email: String::new(),
            age: String::new(),
            tentative_soumission: false,
            resultat_valide: None,
        }
    }
}

// -----------------------------------------------------------------
// 2. Fonctions de validation, séparées de l'affichage.
//    Chacune renvoie soit Ok(valeur_propre), soit Err(message).
//    C'est le même principe qu'une fonction PHP valider_email()
//    qu'on appellerait avant l'INSERT en base.
// -----------------------------------------------------------------

fn valider_nom(nom: &str) -> Result<String, String> {
    let nom = nom.trim();
    if nom.is_empty() {
        Err("Le nom ne peut pas être vide.".to_string())
    } else if nom.chars().count() < 2 {
        Err("Le nom doit contenir au moins 2 caractères.".to_string())
    } else {
        Ok(nom.to_string())
    }
}

fn valider_email(email: &str) -> Result<String, String> {
    let email = email.trim();
    // Validation volontairement simple (pédagogique), pas une regex
    // conforme RFC 5322 - on pourrait le mentionner en classe comme
    // limite assumée, exactement comme pour vos exercices PHP.
    if email.is_empty() {
        Err("L'email ne peut pas être vide.".to_string())
    } else if !email.contains('@') || !email.contains('.') {
        Err("Format d'email invalide (attendu : nom@domaine.ext).".to_string())
    } else {
        Ok(email.to_string())
    }
}

fn valider_age(age: &str) -> Result<u8, String> {
    let age = age.trim();
    if age.is_empty() {
        Err("L'âge ne peut pas être vide.".to_string())
    } else {
        match age.parse::<u8>() {
            Ok(valeur) if valeur >= 6 && valeur <= 120 => Ok(valeur),
            Ok(_) => Err("L'âge doit être compris entre 6 et 120.".to_string()),
            Err(_) => Err("L'âge doit être un nombre entier.".to_string()),
        }
    }
}

// -----------------------------------------------------------------
// 3. Le trait eframe::App (API depuis eframe 0.34 : méthode `ui`)
// -----------------------------------------------------------------
impl eframe::App for MonFormulaire {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ui, |ui| {
            ui.heading("Formulaire d'inscription");
            ui.label("Tous les champs sont obligatoires.");
            ui.separator();

            // --- Champ Nom ---
            ui.horizontal(|ui| {
                ui.label("Nom :");
                ui.text_edit_singleline(&mut self.nom);
            });
            // On n'affiche l'erreur qu'après une tentative de
            // soumission, pour ne pas agresser l'utilisateur dès
            // qu'il commence à taper.
            if self.tentative_soumission {
                if let Err(message) = valider_nom(&self.nom) {
                    ui.colored_label(egui::Color32::RED, message);
                }
            }

            ui.add_space(8.0);

            // --- Champ Email ---
            ui.horizontal(|ui| {
                ui.label("Email :");
                ui.text_edit_singleline(&mut self.email);
            });
            if self.tentative_soumission {
                if let Err(message) = valider_email(&self.email) {
                    ui.colored_label(egui::Color32::RED, message);
                }
            }

            ui.add_space(8.0);

            // --- Champ Âge ---
            ui.horizontal(|ui| {
                ui.label("Âge :");
                ui.text_edit_singleline(&mut self.age);
            });
            if self.tentative_soumission {
                if let Err(message) = valider_age(&self.age) {
                    ui.colored_label(egui::Color32::RED, message);
                }
            }

            ui.add_space(16.0);
            ui.separator();

            // --- Bouton de soumission ---
            if ui.button("Valider").clicked() {
                self.tentative_soumission = true;

                // On valide chaque champ. Si tous réussissent, on
                // construit la Personne ; sinon resultat_valide
                // reste à None et les messages d'erreur ci-dessus
                // s'affichent au prochain rendu.
                let nom = valider_nom(&self.nom);
                let email = valider_email(&self.email);
                let age = valider_age(&self.age);

                match (nom, email, age) {
                    (Ok(nom), Ok(email), Ok(age)) => {
                        self.resultat_valide = Some(Personne { nom, email, age });
                    }
                    _ => {
                        self.resultat_valide = None;
                    }
                }
            }

            // --- Résultat ---
            if let Some(personne) = &self.resultat_valide {
                ui.add_space(12.0);
                ui.separator();
                ui.colored_label(egui::Color32::GREEN, "Formulaire valide !");
                ui.label(format!("Nom   : {}", personne.nom));
                ui.label(format!("Email : {}", personne.email));
                ui.label(format!("Âge   : {}", personne.age));
            }
        });
    }
}

fn main() -> eframe::Result<()> {
    let options = eframe::NativeOptions::default();
    eframe::run_native(
        "Formulaire egui",
        options,
        Box::new(|_cc| Ok(Box::new(MonFormulaire::default()))),
    )
}