objets_metier_rs 1.0.2

Bibliothèque Rust moderne et sûre pour l'API COM Objets Métier Sage 100c - Production Ready
//! Résolution automatique des CLSIDs Sage depuis le registre Windows
//!
//! Ce module permet de découvrir automatiquement les versions de Sage installées
//! en interrogeant le registre Windows. Il supporte :
//! - BSCpta (Comptabilité) : Sage.BSCpta.Application.{version}
//! - BSCial (Commercial) : Sage.BSCial.Application.{version}
//!
//! # Exemples
//!
//! ```no_run
//! use objets_metier_rs::com::clsid_resolver::*;
//!
//! // Trouver toutes les versions BSCpta installées
//! let versions = find_bscpta_versions()?;
//! for (prog_id, clsid) in versions {
//!     println!("{} -> {}", prog_id, clsid);
//! }
//!
//! // Obtenir la dernière version BSCpta
//! let (prog_id, clsid) = find_latest_bscpta()?;
//! println!("Dernière version: {} ({})", prog_id, clsid);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```

use crate::errors::{SageError, SageResult};
use winreg::enums::*;
use winreg::RegKey;

/// Type de module Sage
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SageModule {
    /// Module Comptabilité (BSCpta)
    Comptabilite,
    /// Module Commercial (BSCial)
    Commercial,
}

impl SageModule {
    /// Retourne le ProgID pour ce module
    fn prog_id(&self) -> &'static str {
        match self {
            SageModule::Comptabilite => "Objets100c.Cpta.Stream",
            SageModule::Commercial => "Objets100c.Cial.Stream",
        }
    }

    /// Retourne le nom du module pour les messages
    fn display_name(&self) -> &'static str {
        match self {
            SageModule::Comptabilite => "Sage Comptabilité",
            SageModule::Commercial => "Sage Commercial",
        }
    }
}

/// Résout un CLSID depuis un ProgID Sage
///
/// # Arguments
///
/// * `prog_id` - ProgID comme "Sage.BSCpta.Application.100c" ou "Sage.BSCial.Application.100c"
///
/// # Exemples
///
/// ```no_run
/// use objets_metier_rs::com::clsid_resolver::resolve_prog_id;
///
/// let clsid = resolve_prog_id("Sage.BSCpta.Application.100c")?;
/// assert_eq!(clsid, "309DE0FB-9FB8-4F4E-8295-CC60C60DAA33");
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn resolve_prog_id(prog_id: &str) -> SageResult<String> {
    let hkcr = RegKey::predef(HKEY_CLASSES_ROOT);

    let prog_id_key = hkcr.open_subkey(prog_id).map_err(|e| {
        SageError::RegistryError(format!(
            "ProgID '{}' introuvable dans le registre: {}",
            prog_id, e
        ))
    })?;

    let clsid_key = prog_id_key.open_subkey("CLSID").map_err(|e| {
        SageError::RegistryError(format!(
            "Clé CLSID absente pour ProgID '{}': {}",
            prog_id, e
        ))
    })?;

    let clsid: String = clsid_key.get_value("").map_err(|e| {
        SageError::RegistryError(format!(
            "Valeur CLSID invalide pour ProgID '{}': {}",
            prog_id, e
        ))
    })?;

    // Retirer les accolades si présentes
    Ok(clsid.trim_matches(|c| c == '{' || c == '}').to_string())
}

/// Trouve toutes les versions d'un module Sage installées
///
/// Retourne une liste avec un seul tuple `(ProgID, CLSID)`
fn find_module_versions(module: SageModule) -> SageResult<Vec<(String, String)>> {
    let prog_id = module.prog_id();

    match resolve_prog_id(prog_id) {
        Ok(clsid) => Ok(vec![(prog_id.to_string(), clsid)]),
        Err(_) => Err(SageError::RegistryError(format!(
            "Aucune version de {} trouvée dans le registre. Vérifiez que Sage 100c est installé.",
            module.display_name()
        ))),
    }
}

/// Trouve la dernière version d'un module Sage installée
fn find_latest_module(module: SageModule) -> SageResult<(String, String)> {
    let versions = find_module_versions(module)?;
    versions.into_iter().next().ok_or_else(|| {
        SageError::RegistryError(format!("Aucune version {} trouvée", module.display_name()))
    })
}

/// Trouve toutes les versions BSCpta (Comptabilité) installées
///
/// Retourne une liste de tuples `(ProgID, CLSID)` triée par version (plus récentes en premier)
///
/// # Exemples
///
/// ```no_run
/// use objets_metier_rs::com::clsid_resolver::find_bscpta_versions;
///
/// let versions = find_bscpta_versions()?;
/// for (prog_id, clsid) in versions {
///     println!("Trouvé: {} -> {}", prog_id, clsid);
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn find_bscpta_versions() -> SageResult<Vec<(String, String)>> {
    find_module_versions(SageModule::Comptabilite)
}

/// Trouve la dernière version BSCpta (Comptabilité) installée
///
/// # Exemples
///
/// ```no_run
/// use objets_metier_rs::com::clsid_resolver::find_latest_bscpta;
///
/// let (prog_id, clsid) = find_latest_bscpta()?;
/// println!("Dernière version: {} ({})", prog_id, clsid);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn find_latest_bscpta() -> SageResult<(String, String)> {
    find_latest_module(SageModule::Comptabilite)
}

/// Trouve toutes les versions BSCial (Commercial) installées
///
/// Retourne une liste de tuples `(ProgID, CLSID)` triée par version (plus récentes en premier)
///
/// # Exemples
///
/// ```no_run
/// use objets_metier_rs::com::clsid_resolver::find_bscial_versions;
///
/// let versions = find_bscial_versions()?;
/// for (prog_id, clsid) in versions {
///     println!("Trouvé: {} -> {}", prog_id, clsid);
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn find_bscial_versions() -> SageResult<Vec<(String, String)>> {
    find_module_versions(SageModule::Commercial)
}

/// Trouve la dernière version BSCial (Commercial) installée
///
/// # Exemples
///
/// ```no_run
/// use objets_metier_rs::com::clsid_resolver::find_latest_bscial;
///
/// let (prog_id, clsid) = find_latest_bscial()?;
/// println!("Dernière version: {} ({})", prog_id, clsid);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn find_latest_bscial() -> SageResult<(String, String)> {
    find_latest_module(SageModule::Commercial)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_find_bscpta_versions() {
        match find_bscpta_versions() {
            Ok(versions) => {
                println!("✅ Versions BSCpta trouvées:");
                for (prog_id, clsid) in versions {
                    println!("   - {} -> {}", prog_id, clsid);
                }
            }
            Err(e) => println!("⚠️  Aucune version BSCpta trouvée: {}", e),
        }
    }

    #[test]
    fn test_find_latest_bscpta() {
        match find_latest_bscpta() {
            Ok((prog_id, clsid)) => {
                println!("✅ Dernière version BSCpta: {} ({})", prog_id, clsid);
            }
            Err(e) => println!("⚠️  Erreur: {}", e),
        }
    }

    #[test]
    fn test_find_bscial_versions() {
        match find_bscial_versions() {
            Ok(versions) => {
                println!("✅ Versions BSCial trouvées:");
                for (prog_id, clsid) in versions {
                    println!("   - {} -> {}", prog_id, clsid);
                }
            }
            Err(e) => println!("⚠️  Aucune version BSCial trouvée: {}", e),
        }
    }

    #[test]
    fn test_find_latest_bscial() {
        match find_latest_bscial() {
            Ok((prog_id, clsid)) => {
                println!("✅ Dernière version BSCial: {} ({})", prog_id, clsid);
            }
            Err(e) => println!("⚠️  Erreur: {}", e),
        }
    }

    #[test]
    fn test_resolve_prog_id_bscpta() {
        // Test avec le ProgID connu de Sage 100c Comptabilité
        match resolve_prog_id("Sage.BSCpta.Application.100c") {
            Ok(clsid) => {
                println!("✅ CLSID BSCpta résolu: {}", clsid);
                assert_eq!(clsid, "309DE0FB-9FB8-4F4E-8295-CC60C60DAA33");
            }
            Err(e) => println!(
                "⚠️  ProgID BSCpta non trouvé (normal si Sage non installé): {}",
                e
            ),
        }
    }

    #[test]
    fn test_resolve_prog_id_bscial() {
        // Test avec le ProgID connu de Sage 100c Commercial
        match resolve_prog_id("Sage.BSCial.Application.100c") {
            Ok(clsid) => {
                println!("✅ CLSID BSCial résolu: {}", clsid);
            }
            Err(e) => println!(
                "⚠️  ProgID BSCial non trouvé (normal si Sage non installé): {}",
                e
            ),
        }
    }
}