Entropyk/README_STORY_1_3.md

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 états Disconnected et Connected
  • FluidId - Identification des fluides pour validation de compatibilité
  • ConnectionError - Gestion d'erreurs avec thiserror
  • Validation des connexions (compatibilité fluide, continuité pression/enthalpie)
  • Extension du trait Component avec get_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


Date d'implémentation: 2026-02-14
Story: 1.3 - Port and Connection System
Statut: Complété et testé