Skip to main content

sbom_tools/config/
defaults.rs

1//! Default configurations and presets for sbom-tools.
2//!
3//! Provides named presets for common use cases and default values.
4
5use super::types::{AppConfig, MatchingConfig, OutputConfig, FilterConfig, BehaviorConfig, GraphAwareDiffConfig, MatchingRulesPathConfig, EcosystemRulesConfig, TuiConfig, EnrichmentConfig};
6
7// ============================================================================
8// Configuration Presets
9// ============================================================================
10
11/// Named configuration presets for common use cases.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConfigPreset {
14    /// Default balanced settings suitable for most cases
15    Default,
16    /// Security-focused: strict matching, fail on vulnerabilities
17    Security,
18    /// CI/CD: machine-readable output, fail on changes
19    CiCd,
20    /// Permissive: loose matching for messy SBOMs
21    Permissive,
22    /// Strict: exact matching for well-maintained SBOMs
23    Strict,
24}
25
26impl ConfigPreset {
27    /// Get the preset name as a string.
28    #[must_use] 
29    pub const fn name(&self) -> &'static str {
30        match self {
31            Self::Default => "default",
32            Self::Security => "security",
33            Self::CiCd => "ci-cd",
34            Self::Permissive => "permissive",
35            Self::Strict => "strict",
36        }
37    }
38
39    /// Parse a preset from a string name.
40    #[must_use] 
41    pub fn from_name(name: &str) -> Option<Self> {
42        match name.to_lowercase().as_str() {
43            "default" | "balanced" => Some(Self::Default),
44            "security" | "security-focused" => Some(Self::Security),
45            "ci-cd" | "ci" | "cd" | "pipeline" => Some(Self::CiCd),
46            "permissive" | "loose" => Some(Self::Permissive),
47            "strict" | "exact" => Some(Self::Strict),
48            _ => None,
49        }
50    }
51
52    /// Get a description of this preset.
53    #[must_use] 
54    pub const fn description(&self) -> &'static str {
55        match self {
56            Self::Default => "Balanced settings suitable for most SBOM comparisons",
57            Self::Security => {
58                "Strict matching with vulnerability detection and CI failure modes"
59            }
60            Self::CiCd => "Machine-readable output optimized for CI/CD pipelines",
61            Self::Permissive => "Loose matching for SBOMs with inconsistent naming",
62            Self::Strict => "Exact matching for well-maintained, consistent SBOMs",
63        }
64    }
65
66    /// Get all available presets.
67    #[must_use] 
68    pub const fn all() -> &'static [Self] {
69        &[
70            Self::Default,
71            Self::Security,
72            Self::CiCd,
73            Self::Permissive,
74            Self::Strict,
75        ]
76    }
77}
78
79impl std::fmt::Display for ConfigPreset {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.name())
82    }
83}
84
85// ============================================================================
86// Preset Implementations
87// ============================================================================
88
89impl AppConfig {
90    /// Create an `AppConfig` from a named preset.
91    #[must_use] 
92    pub fn from_preset(preset: ConfigPreset) -> Self {
93        match preset {
94            ConfigPreset::Default => Self::default(),
95            ConfigPreset::Security => Self::security_preset(),
96            ConfigPreset::CiCd => Self::ci_cd_preset(),
97            ConfigPreset::Permissive => Self::permissive_preset(),
98            ConfigPreset::Strict => Self::strict_preset(),
99        }
100    }
101
102    /// Security-focused preset.
103    ///
104    /// - Strict matching to avoid false negatives
105    /// - Fail on new vulnerabilities
106    /// - Enable typosquat detection
107    #[must_use] 
108    pub fn security_preset() -> Self {
109        Self {
110            matching: MatchingConfig {
111                fuzzy_preset: "strict".to_string(),
112                threshold: Some(0.9),
113                include_unchanged: false,
114            },
115            output: OutputConfig::default(),
116            filtering: FilterConfig::default(),
117            behavior: BehaviorConfig {
118                fail_on_vuln: true,
119                fail_on_change: false,
120                quiet: false,
121                explain_matches: false,
122                recommend_threshold: false,
123            },
124            graph_diff: GraphAwareDiffConfig::enabled(),
125            rules: MatchingRulesPathConfig::default(),
126            ecosystem_rules: EcosystemRulesConfig {
127                config_file: None,
128                disabled: false,
129                detect_typosquats: true,
130            },
131            tui: TuiConfig::default(),
132            enrichment: Some(EnrichmentConfig::default()),
133        }
134    }
135
136    /// CI/CD pipeline preset.
137    ///
138    /// - JSON output for machine parsing
139    /// - Fail on any changes
140    /// - Quiet mode to reduce noise
141    #[must_use] 
142    pub fn ci_cd_preset() -> Self {
143        use crate::reports::ReportFormat;
144
145        Self {
146            matching: MatchingConfig {
147                fuzzy_preset: "balanced".to_string(),
148                threshold: None,
149                include_unchanged: false,
150            },
151            output: OutputConfig {
152                format: ReportFormat::Json,
153                file: None,
154                report_types: crate::reports::ReportType::All,
155                no_color: true,
156                streaming: super::types::StreamingConfig::default(),
157                export_template: None,
158            },
159            filtering: FilterConfig {
160                only_changes: true,
161                min_severity: None,
162                exclude_vex_resolved: false,
163            },
164            behavior: BehaviorConfig {
165                fail_on_vuln: true,
166                fail_on_change: true,
167                quiet: true,
168                explain_matches: false,
169                recommend_threshold: false,
170            },
171            graph_diff: GraphAwareDiffConfig::enabled(),
172            rules: MatchingRulesPathConfig::default(),
173            ecosystem_rules: EcosystemRulesConfig::default(),
174            tui: TuiConfig::default(),
175            enrichment: Some(EnrichmentConfig::default()),
176        }
177    }
178
179    /// Permissive preset for messy SBOMs.
180    ///
181    /// - Low matching threshold
182    /// - Include unchanged for full picture
183    /// - No fail modes
184    #[must_use] 
185    pub fn permissive_preset() -> Self {
186        Self {
187            matching: MatchingConfig {
188                fuzzy_preset: "permissive".to_string(),
189                threshold: Some(0.6),
190                include_unchanged: true,
191            },
192            output: OutputConfig::default(),
193            filtering: FilterConfig::default(),
194            behavior: BehaviorConfig::default(),
195            graph_diff: GraphAwareDiffConfig::default(),
196            rules: MatchingRulesPathConfig::default(),
197            ecosystem_rules: EcosystemRulesConfig::default(),
198            tui: TuiConfig::default(),
199            enrichment: None,
200        }
201    }
202
203    /// Strict preset for well-maintained SBOMs.
204    ///
205    /// - High matching threshold
206    /// - Graph-aware diffing
207    /// - Detailed explanations available
208    #[must_use] 
209    pub fn strict_preset() -> Self {
210        Self {
211            matching: MatchingConfig {
212                fuzzy_preset: "strict".to_string(),
213                threshold: Some(0.95),
214                include_unchanged: false,
215            },
216            output: OutputConfig::default(),
217            filtering: FilterConfig::default(),
218            behavior: BehaviorConfig {
219                fail_on_vuln: false,
220                fail_on_change: false,
221                quiet: false,
222                explain_matches: false,
223                recommend_threshold: false,
224            },
225            graph_diff: GraphAwareDiffConfig::enabled(),
226            rules: MatchingRulesPathConfig::default(),
227            ecosystem_rules: EcosystemRulesConfig::default(),
228            tui: TuiConfig::default(),
229            enrichment: None,
230        }
231    }
232}
233
234// ============================================================================
235// Default Value Constants
236// ============================================================================
237
238/// Default matching threshold.
239pub const DEFAULT_MATCHING_THRESHOLD: f64 = 0.8;
240
241/// Default cluster threshold for matrix comparisons.
242pub const DEFAULT_CLUSTER_THRESHOLD: f64 = 0.7;
243
244/// Default cache TTL for enrichment in seconds.
245pub const DEFAULT_ENRICHMENT_CACHE_TTL: u64 = 3600;
246
247/// Default max concurrent requests for enrichment.
248pub const DEFAULT_ENRICHMENT_MAX_CONCURRENT: usize = 10;
249
250// ============================================================================
251// Tests
252// ============================================================================
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_preset_names() {
260        assert_eq!(ConfigPreset::Default.name(), "default");
261        assert_eq!(ConfigPreset::Security.name(), "security");
262        assert_eq!(ConfigPreset::CiCd.name(), "ci-cd");
263    }
264
265    #[test]
266    fn test_preset_from_name() {
267        assert_eq!(
268            ConfigPreset::from_name("default"),
269            Some(ConfigPreset::Default)
270        );
271        assert_eq!(
272            ConfigPreset::from_name("security"),
273            Some(ConfigPreset::Security)
274        );
275        assert_eq!(
276            ConfigPreset::from_name("security-focused"),
277            Some(ConfigPreset::Security)
278        );
279        assert_eq!(ConfigPreset::from_name("ci-cd"), Some(ConfigPreset::CiCd));
280        assert_eq!(
281            ConfigPreset::from_name("pipeline"),
282            Some(ConfigPreset::CiCd)
283        );
284        assert_eq!(ConfigPreset::from_name("invalid"), None);
285    }
286
287    #[test]
288    fn test_security_preset() {
289        let config = AppConfig::security_preset();
290        assert_eq!(config.matching.fuzzy_preset, "strict");
291        assert!(config.behavior.fail_on_vuln);
292        assert!(config.ecosystem_rules.detect_typosquats);
293        assert!(config.enrichment.is_some());
294    }
295
296    #[test]
297    fn test_ci_cd_preset() {
298        let config = AppConfig::ci_cd_preset();
299        assert!(config.behavior.fail_on_vuln);
300        assert!(config.behavior.fail_on_change);
301        assert!(config.behavior.quiet);
302        assert!(config.output.no_color);
303    }
304
305    #[test]
306    fn test_permissive_preset() {
307        let config = AppConfig::permissive_preset();
308        assert_eq!(config.matching.fuzzy_preset, "permissive");
309        assert_eq!(config.matching.threshold, Some(0.6));
310        assert!(config.matching.include_unchanged);
311    }
312
313    #[test]
314    fn test_strict_preset() {
315        let config = AppConfig::strict_preset();
316        assert_eq!(config.matching.fuzzy_preset, "strict");
317        assert_eq!(config.matching.threshold, Some(0.95));
318        assert!(config.graph_diff.enabled);
319    }
320
321    #[test]
322    fn test_from_preset() {
323        let default = AppConfig::from_preset(ConfigPreset::Default);
324        let security = AppConfig::from_preset(ConfigPreset::Security);
325
326        assert_eq!(default.matching.fuzzy_preset, "balanced");
327        assert_eq!(security.matching.fuzzy_preset, "strict");
328    }
329
330    #[test]
331    fn test_all_presets() {
332        let all = ConfigPreset::all();
333        assert_eq!(all.len(), 5);
334    }
335}