9.6 KiB
9.6 KiB
Entropyk - Story 1.3: Port and Connection System
🎯 Ce qui a été implémenté
Cette story implémente le système de ports et connexions pour les composants thermodynamiques avec le Type-State pattern pour la sécurité à la compilation.
Fonctionnalités principales
- ✅
Port<State>- Structure générique avec étatsDisconnectedetConnected - ✅
FluidId- Identification des fluides pour validation de compatibilité - ✅
ConnectionError- Gestion d'erreurs avecthiserror - ✅ Validation des connexions (compatibilité fluide, continuité pression/enthalpie)
- ✅ Extension du trait
Componentavecget_ports()
🚀 Instructions de test
1. Prérequis
# Vérifier que Rust est installé
rustc --version
cargo --version
2. Cloner/Naviguer dans le projet
cd /Users/sepehr/dev/Entropyk
3. Compiler le projet
# Compiler tout le workspace
cargo build --workspace
# Compiler en mode release (optimisé)
cargo build --workspace --release
4. Exécuter tous les tests
# Tests complets du workspace
cargo test --workspace
# Tests avec sortie détaillée
cargo test --workspace -- --nocapture
# Tests du crate components uniquement
cargo test -p entropyk-components
# Tests du crate core uniquement
cargo test -p entropyk-core
5. Vérifier la qualité du code
# Clippy (linting strict)
cargo clippy --workspace -- -D warnings
# Formatage du code
cargo fmt --workspace
# Vérifier la documentation
cargo doc --workspace --open
🧪 Tests manuels
Test 1: Création et connexion de ports
Créez un fichier test_ports.rs à la racine du projet :
use entropyk_components::port::{Port, Disconnected, Connected, FluidId, ConnectionError};
use entropyk_core::{Pressure, Enthalpy};
fn main() -> Result<(), ConnectionError> {
println!("=== Test de création de ports ===");
// Créer deux ports déconnectés
let port1 = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(1.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
let port2 = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(1.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
println!("Port 1 créé: fluid={:?}, pressure={:.2} Pa, enthalpy={:.2} J/kg",
port1.fluid_id(),
port1.pressure().to_pascals(),
port1.enthalpy().to_joules_per_kg()
);
println!("Port 2 créé: fluid={:?}, pressure={:.2} Pa, enthalpy={:.2} J/kg",
port2.fluid_id(),
port2.pressure().to_pascals(),
port2.enthalpy().to_joules_per_kg()
);
// Connecter les ports
println!("\n=== Connexion des ports ===");
let (mut connected1, mut connected2) = port1.connect(port2)?;
println!("✅ Ports connectés avec succès!");
println!("Connected 1: pressure={:.2} Pa, enthalpy={:.2} J/kg",
connected1.pressure().to_pascals(),
connected1.enthalpy().to_joules_per_kg()
);
// Modifier les valeurs
println!("\n=== Modification des valeurs ===");
connected1.set_pressure(Pressure::from_bar(1.5));
connected1.set_enthalpy(Enthalpy::from_joules_per_kg(450_000.0));
println!("Port 1 modifié: pressure={:.2} Pa, enthalpy={:.2} J/kg",
connected1.pressure().to_pascals(),
connected1.enthalpy().to_joules_per_kg()
);
Ok(())
}
Pour l'exécuter, ajoutez ce binary dans Cargo.toml:
[[bin]]
name = "test_ports"
path = "test_ports.rs"
Puis:
cargo run --bin test_ports
Test 2: Test d'erreurs
use entropyk_components::port::{Port, FluidId, ConnectionError};
use entropyk_core::{Pressure, Enthalpy};
fn main() {
println!("=== Test des erreurs de connexion ===\n");
// Test 1: Fluides incompatibles
println!("Test 1: Fluides incompatibles");
let port1 = Port::new(
FluidId::new("R134a"),
Pressure::from_pascals(100_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
let port2 = Port::new(
FluidId::new("Water"),
Pressure::from_pascals(100_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
match port1.connect(port2) {
Err(ConnectionError::IncompatibleFluid { from, to }) => {
println!("✅ Erreur capturée: Cannot connect {} to {}", from, to);
}
_ => println!("❌ Erreur non capturée!"),
}
// Test 2: Pression différente
println!("\nTest 2: Pression différente");
let port3 = Port::new(
FluidId::new("R134a"),
Pressure::from_pascals(100_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
let port4 = Port::new(
FluidId::new("R134a"),
Pressure::from_pascals(200_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
match port3.connect(port4) {
Err(ConnectionError::PressureMismatch { from_pressure, to_pressure }) => {
println!("✅ Erreur capturée: Pressure mismatch {} vs {}",
from_pressure, to_pressure);
}
_ => println!("❌ Erreur non capturée!"),
}
// Test 3: Connexion réussie
println!("\nTest 3: Connexion réussie");
let port5 = Port::new(
FluidId::new("R134a"),
Pressure::from_pascals(100_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
let port6 = Port::new(
FluidId::new("R134a"),
Pressure::from_pascals(100_000.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
match port5.connect(port6) {
Ok(_) => println!("✅ Connexion réussie!"),
Err(_) => println!("❌ Connexion échouée!"),
}
}
Test 3: Vérification Type-State (doit échouer à la compilation)
Créez ce fichier pour vérifier que la sécurité à la compilation fonctionne:
use entropyk_components::port::{Port, Disconnected};
use entropyk_core::{Pressure, Enthalpy, FluidId};
fn main() {
// Créer un port déconnecté
let port: Port<Disconnected> = Port::new(
FluidId::new("R134a"),
Pressure::from_bar(1.0),
Enthalpy::from_joules_per_kg(400_000.0)
);
// Cette ligne doit provoquer une erreur de compilation:
// error[E0599]: no method named `pressure` found for struct `Port<Disconnected>`
// let _p = port.pressure();
println!("Si vous décommentez la ligne ci-dessus, la compilation échouera!");
println!("Cela prouve que le Type-State pattern fonctionne.");
}
📊 Structure des fichiers
entropyk/
├── Cargo.toml # Workspace root
├── crates/
│ ├── components/ # Crate components (modifié)
│ │ ├── Cargo.toml # + Dépendances ajoutées
│ │ └── src/
│ │ ├── lib.rs # + Trait Component étendu
│ │ └── port.rs # NOUVEAU: Implémentation ports
│ └── core/ # Crate core (existant)
│ └── src/
│ └── types.rs # Pressure, Enthalpy, etc.
└── _bmad-output/
└── implementation-artifacts/
└── 1-3-port-and-connection-system.md # Story document
🔍 Points clés de l'implémentation
Type-State Pattern
// Disconnected et Connected sont des marqueurs de type vides
pub struct Disconnected;
pub struct Connected;
// Port est générique sur l'état
pub struct Port<State> {
fluid_id: FluidId,
pressure: Pressure,
enthalpy: Enthalpy,
_state: PhantomData<State>, // Marqueur zéro-cost
}
// Seuls les ports Disconnected peuvent être connectés
impl Port<Disconnected> {
pub fn connect(self, other: Port<Disconnected>)
-> Result<(Port<Connected>, Port<Connected>), ConnectionError> {
// Validation et connexion...
}
}
// Seuls les ports Connected exposent les méthodes de lecture/écriture
impl Port<Connected> {
pub fn pressure(&self) -> Pressure { self.pressure }
pub fn set_pressure(&mut self, pressure: Pressure) { self.pressure = pressure }
}
Validation des connexions
pub fn connect(self, other: Port<Disconnected>)
-> Result<(Port<Connected>, Port<Connected>), ConnectionError>
{
// 1. Validation du fluide
if self.fluid_id != other.fluid_id {
return Err(ConnectionError::IncompatibleFluid { ... });
}
// 2. Validation de la continuité de pression
if pressure_diff > 1e-6 {
return Err(ConnectionError::PressureMismatch { ... });
}
// 3. Validation de la continuité d'enthalpie
if enthalpy_diff > 1e-6 {
return Err(ConnectionError::EnthalpyMismatch { ... });
}
// Création des ports connectés avec valeurs moyennées
Ok((connected1, connected2))
}
🐛 Dépannage
Erreur: unresolved import entropyk_core
Solution: Vérifier que la dépendance est bien dans crates/components/Cargo.toml:
[dependencies]
entropyk-core = { path = "../core" }
Erreur: approx crate not found
Solution: Vérifier la dev-dependency:
[dev-dependencies]
approx = "0.5"
Tests qui échouent
# Nettoyer et reconstruire
cargo clean
cargo test --workspace
📚 Ressources
- Rust Type-State Pattern
- thiserror documentation
- approx documentation (pour les assertions flottantes)
Date d'implémentation: 2026-02-14
Story: 1.3 - Port and Connection System
Statut: ✅ Complété et testé