use serde::{Deserialize, Serialize};
use super::core::DebtmapConfig;
use super::scoring::ScoringWeights;
use super::thresholds::ThresholdsConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PresetLevel {
Strict,
Balanced,
Permissive,
}
impl PresetLevel {
pub fn to_config(self) -> DebtmapConfig {
match self {
PresetLevel::Strict => strict_preset(),
PresetLevel::Balanced => balanced_preset(),
PresetLevel::Permissive => permissive_preset(),
}
}
pub fn scoring_weights(self) -> ScoringWeights {
match self {
PresetLevel::Strict => ScoringWeights {
coverage: 0.55, complexity: 0.30,
semantic: 0.00,
dependency: 0.15,
security: 0.00,
organization: 0.00,
},
PresetLevel::Balanced => ScoringWeights::default(),
PresetLevel::Permissive => ScoringWeights {
coverage: 0.40, complexity: 0.40, semantic: 0.00,
dependency: 0.20,
security: 0.00,
organization: 0.00,
},
}
}
pub fn thresholds(self) -> ThresholdsConfig {
match self {
PresetLevel::Strict => ThresholdsConfig {
complexity: Some(20),
duplication: Some(5),
max_file_length: Some(500),
max_function_length: Some(30),
minimum_cyclomatic_complexity: Some(3),
minimum_cognitive_complexity: Some(5),
min_score_threshold: Some(2.0),
..Default::default()
},
PresetLevel::Balanced => ThresholdsConfig {
complexity: Some(50),
duplication: Some(10),
max_file_length: Some(1000),
max_function_length: Some(50),
minimum_cyclomatic_complexity: Some(5),
minimum_cognitive_complexity: Some(10),
min_score_threshold: Some(3.0),
..Default::default()
},
PresetLevel::Permissive => ThresholdsConfig {
complexity: Some(100),
duplication: Some(20),
max_file_length: Some(2000),
max_function_length: Some(100),
minimum_cyclomatic_complexity: Some(10),
minimum_cognitive_complexity: Some(15),
min_score_threshold: Some(5.0),
..Default::default()
},
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"strict" => Some(PresetLevel::Strict),
"balanced" => Some(PresetLevel::Balanced),
"permissive" | "lenient" => Some(PresetLevel::Permissive),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
PresetLevel::Strict => "strict",
PresetLevel::Balanced => "balanced",
PresetLevel::Permissive => "permissive",
}
}
}
impl std::fmt::Display for PresetLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl std::str::FromStr for PresetLevel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
PresetLevel::parse(s).ok_or_else(|| {
format!(
"Invalid preset level: '{}'. Valid options: strict, balanced, permissive",
s
)
})
}
}
fn strict_preset() -> DebtmapConfig {
DebtmapConfig {
scoring: Some(PresetLevel::Strict.scoring_weights()),
thresholds: Some(PresetLevel::Strict.thresholds()),
..Default::default()
}
}
fn balanced_preset() -> DebtmapConfig {
DebtmapConfig {
scoring: Some(PresetLevel::Balanced.scoring_weights()),
thresholds: Some(PresetLevel::Balanced.thresholds()),
..Default::default()
}
}
fn permissive_preset() -> DebtmapConfig {
DebtmapConfig {
scoring: Some(PresetLevel::Permissive.scoring_weights()),
thresholds: Some(PresetLevel::Permissive.thresholds()),
..Default::default()
}
}
pub fn merge_preset_with_config(preset: PresetLevel, config: DebtmapConfig) -> DebtmapConfig {
let preset_config = preset.to_config();
DebtmapConfig {
scoring: config.scoring.or(preset_config.scoring),
display: config.display.or(preset_config.display),
external_api: config.external_api.or(preset_config.external_api),
god_object_detection: config
.god_object_detection
.or(preset_config.god_object_detection),
thresholds: config.thresholds.or(preset_config.thresholds),
languages: config.languages.or(preset_config.languages),
ignore: config.ignore.or(preset_config.ignore),
output: config.output.or(preset_config.output),
context: config.context.or(preset_config.context),
entropy: config.entropy.or(preset_config.entropy),
role_multipliers: config.role_multipliers.or(preset_config.role_multipliers),
complexity_thresholds: config
.complexity_thresholds
.or(preset_config.complexity_thresholds),
error_handling: config.error_handling.or(preset_config.error_handling),
normalization: config.normalization.or(preset_config.normalization),
loc: config.loc.or(preset_config.loc),
tiers: config.tiers.or(preset_config.tiers),
role_coverage_weights: config
.role_coverage_weights
.or(preset_config.role_coverage_weights),
role_multiplier_config: config
.role_multiplier_config
.or(preset_config.role_multiplier_config),
orchestrator_detection: config
.orchestrator_detection
.or(preset_config.orchestrator_detection),
orchestration_adjustment: config
.orchestration_adjustment
.or(preset_config.orchestration_adjustment),
classification: config.classification.or(preset_config.classification),
mapping_patterns: config.mapping_patterns.or(preset_config.mapping_patterns),
coverage_expectations: config
.coverage_expectations
.or(preset_config.coverage_expectations),
complexity_weights: config
.complexity_weights
.or(preset_config.complexity_weights),
functional_analysis: config
.functional_analysis
.or(preset_config.functional_analysis),
boilerplate_detection: config
.boilerplate_detection
.or(preset_config.boilerplate_detection),
scoring_rebalanced: config
.scoring_rebalanced
.or(preset_config.scoring_rebalanced),
context_multipliers: config
.context_multipliers
.or(preset_config.context_multipliers),
batch_analysis: config.batch_analysis.or(preset_config.batch_analysis),
retry: config.retry.or(preset_config.retry),
analysis: config.analysis.or(preset_config.analysis),
state_detection: config.state_detection.or(preset_config.state_detection),
data_flow_scoring: config.data_flow_scoring.or(preset_config.data_flow_scoring),
context_suggestion: config
.context_suggestion
.or(preset_config.context_suggestion),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preset_levels_distinct() {
let strict = PresetLevel::Strict.thresholds();
let balanced = PresetLevel::Balanced.thresholds();
let permissive = PresetLevel::Permissive.thresholds();
assert!(strict.complexity.unwrap() < balanced.complexity.unwrap());
assert!(balanced.complexity.unwrap() < permissive.complexity.unwrap());
}
#[test]
fn test_preset_scoring_weights_valid() {
let presets = vec![
PresetLevel::Strict,
PresetLevel::Balanced,
PresetLevel::Permissive,
];
for preset in presets {
let weights = preset.scoring_weights();
assert!(weights.validate().is_ok());
let sum = weights.coverage
+ weights.complexity
+ weights.semantic
+ weights.dependency
+ weights.security
+ weights.organization;
assert!((sum - 1.0).abs() < 0.01);
}
}
#[test]
fn test_preset_parse() {
assert_eq!(PresetLevel::parse("strict"), Some(PresetLevel::Strict));
assert_eq!(PresetLevel::parse("Strict"), Some(PresetLevel::Strict));
assert_eq!(PresetLevel::parse("STRICT"), Some(PresetLevel::Strict));
assert_eq!(PresetLevel::parse("balanced"), Some(PresetLevel::Balanced));
assert_eq!(
PresetLevel::parse("permissive"),
Some(PresetLevel::Permissive)
);
assert_eq!(PresetLevel::parse("lenient"), Some(PresetLevel::Permissive));
assert_eq!(PresetLevel::parse("invalid"), None);
}
#[test]
fn test_preset_display() {
assert_eq!(PresetLevel::Strict.to_string(), "strict");
assert_eq!(PresetLevel::Balanced.to_string(), "balanced");
assert_eq!(PresetLevel::Permissive.to_string(), "permissive");
}
#[test]
fn test_merge_preset_with_config() {
let preset = PresetLevel::Strict;
let custom_config = DebtmapConfig {
scoring: Some(ScoringWeights {
coverage: 0.60,
complexity: 0.25,
semantic: 0.00,
dependency: 0.15,
security: 0.00,
organization: 0.00,
}),
..Default::default()
};
let merged = merge_preset_with_config(preset, custom_config);
assert_eq!(merged.scoring.unwrap().coverage, 0.60);
assert!(merged.thresholds.is_some());
}
#[test]
fn test_strict_preset_characteristics() {
let config = PresetLevel::Strict.to_config();
let thresholds = config.thresholds.unwrap();
let scoring = config.scoring.unwrap();
assert_eq!(thresholds.complexity, Some(20));
assert_eq!(thresholds.duplication, Some(5));
assert!(scoring.coverage > 0.50);
}
#[test]
fn test_permissive_preset_characteristics() {
let config = PresetLevel::Permissive.to_config();
let thresholds = config.thresholds.unwrap();
let scoring = config.scoring.unwrap();
assert_eq!(thresholds.complexity, Some(100));
assert_eq!(thresholds.duplication, Some(20));
assert!(scoring.coverage < 0.50);
}
#[test]
fn test_balanced_is_default() {
let balanced = PresetLevel::Balanced.to_config();
assert_eq!(
balanced.scoring.unwrap().coverage,
ScoringWeights::default().coverage
);
}
}