Skip to main content

valknut_rs/api/
config_types.rs

1//! Simplified configuration types for the public API.
2//!
3//! This module provides a clean, unified configuration interface that eliminates
4//! complexity and duplication while maintaining backward compatibility.
5
6use crate::core::config::{validate_unit_range, ValknutConfig};
7use crate::core::errors::{Result, ValknutError};
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11/// Unified analysis configuration for the public API
12///
13/// This is the main configuration interface for users. It provides a clean,
14/// composable API that automatically handles internal configuration complexity.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct AnalysisConfig {
17    /// Analysis modules to enable
18    pub modules: AnalysisModules,
19
20    /// Language-specific settings
21    pub languages: LanguageSettings,
22
23    /// File discovery and filtering
24    pub files: FileSettings,
25
26    /// Quality thresholds and limits
27    pub quality: QualitySettings,
28
29    /// Coverage analysis configuration
30    pub coverage: CoverageSettings,
31}
32
33/// Analysis modules that can be enabled/disabled
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct AnalysisModules {
36    /// Enable complexity and scoring analysis
37    pub complexity: bool,
38
39    /// Enable dependency graph analysis
40    pub dependencies: bool,
41
42    /// Enable duplicate code detection
43    pub duplicates: bool,
44
45    /// Enable refactoring opportunity detection
46    pub refactoring: bool,
47
48    /// Enable code structure analysis
49    pub structure: bool,
50
51    /// Enable code coverage analysis
52    pub coverage: bool,
53}
54
55/// Language configuration for analysis
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct LanguageSettings {
58    /// Languages to analyze (if empty, auto-detect from files)
59    pub enabled: Vec<String>,
60
61    /// Maximum file size per language (in MB)
62    pub max_file_size_mb: Option<f64>,
63
64    /// Language-specific complexity thresholds
65    pub complexity_thresholds: std::collections::HashMap<String, f64>,
66}
67
68/// File discovery and filtering settings
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct FileSettings {
71    /// Patterns to include in analysis
72    pub include_patterns: Vec<String>,
73
74    /// Patterns to exclude from analysis
75    pub exclude_patterns: Vec<String>,
76
77    /// Maximum number of files to analyze (None = unlimited)
78    pub max_files: Option<usize>,
79
80    /// Maximum file size in bytes (None = unlimited, default = 500KB)
81    /// Files larger than this are skipped during analysis
82    pub max_file_size_bytes: Option<u64>,
83
84    /// Follow symbolic links during file discovery
85    pub follow_symlinks: bool,
86}
87
88/// Quality thresholds and analysis limits
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct QualitySettings {
91    /// Minimum confidence threshold for results (0.0-1.0)
92    pub confidence_threshold: f64,
93
94    /// Maximum analysis time per file (seconds)
95    pub max_analysis_time_per_file: Option<u64>,
96
97    /// Enable strict validation mode
98    pub strict_mode: bool,
99}
100
101/// Coverage analysis configuration
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CoverageSettings {
104    /// Enable coverage analysis
105    pub enabled: bool,
106
107    /// Specific coverage file path (overrides auto discovery)
108    pub file_path: Option<PathBuf>,
109
110    /// Enable automatic coverage file discovery
111    pub auto_discover: bool,
112
113    /// Maximum age of coverage files in days (0 = no age limit)
114    pub max_age_days: u32,
115
116    /// Additional search paths for coverage files
117    pub search_paths: Vec<String>,
118}
119
120/// Default implementation for [`AnalysisConfig`].
121impl Default for AnalysisConfig {
122    /// Returns the default analysis configuration.
123    fn default() -> Self {
124        Self {
125            modules: AnalysisModules::default(),
126            languages: LanguageSettings::default(),
127            files: FileSettings::default(),
128            quality: QualitySettings::default(),
129            coverage: CoverageSettings::default(),
130        }
131    }
132}
133
134/// Default implementation for [`AnalysisModules`].
135impl Default for AnalysisModules {
136    /// Returns the default analysis modules configuration.
137    fn default() -> Self {
138        Self {
139            complexity: true,
140            dependencies: true,
141            duplicates: false,
142            refactoring: true,
143            structure: true,
144            coverage: true,
145        }
146    }
147}
148
149/// Default implementation for [`LanguageSettings`].
150impl Default for LanguageSettings {
151    /// Returns the default language settings.
152    fn default() -> Self {
153        Self {
154            enabled: vec![
155                "python".to_string(),
156                "javascript".to_string(),
157                "typescript".to_string(),
158            ],
159            max_file_size_mb: Some(10.0),
160            complexity_thresholds: [
161                ("python".to_string(), 10.0),
162                ("javascript".to_string(), 10.0),
163                ("typescript".to_string(), 10.0),
164                ("rust".to_string(), 15.0),
165                ("go".to_string(), 12.0),
166            ]
167            .iter()
168            .cloned()
169            .collect(),
170        }
171    }
172}
173
174/// Default implementation for [`FileSettings`].
175impl Default for FileSettings {
176    /// Returns the default file settings.
177    fn default() -> Self {
178        Self {
179            include_patterns: vec!["**/*".to_string()],
180            exclude_patterns: vec![
181                "*/node_modules/*".to_string(),
182                "*/venv/*".to_string(),
183                "*/target/*".to_string(),
184                "*/__pycache__/*".to_string(),
185                "*.min.js".to_string(),
186            ],
187            max_files: None,
188            max_file_size_bytes: Some(500 * 1024), // 500KB default
189            follow_symlinks: false,
190        }
191    }
192}
193
194/// Default implementation for [`QualitySettings`].
195impl Default for QualitySettings {
196    /// Returns the default quality settings.
197    fn default() -> Self {
198        Self {
199            confidence_threshold: 0.7,
200            max_analysis_time_per_file: Some(30),
201            strict_mode: false,
202        }
203    }
204}
205
206/// Default implementation for [`CoverageSettings`].
207impl Default for CoverageSettings {
208    /// Returns the default coverage settings.
209    fn default() -> Self {
210        Self {
211            enabled: true,
212            file_path: None,
213            auto_discover: true,
214            max_age_days: 7,
215            search_paths: vec![
216                "./coverage/".to_string(),
217                "./target/coverage/".to_string(),
218                "./target/tarpaulin/".to_string(),
219            ],
220        }
221    }
222}
223
224/// Constructor and fluent builder methods for [`AnalysisConfig`].
225impl AnalysisConfig {
226    /// Create a new analysis configuration
227    pub fn new() -> Self {
228        Self::default()
229    }
230
231    /// Enable/disable analysis modules with a fluent interface
232    pub fn modules(mut self, f: impl FnOnce(AnalysisModules) -> AnalysisModules) -> Self {
233        self.modules = f(self.modules);
234        self
235    }
236
237    /// Configure languages with a fluent interface
238    pub fn languages(mut self, f: impl FnOnce(LanguageSettings) -> LanguageSettings) -> Self {
239        self.languages = f(self.languages);
240        self
241    }
242
243    /// Configure file settings with a fluent interface
244    pub fn files(mut self, f: impl FnOnce(FileSettings) -> FileSettings) -> Self {
245        self.files = f(self.files);
246        self
247    }
248
249    /// Configure quality settings with a fluent interface
250    pub fn quality(mut self, f: impl FnOnce(QualitySettings) -> QualitySettings) -> Self {
251        self.quality = f(self.quality);
252        self
253    }
254
255    /// Configure coverage settings with a fluent interface
256    pub fn coverage(mut self, f: impl FnOnce(CoverageSettings) -> CoverageSettings) -> Self {
257        self.coverage = f(self.coverage);
258        self
259    }
260
261    // Convenience methods for common operations
262
263    /// Set the languages to analyze
264    pub fn with_languages(mut self, languages: Vec<String>) -> Self {
265        self.languages.enabled = languages;
266        self
267    }
268
269    /// Add a language to analyze
270    pub fn with_language(mut self, language: impl Into<String>) -> Self {
271        self.languages.enabled.push(language.into());
272        self
273    }
274
275    /// Set confidence threshold
276    pub fn with_confidence_threshold(mut self, threshold: f64) -> Self {
277        self.quality.confidence_threshold = threshold;
278        self
279    }
280
281    /// Set maximum number of files to analyze
282    pub fn with_max_files(mut self, max_files: usize) -> Self {
283        self.files.max_files = Some(max_files);
284        self
285    }
286
287    /// Add an exclusion pattern
288    pub fn exclude_pattern(mut self, pattern: impl Into<String>) -> Self {
289        self.files.exclude_patterns.push(pattern.into());
290        self
291    }
292
293    /// Add an inclusion pattern
294    pub fn include_pattern(mut self, pattern: impl Into<String>) -> Self {
295        self.files.include_patterns.push(pattern.into());
296        self
297    }
298
299    /// Enable all analysis modules
300    pub fn enable_all_modules(mut self) -> Self {
301        self.modules.complexity = true;
302        self.modules.dependencies = true;
303        self.modules.duplicates = true;
304        self.modules.refactoring = true;
305        self.modules.structure = true;
306        self.modules.coverage = true;
307        self
308    }
309
310    /// Disable all analysis modules (useful for selective enabling)
311    pub fn disable_all_modules(mut self) -> Self {
312        self.modules.complexity = false;
313        self.modules.dependencies = false;
314        self.modules.duplicates = false;
315        self.modules.refactoring = false;
316        self.modules.structure = false;
317        self.modules.coverage = false;
318        self
319    }
320
321    /// Enable only essential modules for fast analysis
322    pub fn essential_modules_only(mut self) -> Self {
323        self.modules.complexity = true;
324        self.modules.dependencies = false;
325        self.modules.duplicates = false;
326        self.modules.refactoring = false;
327        self.modules.structure = false;
328        self.modules.coverage = false;
329        self
330    }
331
332    /// Validate the configuration
333    pub fn validate(&self) -> Result<()> {
334        // Validate confidence threshold
335        validate_unit_range(self.quality.confidence_threshold, "confidence_threshold")?;
336
337        // Validate file limits
338        if let Some(max_files) = self.files.max_files {
339            if max_files == 0 {
340                return Err(ValknutError::validation(
341                    "max_files must be greater than 0 when specified",
342                ));
343            }
344        }
345
346        // Validate file size limits
347        if let Some(max_size) = self.languages.max_file_size_mb {
348            if max_size <= 0.0 {
349                return Err(ValknutError::validation(
350                    "max_file_size_mb must be positive when specified",
351                ));
352            }
353        }
354
355        // Validate coverage age
356        if self.coverage.enabled && self.coverage.max_age_days == 0 && self.coverage.auto_discover {
357            // This is actually fine - 0 means no age limit
358        }
359
360        // Validate that at least one module is enabled
361        let modules_enabled = [
362            self.modules.complexity,
363            self.modules.dependencies,
364            self.modules.duplicates,
365            self.modules.refactoring,
366            self.modules.structure,
367            self.modules.coverage,
368        ];
369        if !modules_enabled.iter().any(|&enabled| enabled) {
370            return Err(ValknutError::validation(
371                "At least one analysis module must be enabled",
372            ));
373        }
374
375        Ok(())
376    }
377
378    /// Convert to internal ValknutConfig
379    ///
380    /// This method handles the complexity of mapping the clean public API
381    /// to the detailed internal configuration structure.
382    pub fn to_valknut_config(self) -> ValknutConfig {
383        let mut config = ValknutConfig::default();
384
385        // Map analysis modules to internal flags
386        config.analysis.enable_scoring = self.modules.complexity;
387        config.analysis.enable_graph_analysis = self.modules.dependencies;
388        config.analysis.enable_lsh_analysis = self.modules.duplicates;
389        config.analysis.enable_refactoring_analysis = self.modules.refactoring;
390        config.analysis.enable_structure_analysis = self.modules.structure;
391        config.analysis.enable_coverage_analysis = self.modules.coverage;
392
393        // Map quality settings
394        config.analysis.confidence_threshold = self.quality.confidence_threshold;
395
396        // Map file settings
397        config.analysis.max_files = self.files.max_files.unwrap_or(0);
398        config.analysis.exclude_patterns = self.files.exclude_patterns;
399        config.analysis.include_patterns = self.files.include_patterns;
400
401        // Map coverage configuration
402        config.coverage.coverage_file = self.coverage.file_path;
403        config.coverage.auto_discover = self.coverage.auto_discover;
404        config.coverage.max_age_days = self.coverage.max_age_days;
405        config.coverage.search_paths = self.coverage.search_paths;
406
407        // Configure languages
408        for language in &self.languages.enabled {
409            if let Some(lang_config) = config.languages.get_mut(language) {
410                lang_config.enabled = true;
411
412                // Apply language-specific settings
413                if let Some(max_size) = self.languages.max_file_size_mb {
414                    lang_config.max_file_size_mb = max_size;
415                }
416
417                if let Some(&threshold) = self.languages.complexity_thresholds.get(language) {
418                    lang_config.complexity_threshold = threshold;
419                }
420            }
421        }
422
423        // Set performance configuration based on quality settings
424        if let Some(timeout) = self.quality.max_analysis_time_per_file {
425            config.performance.file_timeout_seconds = timeout;
426        }
427
428        config
429    }
430
431    /// Create from ValknutConfig
432    ///
433    /// This method handles the reverse conversion from the detailed internal
434    /// configuration to the simplified public API.
435    pub fn from_valknut_config(valknut_config: ValknutConfig) -> Result<Self> {
436        // Extract enabled languages and their settings
437        let enabled_languages: Vec<String> = valknut_config
438            .languages
439            .iter()
440            .filter_map(|(name, config)| {
441                if config.enabled {
442                    Some(name.clone())
443                } else {
444                    None
445                }
446            })
447            .collect();
448
449        // Extract complexity thresholds
450        let complexity_thresholds: std::collections::HashMap<String, f64> = valknut_config
451            .languages
452            .iter()
453            .filter(|(_, config)| config.enabled)
454            .map(|(name, config)| (name.clone(), config.complexity_threshold))
455            .collect();
456
457        // Extract file size limit (use first enabled language's limit)
458        let max_file_size_mb = valknut_config
459            .languages
460            .values()
461            .find(|config| config.enabled)
462            .map(|config| config.max_file_size_mb);
463
464        Ok(Self {
465            modules: AnalysisModules {
466                complexity: valknut_config.analysis.enable_scoring,
467                dependencies: valknut_config.analysis.enable_graph_analysis,
468                duplicates: valknut_config.analysis.enable_lsh_analysis,
469                refactoring: valknut_config.analysis.enable_refactoring_analysis,
470                structure: valknut_config.analysis.enable_structure_analysis,
471                coverage: valknut_config.analysis.enable_coverage_analysis,
472            },
473            languages: LanguageSettings {
474                enabled: enabled_languages,
475                max_file_size_mb,
476                complexity_thresholds,
477            },
478            files: FileSettings {
479                include_patterns: valknut_config.analysis.include_patterns,
480                exclude_patterns: valknut_config.analysis.exclude_patterns,
481                max_files: if valknut_config.analysis.max_files == 0 {
482                    None
483                } else {
484                    Some(valknut_config.analysis.max_files)
485                },
486                max_file_size_bytes: if valknut_config.analysis.max_file_size_bytes == 0 {
487                    None
488                } else {
489                    Some(valknut_config.analysis.max_file_size_bytes)
490                },
491                follow_symlinks: false, // Default value, not stored in ValknutConfig
492            },
493            quality: QualitySettings {
494                confidence_threshold: valknut_config.analysis.confidence_threshold,
495                max_analysis_time_per_file: Some(valknut_config.performance.file_timeout_seconds),
496                strict_mode: false, // Default value, not stored in ValknutConfig
497            },
498            coverage: CoverageSettings {
499                enabled: valknut_config.analysis.enable_coverage_analysis,
500                file_path: valknut_config.coverage.coverage_file,
501                auto_discover: valknut_config.coverage.auto_discover,
502                max_age_days: valknut_config.coverage.max_age_days,
503                search_paths: valknut_config.coverage.search_paths,
504            },
505        })
506    }
507}
508
509// Additional convenience implementations for the new config components
510
511/// Factory methods for [`AnalysisModules`] presets.
512impl AnalysisModules {
513    /// Creates a configuration with all analysis modules enabled.
514    pub fn all() -> Self {
515        Self {
516            complexity: true,
517            dependencies: true,
518            duplicates: true,
519            refactoring: true,
520            structure: true,
521            coverage: true,
522        }
523    }
524
525    /// Creates a configuration with only essential modules for fast analysis.
526    ///
527    /// Only enables complexity analysis, which provides basic code health metrics
528    /// with minimal overhead.
529    pub fn essential() -> Self {
530        Self {
531            complexity: true,
532            dependencies: false,
533            duplicates: false,
534            refactoring: false,
535            structure: false,
536            coverage: false,
537        }
538    }
539
540    /// Creates a configuration focused on code quality analysis.
541    ///
542    /// Enables complexity, duplicate detection, and refactoring modules.
543    pub fn code_quality() -> Self {
544        Self {
545            complexity: true,
546            dependencies: false,
547            duplicates: true,
548            refactoring: true,
549            structure: false,
550            coverage: false,
551        }
552    }
553}
554
555/// Builder methods for [`LanguageSettings`].
556impl LanguageSettings {
557    /// Add a language to the enabled list
558    pub fn add_language(mut self, language: impl Into<String>) -> Self {
559        self.enabled.push(language.into());
560        self
561    }
562
563    /// Set complexity threshold for a specific language
564    pub fn with_complexity_threshold(
565        mut self,
566        language: impl Into<String>,
567        threshold: f64,
568    ) -> Self {
569        self.complexity_thresholds
570            .insert(language.into(), threshold);
571        self
572    }
573
574    /// Set maximum file size
575    pub fn with_max_file_size_mb(mut self, size_mb: f64) -> Self {
576        self.max_file_size_mb = Some(size_mb);
577        self
578    }
579}
580
581/// Builder methods for [`FileSettings`].
582impl FileSettings {
583    /// Add multiple exclusion patterns
584    pub fn exclude_patterns(mut self, patterns: Vec<String>) -> Self {
585        self.exclude_patterns.extend(patterns);
586        self
587    }
588
589    /// Add multiple inclusion patterns
590    pub fn include_patterns(mut self, patterns: Vec<String>) -> Self {
591        self.include_patterns.extend(patterns);
592        self
593    }
594
595    /// Set maximum files to analyze
596    pub fn with_max_files(mut self, max_files: usize) -> Self {
597        self.max_files = Some(max_files);
598        self
599    }
600}
601
602/// Builder methods for [`QualitySettings`].
603impl QualitySettings {
604    /// Enable strict validation mode
605    pub fn strict(mut self) -> Self {
606        self.strict_mode = true;
607        self
608    }
609
610    /// Set analysis timeout per file
611    pub fn with_timeout(mut self, seconds: u64) -> Self {
612        self.max_analysis_time_per_file = Some(seconds);
613        self
614    }
615}
616
617/// Factory and builder methods for [`CoverageSettings`].
618impl CoverageSettings {
619    /// Disable coverage analysis
620    pub fn disabled() -> Self {
621        Self {
622            enabled: false,
623            ..Self::default()
624        }
625    }
626
627    /// Use a specific coverage file
628    pub fn with_file(mut self, path: PathBuf) -> Self {
629        self.file_path = Some(path);
630        self.auto_discover = false;
631        self
632    }
633
634    /// Add additional search paths
635    pub fn with_search_paths(mut self, paths: Vec<String>) -> Self {
636        self.search_paths.extend(paths);
637        self
638    }
639}
640
641
642#[cfg(test)]
643#[path = "config_types_tests.rs"]
644mod tests;