1use super::types::*;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConfigPreset {
14 Default,
16 Security,
18 CiCd,
20 Permissive,
22 Strict,
24}
25
26impl ConfigPreset {
27 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 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 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 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
81impl AppConfig {
86 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 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 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 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 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
224pub const DEFAULT_MATCHING_THRESHOLD: f64 = 0.8;
230
231pub const DEFAULT_CLUSTER_THRESHOLD: f64 = 0.7;
233
234pub const DEFAULT_ENRICHMENT_CACHE_TTL: u64 = 3600;
236
237pub const DEFAULT_ENRICHMENT_MAX_CONCURRENT: usize = 10;
239
240#[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}