1use std::path::Path;
6use std::str::FromStr;
7
8use serde::Deserialize;
9
10use crate::error::LspzError;
11use crate::metrics::MetricsConfig;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum OutputFormat {
17 Json,
19 Toon,
21 Passthrough,
23}
24
25impl FromStr for OutputFormat {
26 type Err = LspzError;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 match s.trim().to_lowercase().as_str() {
30 "json" => Ok(Self::Json),
31 "toon" => Ok(Self::Toon),
32 "passthrough" => Ok(Self::Passthrough),
33 _ => Err(LspzError::Config(format!("unknown output format: {s}"))),
34 }
35 }
36}
37
38#[derive(Debug, Clone, Default, Deserialize)]
42pub struct CappingConfig {
43 pub max_diags: usize,
45 pub max_completions: usize,
47 pub max_symbols: usize,
49}
50
51impl CappingConfig {
52 pub fn any_enabled(&self) -> bool {
54 self.max_diags > 0 || self.max_completions > 0 || self.max_symbols > 0
55 }
56}
57
58#[derive(Debug, Clone, Deserialize)]
60pub struct Config {
61 pub backend_cmd: String,
63 #[serde(default)]
65 pub capping: CappingConfig,
66 #[serde(default = "default_true")]
68 pub enable_diag_compress: bool,
69 #[serde(default = "default_true")]
71 pub enable_completion_compress: bool,
72 #[serde(default = "default_true")]
74 pub enable_hover_compress: bool,
75 #[serde(default = "default_true")]
77 pub enable_document_symbol_compress: bool,
78 #[serde(default = "default_true")]
80 pub enable_location_compress: bool,
81 #[serde(default = "default_true")]
83 pub enable_workspace_symbol_compress: bool,
84 #[serde(default = "default_true")]
86 pub enable_workspace_diag_compress: bool,
87 #[serde(default = "default_output_format")]
89 pub output_format: OutputFormat,
90 #[serde(default = "default_log_level")]
92 pub log_level: String,
93 #[serde(default)]
95 pub metrics: MetricsConfig,
96}
97
98fn default_true() -> bool {
99 true
100}
101
102fn default_output_format() -> OutputFormat {
103 OutputFormat::Toon
104}
105
106fn default_log_level() -> String {
107 "info".into()
108}
109
110impl Default for Config {
111 fn default() -> Self {
112 Self {
113 backend_cmd: String::new(),
114 capping: CappingConfig::default(),
115 enable_diag_compress: true,
116 enable_completion_compress: true,
117 enable_hover_compress: true,
118 enable_document_symbol_compress: true,
119 enable_location_compress: true,
120 enable_workspace_symbol_compress: true,
121 enable_workspace_diag_compress: true,
122 output_format: default_output_format(),
123 log_level: "info".into(),
124 metrics: MetricsConfig::default(),
125 }
126 }
127}
128
129impl Config {
130 pub fn builder() -> ConfigBuilder {
132 ConfigBuilder::default()
133 }
134
135 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, LspzError> {
139 let content = std::fs::read_to_string(path.as_ref())
140 .map_err(|e| LspzError::Config(format!("cannot read config file: {e}")))?;
141 toml::from_str(&content).map_err(|e| LspzError::Config(format!("invalid config file: {e}")))
142 }
143
144 pub fn is_interceptor_enabled(&self, name: &str) -> bool {
149 match name {
150 "capping" => self.capping.any_enabled(),
151 "diagnostics_compressor" => self.enable_diag_compress,
152 "completion_compressor" => self.enable_completion_compress,
153 "hover_compressor" => self.enable_hover_compress,
154 "document_symbol_compressor" => self.enable_document_symbol_compress,
155 "location_compressor" => self.enable_location_compress,
156 "workspace_symbol_compressor" => self.enable_workspace_symbol_compress,
157 "workspace_diagnostic_compressor" => self.enable_workspace_diag_compress,
158 _ => true,
159 }
160 }
161}
162
163#[derive(Debug, Default)]
165pub struct ConfigBuilder {
166 backend_cmd: Option<String>,
167 capping: Option<CappingConfig>,
168 enable_diag_compress: Option<bool>,
169 enable_completion_compress: Option<bool>,
170 enable_hover_compress: Option<bool>,
171 enable_document_symbol_compress: Option<bool>,
172 enable_location_compress: Option<bool>,
173 enable_workspace_symbol_compress: Option<bool>,
174 enable_workspace_diag_compress: Option<bool>,
175 output_format: Option<OutputFormat>,
176 log_level: Option<String>,
177 metrics: Option<MetricsConfig>,
178}
179
180impl ConfigBuilder {
181 pub fn backend_cmd(mut self, cmd: impl Into<String>) -> Self {
183 self.backend_cmd = Some(cmd.into());
184 self
185 }
186
187 pub fn enable_diag_compress(mut self, enable: bool) -> Self {
189 self.enable_diag_compress = Some(enable);
190 self
191 }
192
193 pub fn enable_completion_compress(mut self, enable: bool) -> Self {
195 self.enable_completion_compress = Some(enable);
196 self
197 }
198
199 pub fn enable_hover_compress(mut self, enable: bool) -> Self {
201 self.enable_hover_compress = Some(enable);
202 self
203 }
204
205 pub fn enable_document_symbol_compress(mut self, enable: bool) -> Self {
207 self.enable_document_symbol_compress = Some(enable);
208 self
209 }
210
211 pub fn enable_location_compress(mut self, enable: bool) -> Self {
213 self.enable_location_compress = Some(enable);
214 self
215 }
216
217 pub fn enable_workspace_symbol_compress(mut self, enable: bool) -> Self {
219 self.enable_workspace_symbol_compress = Some(enable);
220 self
221 }
222
223 pub fn enable_workspace_diag_compress(mut self, enable: bool) -> Self {
225 self.enable_workspace_diag_compress = Some(enable);
226 self
227 }
228
229 pub fn output_format(mut self, fmt: OutputFormat) -> Self {
231 self.output_format = Some(fmt);
232 self
233 }
234
235 pub fn log_level(mut self, level: impl Into<String>) -> Self {
237 self.log_level = Some(level.into());
238 self
239 }
240
241 pub fn capping(mut self, capping: CappingConfig) -> Self {
243 self.capping = Some(capping);
244 self
245 }
246
247 pub fn metrics(mut self, metrics: MetricsConfig) -> Self {
249 self.metrics = Some(metrics);
250 self
251 }
252
253 pub fn build(self) -> Result<Config, LspzError> {
255 let backend_cmd = self
256 .backend_cmd
257 .or_else(|| std::env::var("LSPZ_BACKEND_CMD").ok())
258 .ok_or_else(|| LspzError::Config("backend_cmd is required".into()))?;
259
260 let enable_diag_compress =
261 resolve_bool_flag(self.enable_diag_compress, "LSPZ_ENABLE_DIAG_COMPRESS", true);
262 let enable_completion_compress = resolve_bool_flag(
263 self.enable_completion_compress,
264 "LSPZ_ENABLE_COMPLETION_COMPRESS",
265 true,
266 );
267 let enable_hover_compress = resolve_bool_flag(
268 self.enable_hover_compress,
269 "LSPZ_ENABLE_HOVER_COMPRESS",
270 true,
271 );
272 let enable_document_symbol_compress = resolve_bool_flag(
273 self.enable_document_symbol_compress,
274 "LSPZ_ENABLE_DOCUMENT_SYMBOL_COMPRESS",
275 true,
276 );
277 let enable_location_compress = resolve_bool_flag(
278 self.enable_location_compress,
279 "LSPZ_ENABLE_LOCATION_COMPRESS",
280 true,
281 );
282 let enable_workspace_symbol_compress = resolve_bool_flag(
283 self.enable_workspace_symbol_compress,
284 "LSPZ_ENABLE_WORKSPACE_SYMBOL_COMPRESS",
285 true,
286 );
287 let enable_workspace_diag_compress = resolve_bool_flag(
288 self.enable_workspace_diag_compress,
289 "LSPZ_ENABLE_WORKSPACE_DIAG_COMPRESS",
290 true,
291 );
292
293 let log_level = self
294 .log_level
295 .or_else(|| std::env::var("LSPZ_LOG_LEVEL").ok())
296 .unwrap_or_else(|| "info".into());
297
298 let output_format = self
299 .output_format
300 .or_else(|| {
301 std::env::var("LSPZ_OUTPUT_FORMAT")
302 .ok()
303 .and_then(|v| OutputFormat::from_str(&v).ok())
304 })
305 .unwrap_or(OutputFormat::Toon);
306
307 let metrics = self.metrics.unwrap_or_else(|| MetricsConfig {
309 enabled: std::env::var("LSPZ_METRICS_ENABLED")
310 .ok()
311 .and_then(|v| v.parse().ok())
312 .unwrap_or(false),
313 report_interval_secs: std::env::var("LSPZ_METRICS_INTERVAL")
314 .ok()
315 .and_then(|v| v.parse().ok())
316 .unwrap_or(0),
317 });
318
319 let capping = self.capping.unwrap_or_else(|| CappingConfig {
321 max_diags: std::env::var("LSPZ_MAX_DIAGS")
322 .ok()
323 .and_then(|v| v.parse().ok())
324 .unwrap_or(0),
325 max_completions: std::env::var("LSPZ_MAX_COMPLETIONS")
326 .ok()
327 .and_then(|v| v.parse().ok())
328 .unwrap_or(0),
329 max_symbols: std::env::var("LSPZ_MAX_SYMBOLS")
330 .ok()
331 .and_then(|v| v.parse().ok())
332 .unwrap_or(0),
333 });
334
335 Ok(Config {
336 backend_cmd,
337 capping,
338 enable_diag_compress,
339 enable_completion_compress,
340 enable_hover_compress,
341 enable_document_symbol_compress,
342 enable_location_compress,
343 enable_workspace_symbol_compress,
344 enable_workspace_diag_compress,
345 output_format,
346 log_level,
347 metrics,
348 })
349 }
350}
351
352fn resolve_bool_flag(builder_val: Option<bool>, env_var: &str, default: bool) -> bool {
354 builder_val
355 .or_else(|| std::env::var(env_var).ok().and_then(|v| v.parse().ok()))
356 .unwrap_or(default)
357}