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