feat(models): add domain structs and CIDR validation
Add shared models (Network, Host, Port, Application, ApplicationPort) with serde derives for Leptos server function serialization. Add server/validation.rs with valider_ip_dans_reseau() and 5 unit tests. Gate SSR-only modules (config, validation) with #[cfg(feature = "ssr")]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
// server/mod.rs — Module serveur
|
||||
//
|
||||
// Contient tout le code qui s'exécute uniquement côté serveur :
|
||||
// - Handlers HTTP additionnels (routes.rs)
|
||||
// - Fonctions serveur Leptos : accès à la base de données, authentification...
|
||||
// - Logique métier qui ne doit JAMAIS être exposée dans le bundle WASM
|
||||
// Contient tout le code qui s'exécute uniquement côté serveur.
|
||||
//
|
||||
// Les "server functions" Leptos (marquées #[server]) sont déclarées ici.
|
||||
// Elles apparaissent comme des appels asynchrones normaux côté client,
|
||||
// mais s'exécutent exclusivement sur le serveur — transparence totale.
|
||||
// Certains sous-modules utilisent des crates SSR-only (dotenvy, ipnetwork...)
|
||||
// et sont donc protégés par `#[cfg(feature = "ssr")]` pour ne pas être
|
||||
// compilés dans le bundle WASM.
|
||||
|
||||
pub mod config;
|
||||
// Accessible des deux côtés (handlers HTTP sont SSR mais le module est déclaré shared)
|
||||
pub mod routes;
|
||||
|
||||
// Modules SSR uniquement — utilisent des crates non disponibles en WASM
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod config;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod validation;
|
||||
|
||||
110
src/server/validation.rs
Normal file
110
src/server/validation.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// server/validation.rs — Validation des données métier
|
||||
//
|
||||
// Ce module vérifie les règles métier qui ne peuvent pas être exprimées
|
||||
// uniquement par les types Rust. Il s'exécute côté serveur uniquement
|
||||
// car il utilise `ipnetwork` pour les calculs CIDR.
|
||||
|
||||
use ipnetwork::IpNetwork;
|
||||
use std::net::IpAddr;
|
||||
use thiserror::Error;
|
||||
|
||||
// ─── Erreurs de validation ────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ValidationError {
|
||||
/// Le format CIDR est invalide (ex: "192.168.1/24" au lieu de "192.168.1.0/24")
|
||||
#[error("CIDR invalide '{0}' : {1}")]
|
||||
CidrInvalide(String, ipnetwork::IpNetworkError),
|
||||
|
||||
/// L'adresse IP n'a pas le bon format
|
||||
#[error("Adresse IP invalide '{0}' : {1}")]
|
||||
IpInvalide(String, std::net::AddrParseError),
|
||||
|
||||
/// L'IP de l'hôte n'appartient pas à la plage du réseau
|
||||
#[error("L'adresse IP {ip} n'appartient pas au réseau {cidr}")]
|
||||
IpHorsReseau { ip: String, cidr: String },
|
||||
}
|
||||
|
||||
// ─── Validation du CIDR ───────────────────────────────────────────────────────
|
||||
|
||||
/// Vérifie qu'une chaîne est un CIDR valide.
|
||||
/// Retourne le réseau parsé si valide.
|
||||
///
|
||||
/// `&str` : on emprunte la chaîne sans en prendre possession (pas de copie).
|
||||
/// `Result<T, E>` : soit une valeur T (succès), soit une erreur E (échec).
|
||||
pub fn valider_cidr(cidr: &str) -> Result<IpNetwork, ValidationError> {
|
||||
cidr.parse::<IpNetwork>()
|
||||
// `.map_err` transforme l'erreur si `parse` échoue
|
||||
// `|e|` est une closure : une fonction anonyme qui prend `e` en paramètre
|
||||
.map_err(|e| ValidationError::CidrInvalide(cidr.to_string(), e))
|
||||
}
|
||||
|
||||
// ─── Validation de l'appartenance IP ↔ Réseau ────────────────────────────────
|
||||
|
||||
/// Vérifie qu'une adresse IP appartient à la plage d'un réseau CIDR.
|
||||
///
|
||||
/// Règle métier clé : un hôte doit toujours être dans le réseau auquel il appartient.
|
||||
/// Ex : 192.168.1.10 ✓ dans 192.168.1.0/24
|
||||
/// 10.0.0.1 ✗ dans 192.168.1.0/24
|
||||
pub fn valider_ip_dans_reseau(ip: &str, cidr: &str) -> Result<(), ValidationError> {
|
||||
// On parse le CIDR et l'IP, propageant les erreurs avec `?`
|
||||
let reseau = valider_cidr(cidr)?;
|
||||
|
||||
let adresse: IpAddr = ip
|
||||
.parse()
|
||||
.map_err(|e| ValidationError::IpInvalide(ip.to_string(), e))?;
|
||||
|
||||
// `IpNetwork::contains` retourne true si l'IP est dans la plage
|
||||
if reseau.contains(adresse) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ValidationError::IpHorsReseau {
|
||||
ip: ip.to_string(),
|
||||
cidr: cidr.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
// `#[cfg(test)]` : ce bloc n'est compilé que lors de `cargo test`
|
||||
// Écrire les tests directement dans le même fichier est idiomatique en Rust.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// `super::*` importe tout depuis le module parent (ce fichier)
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ip_dans_reseau_valide() {
|
||||
// `.unwrap()` est acceptable dans les tests — si ça échoue, le test échoue
|
||||
assert!(valider_ip_dans_reseau("192.168.1.10", "192.168.1.0/24").is_ok());
|
||||
assert!(valider_ip_dans_reseau("10.0.0.1", "10.0.0.0/8").is_ok());
|
||||
assert!(valider_ip_dans_reseau("172.16.5.100", "172.16.0.0/12").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ip_hors_reseau() {
|
||||
assert!(valider_ip_dans_reseau("10.0.0.1", "192.168.1.0/24").is_err());
|
||||
assert!(valider_ip_dans_reseau("192.168.2.1", "192.168.1.0/24").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cidr_invalide() {
|
||||
assert!(valider_cidr("pas-un-cidr").is_err());
|
||||
assert!(valider_cidr("192.168.1/24").is_err()); // adresse tronquée
|
||||
assert!(valider_cidr("192.168.1.0/33").is_err()); // préfixe > 32
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ip_invalide() {
|
||||
assert!(valider_ip_dans_reseau("999.0.0.1", "192.168.1.0/24").is_err());
|
||||
assert!(valider_ip_dans_reseau("pas-une-ip", "192.168.1.0/24").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protocole_connu() {
|
||||
assert_eq!(crate::models::Port::protocole_connu(22), Some("SSH"));
|
||||
assert_eq!(crate::models::Port::protocole_connu(443), Some("HTTPS"));
|
||||
assert_eq!(crate::models::Port::protocole_connu(9999), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user