use super::types::{
AppConfig, BehaviorConfig, EcosystemRulesConfig, EnrichmentConfig, FilterConfig,
GraphAwareDiffConfig, MatchingConfig, MatchingRulesPathConfig, OutputConfig, TuiConfig,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigPreset {
Default,
Security,
CiCd,
Permissive,
Strict,
}
impl ConfigPreset {
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Default => "default",
Self::Security => "security",
Self::CiCd => "ci-cd",
Self::Permissive => "permissive",
Self::Strict => "strict",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"default" | "balanced" => Some(Self::Default),
"security" | "security-focused" => Some(Self::Security),
"ci-cd" | "ci" | "cd" | "pipeline" => Some(Self::CiCd),
"permissive" | "loose" => Some(Self::Permissive),
"strict" | "exact" => Some(Self::Strict),
_ => None,
}
}
#[must_use]
pub const fn description(&self) -> &'static str {
match self {
Self::Default => "Balanced settings suitable for most SBOM comparisons",
Self::Security => "Strict matching with vulnerability detection and CI failure modes",
Self::CiCd => "Machine-readable output optimized for CI/CD pipelines",
Self::Permissive => "Loose matching for SBOMs with inconsistent naming",
Self::Strict => "Exact matching for well-maintained, consistent SBOMs",
}
}
#[must_use]
pub const fn all() -> &'static [Self] {
&[
Self::Default,
Self::Security,
Self::CiCd,
Self::Permissive,
Self::Strict,
]
}
}
impl std::fmt::Display for ConfigPreset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl AppConfig {
#[must_use]
pub fn from_preset(preset: ConfigPreset) -> Self {
match preset {
ConfigPreset::Default => Self::default(),
ConfigPreset::Security => Self::security_preset(),
ConfigPreset::CiCd => Self::ci_cd_preset(),
ConfigPreset::Permissive => Self::permissive_preset(),
ConfigPreset::Strict => Self::strict_preset(),
}
}
#[must_use]
pub fn security_preset() -> Self {
Self {
matching: MatchingConfig {
fuzzy_preset: crate::config::FuzzyPreset::Strict,
threshold: Some(0.9),
include_unchanged: false,
},
output: OutputConfig::default(),
filtering: FilterConfig::default(),
behavior: BehaviorConfig {
fail_on_vuln: true,
fail_on_change: false,
quiet: false,
explain_matches: false,
recommend_threshold: false,
},
graph_diff: GraphAwareDiffConfig::enabled(),
rules: MatchingRulesPathConfig::default(),
ecosystem_rules: EcosystemRulesConfig {
config_file: None,
disabled: false,
detect_typosquats: true,
},
tui: TuiConfig::default(),
enrichment: Some(EnrichmentConfig::default()),
}
}
#[must_use]
pub fn ci_cd_preset() -> Self {
use crate::reports::ReportFormat;
Self {
matching: MatchingConfig {
fuzzy_preset: crate::config::FuzzyPreset::Balanced,
threshold: None,
include_unchanged: false,
},
output: OutputConfig {
format: ReportFormat::Json,
file: None,
report_types: crate::reports::ReportType::All,
no_color: true,
streaming: super::types::StreamingConfig::default(),
export_template: None,
},
filtering: FilterConfig {
only_changes: true,
min_severity: None,
exclude_vex_resolved: false,
fail_on_vex_gap: false,
},
behavior: BehaviorConfig {
fail_on_vuln: true,
fail_on_change: true,
quiet: true,
explain_matches: false,
recommend_threshold: false,
},
graph_diff: GraphAwareDiffConfig::enabled(),
rules: MatchingRulesPathConfig::default(),
ecosystem_rules: EcosystemRulesConfig::default(),
tui: TuiConfig::default(),
enrichment: Some(EnrichmentConfig::default()),
}
}
#[must_use]
pub fn permissive_preset() -> Self {
Self {
matching: MatchingConfig {
fuzzy_preset: crate::config::FuzzyPreset::Permissive,
threshold: Some(0.6),
include_unchanged: true,
},
output: OutputConfig::default(),
filtering: FilterConfig::default(),
behavior: BehaviorConfig::default(),
graph_diff: GraphAwareDiffConfig::default(),
rules: MatchingRulesPathConfig::default(),
ecosystem_rules: EcosystemRulesConfig::default(),
tui: TuiConfig::default(),
enrichment: None,
}
}
#[must_use]
pub fn strict_preset() -> Self {
Self {
matching: MatchingConfig {
fuzzy_preset: crate::config::FuzzyPreset::Strict,
threshold: Some(0.95),
include_unchanged: false,
},
output: OutputConfig::default(),
filtering: FilterConfig::default(),
behavior: BehaviorConfig {
fail_on_vuln: false,
fail_on_change: false,
quiet: false,
explain_matches: false,
recommend_threshold: false,
},
graph_diff: GraphAwareDiffConfig::enabled(),
rules: MatchingRulesPathConfig::default(),
ecosystem_rules: EcosystemRulesConfig::default(),
tui: TuiConfig::default(),
enrichment: None,
}
}
}
pub const DEFAULT_MATCHING_THRESHOLD: f64 = 0.8;
pub const DEFAULT_CLUSTER_THRESHOLD: f64 = 0.7;
pub const DEFAULT_ENRICHMENT_CACHE_TTL: u64 = 3600;
pub const DEFAULT_ENRICHMENT_MAX_CONCURRENT: usize = 10;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preset_names() {
assert_eq!(ConfigPreset::Default.name(), "default");
assert_eq!(ConfigPreset::Security.name(), "security");
assert_eq!(ConfigPreset::CiCd.name(), "ci-cd");
}
#[test]
fn test_preset_from_name() {
assert_eq!(
ConfigPreset::from_name("default"),
Some(ConfigPreset::Default)
);
assert_eq!(
ConfigPreset::from_name("security"),
Some(ConfigPreset::Security)
);
assert_eq!(
ConfigPreset::from_name("security-focused"),
Some(ConfigPreset::Security)
);
assert_eq!(ConfigPreset::from_name("ci-cd"), Some(ConfigPreset::CiCd));
assert_eq!(
ConfigPreset::from_name("pipeline"),
Some(ConfigPreset::CiCd)
);
assert_eq!(ConfigPreset::from_name("invalid"), None);
}
#[test]
fn test_security_preset() {
let config = AppConfig::security_preset();
assert_eq!(
config.matching.fuzzy_preset,
crate::config::FuzzyPreset::Strict
);
assert!(config.behavior.fail_on_vuln);
assert!(config.ecosystem_rules.detect_typosquats);
assert!(config.enrichment.is_some());
}
#[test]
fn test_ci_cd_preset() {
let config = AppConfig::ci_cd_preset();
assert!(config.behavior.fail_on_vuln);
assert!(config.behavior.fail_on_change);
assert!(config.behavior.quiet);
assert!(config.output.no_color);
}
#[test]
fn test_permissive_preset() {
let config = AppConfig::permissive_preset();
assert_eq!(
config.matching.fuzzy_preset,
crate::config::FuzzyPreset::Permissive
);
assert_eq!(config.matching.threshold, Some(0.6));
assert!(config.matching.include_unchanged);
}
#[test]
fn test_strict_preset() {
let config = AppConfig::strict_preset();
assert_eq!(
config.matching.fuzzy_preset,
crate::config::FuzzyPreset::Strict
);
assert_eq!(config.matching.threshold, Some(0.95));
assert!(config.graph_diff.enabled);
}
#[test]
fn test_from_preset() {
let default = AppConfig::from_preset(ConfigPreset::Default);
let security = AppConfig::from_preset(ConfigPreset::Security);
assert_eq!(
default.matching.fuzzy_preset,
crate::config::FuzzyPreset::Balanced
);
assert_eq!(
security.matching.fuzzy_preset,
crate::config::FuzzyPreset::Strict
);
}
#[test]
fn test_all_presets() {
let all = ConfigPreset::all();
assert_eq!(all.len(), 5);
}
}