1use super::types::{
6 AppConfig, BehaviorConfig, EcosystemRulesConfig, EnrichmentConfig, FilterConfig,
7 GraphAwareDiffConfig, MatchingConfig, MatchingRulesPathConfig, OutputConfig, TuiConfig,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ConfigPreset {
17 Default,
19 Security,
21 CiCd,
23 Permissive,
25 Strict,
27}
28
29impl ConfigPreset {
30 #[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 #[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 #[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 #[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
86impl AppConfig {
91 #[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 #[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 #[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 #[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 #[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
244pub const DEFAULT_MATCHING_THRESHOLD: f64 = 0.8;
250
251pub const DEFAULT_CLUSTER_THRESHOLD: f64 = 0.7;
253
254pub const DEFAULT_ENRICHMENT_CACHE_TTL: u64 = 3600;
256
257pub const DEFAULT_ENRICHMENT_MAX_CONCURRENT: usize = 10;
259
260#[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}