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