1use super::types::{AppConfig, MatchingConfig, OutputConfig, FilterConfig, BehaviorConfig, GraphAwareDiffConfig, MatchingRulesPathConfig, EcosystemRulesConfig, TuiConfig, EnrichmentConfig};
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 #[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 #[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 #[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 #[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
85impl AppConfig {
90 #[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 #[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 #[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 #[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 #[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
234pub const DEFAULT_MATCHING_THRESHOLD: f64 = 0.8;
240
241pub const DEFAULT_CLUSTER_THRESHOLD: f64 = 0.7;
243
244pub const DEFAULT_ENRICHMENT_CACHE_TTL: u64 = 3600;
246
247pub const DEFAULT_ENRICHMENT_MAX_CONCURRENT: usize = 10;
249
250#[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}