cfgmatic-paths 0.1.3

Cross-platform configuration path discovery following XDG and platform conventions
Documentation
//! Windows directory finder.

use super::DirectoryFinder;
use crate::env::Env;
use std::path::PathBuf;

/// Windows directory finder using known folder IDs.
#[derive(Debug, Clone)]
pub struct WindowsDirectoryFinder {
    app_name: String,
    company_name: String,
}

impl WindowsDirectoryFinder {
    /// Create a new finder for Windows.
    pub fn new(app_name: impl Into<String>, company_name: impl Into<String>) -> Self {
        Self {
            app_name: app_name.into(),
            company_name: company_name.into(),
        }
    }

    /// Get the AppData roaming path.
    fn appdata_roaming(&self, env: &dyn Env) -> PathBuf {
        env.get("APPDATA")
            .map(PathBuf::from)
            .unwrap_or_else(|| {
                // Fallback: use USERPROFILE
                env.home_dir()
                    .map(|h| h.join("AppData").join("Roaming"))
                    .unwrap_or_else(|| PathBuf::from("C:\\"))
            })
            .join(&self.company_name)
            .join(&self.app_name)
    }

    /// Get the AppData local path.
    fn appdata_local(&self, env: &dyn Env) -> PathBuf {
        env.get("LOCALAPPDATA")
            .map(PathBuf::from)
            .unwrap_or_else(|| {
                env.home_dir()
                    .map(|h| h.join("AppData").join("Local"))
                    .unwrap_or_else(|| PathBuf::from("C:\\"))
            })
            .join(&self.company_name)
            .join(&self.app_name)
    }

    /// Get the ProgramData path.
    fn program_data(&self, env: &dyn Env) -> PathBuf {
        env.get("PROGRAMDATA")
            .map(PathBuf::from)
            .unwrap_or_else(|| PathBuf::from("C:\\ProgramData"))
            .join(&self.company_name)
            .join(&self.app_name)
    }
}

impl DirectoryFinder for WindowsDirectoryFinder {
    fn user_dirs(&self, env: &dyn Env) -> Vec<PathBuf> {
        vec![
            // Roaming AppData (synced across domain)
            self.appdata_roaming(env),
            // Local AppData (machine-specific)
            self.appdata_local(env),
        ]
    }

    fn local_dirs(&self, _env: &dyn Env) -> Vec<PathBuf> {
        // On Windows, "local" typically means per-machine
        // We could include LOCALAPPDATA here, but it's already in user_dirs
        Vec::new()
    }

    fn system_dirs(&self, env: &dyn Env) -> Vec<PathBuf> {
        vec![
            // ProgramData (all users)
            self.program_data(env),
            // Legacy: Program Files\Common Files
            PathBuf::from("C:\\Program Files\\Common Files")
                .join(&self.company_name)
                .join(&self.app_name),
        ]
    }
}

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

    struct TestEnv {
        vars: std::collections::HashMap<String, String>,
        home: PathBuf,
    }

    impl TestEnv {
        fn new() -> Self {
            Self {
                vars: std::collections::HashMap::new(),
                home: PathBuf::from("C:\\Users\\TestUser"),
            }
        }

        fn with_var(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
            self.vars.insert(key.into(), value.into());
            self
        }
    }

    impl Env for TestEnv {
        fn get(&self, key: &str) -> Option<String> {
            self.vars.get(key).cloned()
        }

        fn home_dir(&self) -> Option<PathBuf> {
            Some(self.home.clone())
        }
    }

    #[test]
    fn test_user_dirs_with_appdata() {
        let env = TestEnv::new().with_var("APPDATA", "C:\\Users\\TestUser\\AppData\\Roaming");
        let finder = WindowsDirectoryFinder::new("MyApp", "MyCompany");
        let dirs = finder.user_dirs(&env);

        assert_eq!(
            dirs[0],
            PathBuf::from("C:\\Users\\TestUser\\AppData\\Roaming\\MyCompany\\MyApp")
        );
    }

    #[test]
    fn test_system_dirs() {
        let env = TestEnv::new().with_var("PROGRAMDATA", "C:\\ProgramData");
        let finder = WindowsDirectoryFinder::new("MyApp", "MyCompany");
        let dirs = finder.system_dirs(&env);

        assert_eq!(dirs[0], PathBuf::from("C:\\ProgramData\\MyCompany\\MyApp"));
    }
}