Skip to main content

rez_lsp_server/config/
provider.rs

1//! Configuration provider implementation.
2
3use async_trait::async_trait;
4use std::env;
5use std::path::PathBuf;
6use tracing::{debug, info, warn};
7
8use super::Config;
9use crate::core::{ConfigError, ConfigProvider, Result};
10
11/// Implementation of ConfigProvider that reads from environment variables.
12#[derive(Debug)]
13pub struct RezConfigProvider {
14    config: Config,
15}
16
17impl RezConfigProvider {
18    /// Create a new configuration provider.
19    pub fn new() -> Self {
20        Self {
21            config: Config::new(),
22        }
23    }
24
25    /// Load configuration from environment variables.
26    ///
27    /// This method reads Rez configuration from standard environment variables:
28    /// - `REZ_PACKAGES_PATH`: Colon/semicolon-separated list of package directories
29    /// - `REZ_LOCAL_PACKAGES_PATH`: Local packages directory (highest priority)
30    /// - `REZ_RELEASE_PACKAGES_PATH`: Release packages directory (lowest priority)
31    /// - `REZ_LSP_DEBUG`: Enable debug logging (true/1)
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if environment variables contain invalid paths or values.
36    pub async fn load_from_environment(&mut self) -> Result<()> {
37        info!("Loading Rez configuration from environment");
38
39        self.config.packages_path = self.get_packages_path_from_env().await?;
40        self.config.local_packages_path = self.get_local_packages_path_from_env().await?;
41        self.config.release_packages_path = self.get_release_packages_path_from_env().await?;
42        self.config.debug_logging = self.get_debug_logging_from_env().await;
43
44        debug!("Packages path: {:?}", self.config.packages_path);
45        debug!("Local packages path: {:?}", self.config.local_packages_path);
46        debug!(
47            "Release packages path: {:?}",
48            self.config.release_packages_path
49        );
50
51        Ok(())
52    }
53
54    /// Get the current configuration.
55    pub fn config(&self) -> &Config {
56        &self.config
57    }
58
59    /// Get REZ_PACKAGES_PATH from environment.
60    async fn get_packages_path_from_env(&self) -> Result<Vec<PathBuf>> {
61        match env::var("REZ_PACKAGES_PATH") {
62            Ok(path_str) => {
63                let paths: Vec<PathBuf> = path_str
64                    .split(if cfg!(windows) { ';' } else { ':' })
65                    .filter(|s| !s.is_empty())
66                    .map(PathBuf::from)
67                    .collect();
68
69                if paths.is_empty() {
70                    warn!("REZ_PACKAGES_PATH is empty, using default paths");
71                    Ok(self.get_default_packages_path())
72                } else {
73                    info!("Found {} package paths in REZ_PACKAGES_PATH", paths.len());
74                    Ok(paths)
75                }
76            }
77            Err(_) => {
78                warn!("REZ_PACKAGES_PATH not set, using default paths");
79                Ok(self.get_default_packages_path())
80            }
81        }
82    }
83
84    /// Get default package paths when REZ_PACKAGES_PATH is not set.
85    fn get_default_packages_path(&self) -> Vec<PathBuf> {
86        let mut paths = Vec::new();
87
88        // Add user's home packages directory
89        if let Some(home_dir) = dirs::home_dir() {
90            paths.push(home_dir.join("packages"));
91        }
92
93        // Add common system paths
94        if cfg!(windows) {
95            paths.push(PathBuf::from("C:\\rez\\packages"));
96        } else {
97            paths.push(PathBuf::from("/opt/rez/packages"));
98            paths.push(PathBuf::from("/usr/local/rez/packages"));
99        }
100
101        paths
102    }
103
104    /// Get REZ_LOCAL_PACKAGES_PATH from environment.
105    async fn get_local_packages_path_from_env(&self) -> Result<Option<PathBuf>> {
106        Ok(env::var("REZ_LOCAL_PACKAGES_PATH").ok().map(PathBuf::from))
107    }
108
109    /// Get REZ_RELEASE_PACKAGES_PATH from environment.
110    async fn get_release_packages_path_from_env(&self) -> Result<Option<PathBuf>> {
111        Ok(env::var("REZ_RELEASE_PACKAGES_PATH")
112            .ok()
113            .map(PathBuf::from))
114    }
115
116    /// Get debug logging setting from environment.
117    async fn get_debug_logging_from_env(&self) -> bool {
118        env::var("REZ_LSP_DEBUG")
119            .map(|v| v.to_lowercase() == "true" || v == "1")
120            .unwrap_or(false)
121    }
122}
123
124#[async_trait]
125impl ConfigProvider for RezConfigProvider {
126    async fn get_package_paths(&self) -> Result<Vec<PathBuf>> {
127        Ok(self.config.packages_path.clone())
128    }
129
130    async fn get_local_packages_path(&self) -> Result<Option<PathBuf>> {
131        Ok(self.config.local_packages_path.clone())
132    }
133
134    async fn get_release_packages_path(&self) -> Result<Option<PathBuf>> {
135        Ok(self.config.release_packages_path.clone())
136    }
137
138    async fn validate(&self) -> Result<()> {
139        self.config.validate()?;
140
141        let mut valid_paths = 0;
142        for path in &self.config.packages_path {
143            if path.exists() && path.is_dir() {
144                valid_paths += 1;
145            } else {
146                warn!(
147                    "Package path does not exist or is not a directory: {:?}",
148                    path
149                );
150            }
151        }
152
153        if valid_paths == 0 {
154            return Err(ConfigError::NoValidPaths.into());
155        }
156
157        info!(
158            "Rez configuration validated: {}/{} paths are valid",
159            valid_paths,
160            self.config.packages_path.len()
161        );
162        Ok(())
163    }
164
165    async fn reload(&mut self) -> Result<()> {
166        self.load_from_environment().await
167    }
168}
169
170impl Default for RezConfigProvider {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use std::env;
180
181    #[tokio::test]
182    async fn test_config_provider_creation() {
183        let provider = RezConfigProvider::new();
184        assert!(provider.config.packages_path.is_empty());
185    }
186
187    #[tokio::test]
188    async fn test_default_packages_path() {
189        let provider = RezConfigProvider::new();
190        let paths = provider.get_default_packages_path();
191        assert!(!paths.is_empty());
192    }
193
194    #[tokio::test]
195    async fn test_packages_path_parsing() {
196        // Test Unix-style path
197        env::set_var("REZ_PACKAGES_PATH", "/path1:/path2:/path3");
198        let mut provider = RezConfigProvider::new();
199        provider.load_from_environment().await.unwrap();
200
201        if !cfg!(windows) {
202            assert_eq!(provider.config.packages_path.len(), 3);
203            assert_eq!(provider.config.packages_path[0], PathBuf::from("/path1"));
204            assert_eq!(provider.config.packages_path[1], PathBuf::from("/path2"));
205            assert_eq!(provider.config.packages_path[2], PathBuf::from("/path3"));
206        }
207
208        env::remove_var("REZ_PACKAGES_PATH");
209    }
210}