Formulaires

Types de champs

TextField — Champs texte

Le TextField gère 6 formats spéciaux via l'enum SpecialFormat :

// Texte simple
form.field(&TextField::text("username").label("Nom").required());

// Email — validé via `validator::ValidateEmail`
form.field(&TextField::email("email").label("Email").required());

// URL — validée via `validator::ValidateUrl`
form.field(&TextField::url("website").label("Site web"));

// Mot de passe — hachage Argon2 automatique dans finalize(), jamais ré-affiché en HTML
form.field(
    &TextField::password("password")
        .label("Mot de passe")
        .required()
        .min_length(8, "Min 8 caractères"),
);

// Textarea
form.field(&TextField::textarea("summary").label("Résumé"));

// RichText — sanitisation XSS automatique avant validation
form.field(&TextField::richtext("content").label("Contenu"));

Options du builder :

TextField::text("nom")
    .label("Mon champ")              // Label affiché
    .placeholder("Entrez...")        // Placeholder
    .required()                       // Obligatoire (message par défaut)
    .min_length(3, "Trop court")     // Longueur min avec message
    .max_length(100, "Trop long")    // Longueur max avec message
    .readonly("Lecture seule")       // Lecture seule
    .disabled("Désactivé")          // Désactivé

Comportements automatiques par format :

FormatValidationTransformation
Emailvalidator::ValidateEmailConversion en lowercase
Urlvalidator::ValidateUrl
PasswordStandardHachage Argon2 dans finalize(), valeur vidée au render()
RichTextStandardSanitisation XSS (sanitize()) avant validation
CsrfToken session

Utilitaires mot de passe :

Le hachage et la vérification sont délégués à PasswordConfig, initialisé au démarrage via password_init() :

use runique::prelude::{hash, verify};

// Hacher manuellement (ex: création de compte hors formulaire)
let hashed = hash("mon_mot_de_passe")?;

// Vérifier un mot de passe clair contre un hash stocké en DB (ex: connexion)
let ok = verify("mdp_clair", &user.password_hash);
if !ok {
    // mot de passe incorrect
}

Le hachage automatique dans finalize() détecte si la valeur commence déjà par $argon2 pour éviter un double hachage. Dans un formulaire de connexion, n'utilisez pas is_valid() pour vérifier le mot de passe — récupérez d'abord l'utilisateur en DB, puis appelez verify() manuellement.

Voir → Configuration des mots de passe pour tous les modes (Auto, Manual, Delegated, Custom) et la configuration dans main.rs.


NumericField — Champs numériques

5 variantes via l'enum NumericConfig :

// Entier avec bornes
form.field(
    &NumericField::integer("age")
        .label("Âge")
        .min(0.0, "Min 0")
        .max(150.0, "Max 150"),
);

// Nombre flottant
form.field(&NumericField::float("price").label("Prix"));

// Nombre décimal avec précision
form.field(
    &NumericField::decimal("amount")
        .label("Montant")
        .digits(2, 4),  // min 2, max 4 chiffres après la virgule
);

// Pourcentage (0–100 par défaut)
form.field(&NumericField::percent("rate").label("Taux"));

// Range slider avec min, max, valeur par défaut
form.field(
    &NumericField::range("volume", 0.0, 100.0, 50.0)
        .label("Volume")
        .step(5.0),
);

Options : .min(val, msg), .max(val, msg), .step(val), .digits(min, max), .label(l), .placeholder(p)


BooleanField — Cases à cocher / Radio simple

// Checkbox simple
form.field(
    &BooleanField::new("accept_terms")
        .label("J'accepte les conditions")
        .required(),
);

// Radio simple (oui/non)
form.field(&BooleanField::radio("newsletter").label("Newsletter"));

// Pré-coché
form.field(&BooleanField::new("remember_me").label("Se souvenir").checked());

ChoiceField — Select / Dropdown

use runique::forms::fields::choice::ChoiceOption;

let choices = vec![
    ChoiceOption::new("fr", "France"),
    ChoiceOption::new("be", "Belgique"),
    ChoiceOption::new("ch", "Suisse"),
];

// Select simple
form.field(
    &ChoiceField::new("country")
        .label("Pays")
        .choices(choices.clone())
        .required(),
);

// Select multiple
form.field(
    &ChoiceField::new("languages")
        .label("Langues")
        .choices(choices)
        .multiple(),
);

La validation vérifie automatiquement que la valeur soumise fait partie des choix déclarés.


RadioField — Boutons radio

form.field(
    &RadioField::new("gender")
        .label("Genre")
        .choices(vec![
            ChoiceOption::new("m", "Masculin"),
            ChoiceOption::new("f", "Féminin"),
            ChoiceOption::new("o", "Autre"),
        ])
        .required(),
);

CheckboxField — Checkboxes multiples

form.field(
    &CheckboxField::new("hobbies")
        .label("Loisirs")
        .choices(vec![
            ChoiceOption::new("sport", "Sport"),
            ChoiceOption::new("musique", "Musique"),
            ChoiceOption::new("lecture", "Lecture"),
        ]),
);

Les valeurs soumises sont au format "val1,val2,val3". La validation vérifie que chaque valeur existe dans les choix.


DateField, TimeField, DateTimeField — Date / Heure

use chrono::NaiveDate;

// Date (format: YYYY-MM-DD)
form.field(
    &DateField::new("birthday")
        .label("Date de naissance")
        .min(NaiveDate::from_ymd_opt(1900, 1, 1).unwrap(), "Trop ancien")
        .max(NaiveDate::from_ymd_opt(2010, 12, 31).unwrap(), "Trop récent"),
);

// Heure (format: HH:MM)
form.field(&TimeField::new("meeting_time").label("Heure du RDV"));

// Date + Heure (format: YYYY-MM-DDTHH:MM)
form.field(&DateTimeField::new("event_start").label("Début de l'événement"));

DurationField — Durée

form.field(
    &DurationField::new("timeout")
        .label("Délai (secondes)")
        .min_seconds(60, "Minimum 1 minute")
        .max_seconds(3600, "Maximum 1 heure"),
);

FileField — Upload de fichiers

// Image avec contraintes complètes — dossier explicite
form.field(
    &FileField::image("avatar")
        .label("Photo de profil")
        .upload_to("uploads/avatars")   // → uploads/avatars/
        .max_size_mb(5)
        .max_files(1)
        .max_dimensions(1920, 1080)
        .allowed_extensions(vec!["png", "jpg", "jpeg", "webp", "avif"]),
);

// Image — dossier automatique depuis MEDIA_ROOT (.env)
// Les fichiers vont dans {MEDIA_ROOT}/{nom_du_champ}/  ex: media/photo/
form.field(
    &FileField::image("photo")
        .label("Photo")
        .upload_to_env()
        .max_size_mb(5),
);

// Sans upload_to — fichiers stockés directement dans MEDIA_ROOT
form.field(
    &FileField::image("image")
        .label("Image")
        .max_size_mb(5),
);

// Document
form.field(
    &FileField::document("cv")
        .label("CV")
        .upload_to("uploads/cv")
        .max_size_mb(10),
);

// Fichier quelconque (multi-fichiers)
form.field(
    &FileField::any("attachments")
        .label("Pièces jointes")
        .max_files(5),
);

Destination des fichiers :

MéthodeDestination
.upload_to("uploads/images")uploads/images/ (chemin exact)
.upload_to_env(){MEDIA_ROOT}/{nom_du_champ}/ (depuis .env)
(aucune)MEDIA_ROOT directement (pas de sous-dossier)

Le déplacement vers la destination finale s'effectue dans finalize(), uniquement si la validation passe. Le dossier est créé automatiquement s'il n'existe pas encore.

Sécurité : les fichiers .svg sont toujours refusés par défaut (risque XSS). La validation d'image utilise le crate image pour vérifier le format réel du fichier. Les fichiers vides (aucun fichier sélectionné) sont ignorés proprement — le champ required fonctionne correctement.

Fichiers JS associés

fn register_fields(form: &mut Forms) {
    // ... champs ...
    form.add_js(&["js/mon_script.js", "js/autre.js"]);
}

Les fichiers JS sont inclus automatiquement dans le rendu HTML du formulaire.


ColorField — Sélecteur de couleur

form.field(
    &ColorField::new("theme_color")
        .label("Couleur du thème")
        .default_color("#3498db"),  // Valide le format #RGB ou #RRGGBB
);

SlugField — Slug URL-friendly

form.field(
    &SlugField::new("slug")
        .label("Slug")
        .placeholder("mon-article-url")
        .allow_unicode(),  // Optionnel : autorise les caractères unicode
);

Validation : lettres, chiffres, tirets, underscores uniquement. Ne peut pas commencer ou finir par un tiret.


UUIDField

form.field(
    &UUIDField::new("external_id")
        .label("ID externe")
        .placeholder("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
);

JSONField — Textarea avec validation JSON

form.field(
    &JSONField::new("metadata")
        .label("Métadonnées")
        .placeholder(r#"{"clé": "valeur"}"#)
        .rows(10),  // Nombre de lignes du textarea
);

IPAddressField — Adresse IP

// IPv4 + IPv6
form.field(&IPAddressField::new("server_ip").label("IP du serveur"));

// IPv4 uniquement
form.field(&IPAddressField::new("gateway").label("Passerelle").ipv4_only());

// IPv6 uniquement
form.field(&IPAddressField::new("ipv6").label("Adresse IPv6").ipv6_only());

HiddenField — Champ caché

Champ invisible dans le formulaire HTML (<input type="hidden">). Deux usages principaux : passer des données techniques sans les montrer à l'utilisateur, ou valider un token CSRF manuellement.

// Champ caché générique (ex: ID d'une entité liée)
form.field(
    &HiddenField::new("entity_id")
        .label("ID entité"),
);

// Champ CSRF interne (géré automatiquement par Runique — pour usage avancé)
form.field(&HiddenField::new_csrf());

Dans les formulaires Runique standards, le CSRF est géré automatiquement via {% csrf %} dans le template. Vous n'avez pas besoin de HiddenField::new_csrf() sauf si vous construisez un formulaire entièrement personnalisé.


Récapitulatif des types de champs

StructConstructeursValidation spéciale
TextFieldtext(), email(), url(), password(), textarea(), richtext()Email/URL via validator, Argon2, sanitisation XSS
NumericFieldinteger(), float(), decimal(), percent(), range()Bornes min/max, précision décimale
BooleanFieldnew(), radio()Requis = doit être coché
ChoiceFieldnew() + .multiple()Valeur dans les choix déclarés
RadioFieldnew()Valeur dans les choix déclarés
CheckboxFieldnew()Toutes les valeurs dans les choix
DateFieldnew()Format YYYY-MM-DD, bornes min/max
TimeFieldnew()Format HH:MM, bornes min/max
DateTimeFieldnew()Format YYYY-MM-DDTHH:MM, bornes min/max
DurationFieldnew()Secondes, bornes min/max
FileFieldimage(), document(), any()Extensions, taille, dimensions, anti-SVG
ColorFieldnew()Format #RRGGBB ou #RGB
SlugFieldnew()ASCII/unicode, pas de tiret en début/fin
UUIDFieldnew()Format UUID valide
JSONFieldnew()JSON valide via serde_json
IPAddressFieldnew() + .ipv4_only() / .ipv6_only()IPv4/IPv6 via std::net::IpAddr
HiddenFieldnew(), new_csrf()Token CSRF si name == "csrf_token"

Helpers de conversion | Erreurs de base de données