sqry_core/config/
workspace.rs1use anyhow::{Result, bail};
6use serde::{Deserialize, Serialize};
7use std::env;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct WorkspaceConfig {
12 pub discovery_max_depth: usize,
26}
27
28impl Default for WorkspaceConfig {
29 fn default() -> Self {
30 Self {
31 discovery_max_depth: 100,
32 }
33 }
34}
35
36impl WorkspaceConfig {
37 const MIN_DISCOVERY_DEPTH: usize = 10;
39
40 const MAX_DISCOVERY_DEPTH: usize = 1000;
42
43 const ABSOLUTE_MAX_DISCOVERY_DEPTH: usize = 1000;
45
46 pub fn new(discovery_max_depth: usize) -> Result<Self> {
52 let config = Self {
53 discovery_max_depth,
54 };
55 config.validate()?;
56 Ok(config)
57 }
58
59 pub fn load_or_default() -> Result<Self> {
65 let mut config = Self::default();
66
67 if let Ok(depth_str) = env::var("SQRY_WORKSPACE_DISCOVERY_DEPTH") {
69 config.discovery_max_depth =
70 Self::parse_env_var(&depth_str, "SQRY_WORKSPACE_DISCOVERY_DEPTH")?;
71 }
72
73 config.validate()?;
74 Ok(config)
75 }
76
77 pub fn effective_discovery_depth(&self) -> Result<usize> {
88 if self.discovery_max_depth == 0 {
89 bail!("workspace.discovery_max_depth cannot be 0 (unlimited not allowed for safety)");
90 }
91
92 if self.discovery_max_depth < Self::MIN_DISCOVERY_DEPTH {
93 bail!(
94 "workspace.discovery_max_depth {} is below minimum {}",
95 self.discovery_max_depth,
96 Self::MIN_DISCOVERY_DEPTH
97 );
98 }
99
100 if self.discovery_max_depth > Self::MAX_DISCOVERY_DEPTH {
101 tracing::warn!(
102 "workspace.discovery_max_depth {} exceeds recommended maximum {}",
103 self.discovery_max_depth,
104 Self::MAX_DISCOVERY_DEPTH
105 );
106 }
107
108 if self.discovery_max_depth > Self::ABSOLUTE_MAX_DISCOVERY_DEPTH {
109 bail!(
110 "workspace.discovery_max_depth {} exceeds absolute hard cap {}",
111 self.discovery_max_depth,
112 Self::ABSOLUTE_MAX_DISCOVERY_DEPTH
113 );
114 }
115
116 Ok(self.discovery_max_depth)
117 }
118
119 fn validate(&self) -> Result<()> {
121 self.effective_discovery_depth()?;
123 Ok(())
124 }
125
126 fn parse_env_var(value: &str, var_name: &str) -> Result<usize> {
133 match value.parse::<usize>() {
134 Ok(parsed) => Ok(parsed),
135 Err(_) => bail!(
136 "Invalid value for {}: '{}'. Expected usize in range [{}, {}]",
137 var_name,
138 value,
139 Self::MIN_DISCOVERY_DEPTH,
140 Self::ABSOLUTE_MAX_DISCOVERY_DEPTH
141 ),
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_default_config() {
152 let config = WorkspaceConfig::default();
153 assert_eq!(config.discovery_max_depth, 100);
154 assert!(config.effective_discovery_depth().is_ok());
155 }
156
157 #[test]
158 fn test_new_with_valid_depth() {
159 let config = WorkspaceConfig::new(50).unwrap();
160 assert_eq!(config.effective_discovery_depth().unwrap(), 50);
161 }
162
163 #[test]
164 fn test_new_with_zero_depth_fails() {
165 let result = WorkspaceConfig::new(0);
166 assert!(result.is_err());
167 assert!(result.unwrap_err().to_string().contains("cannot be 0"));
168 }
169
170 #[test]
171 fn test_below_minimum_fails() {
172 let result = WorkspaceConfig::new(5);
173 assert!(result.is_err());
174 assert!(result.unwrap_err().to_string().contains("below minimum 10"));
175 }
176
177 #[test]
178 fn test_at_minimum_succeeds() {
179 let config = WorkspaceConfig::new(10).unwrap();
180 assert_eq!(config.effective_discovery_depth().unwrap(), 10);
181 }
182
183 #[test]
184 fn test_at_maximum_succeeds() {
185 let config = WorkspaceConfig::new(1000).unwrap();
186 assert_eq!(config.effective_discovery_depth().unwrap(), 1000);
187 }
188
189 #[test]
190 fn test_above_hard_cap_fails() {
191 let result = WorkspaceConfig::new(1001);
192 assert!(result.is_err());
193 assert!(
194 result
195 .unwrap_err()
196 .to_string()
197 .contains("exceeds absolute hard cap")
198 );
199 }
200
201 #[test]
202 fn test_parse_env_var_valid() {
203 let result = WorkspaceConfig::parse_env_var("50", "TEST_VAR");
204 assert_eq!(result.unwrap(), 50);
205 }
206
207 #[test]
208 fn test_parse_env_var_invalid() {
209 let result = WorkspaceConfig::parse_env_var("abc", "TEST_VAR");
210 assert!(result.is_err());
211 assert!(
212 result
213 .unwrap_err()
214 .to_string()
215 .contains("Invalid value for TEST_VAR")
216 );
217 }
218
219 #[test]
220 fn test_parse_env_var_negative() {
221 let result = WorkspaceConfig::parse_env_var("-10", "TEST_VAR");
222 assert!(result.is_err());
223 assert!(result.unwrap_err().to_string().contains("Invalid value"));
224 }
225
226 #[test]
227 fn test_effective_depth_warns_at_max() {
228 let config = WorkspaceConfig {
229 discovery_max_depth: 1000,
230 };
231 assert_eq!(config.effective_discovery_depth().unwrap(), 1000);
233 }
234}