use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default)]
pub observer: ObserverConfig,
#[serde(default)]
pub singleton: SingletonConfig,
#[serde(default)]
pub factory: FactoryConfig,
#[serde(default)]
pub strategy: StrategyConfig,
#[serde(default)]
pub callback: CallbackConfig,
#[serde(default)]
pub template_method: TemplateMethodConfig,
#[serde(default)]
pub custom_rules: Vec<CustomPatternRule>,
#[serde(default = "default_confidence_threshold")]
pub confidence_threshold: f32,
}
fn default_enabled() -> bool {
true
}
fn default_confidence_threshold() -> f32 {
0.7
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverConfig {
#[serde(default = "default_interface_markers")]
pub interface_markers: Vec<String>,
#[serde(default = "default_registration_methods")]
pub registration_methods: Vec<String>,
#[serde(default = "default_method_prefixes")]
pub method_prefixes: Vec<String>,
}
fn default_interface_markers() -> Vec<String> {
vec![
"ABC".to_string(),
"Protocol".to_string(),
"Interface".to_string(),
]
}
fn default_registration_methods() -> Vec<String> {
vec![
"add_observer".to_string(),
"register".to_string(),
"subscribe".to_string(),
]
}
fn default_method_prefixes() -> Vec<String> {
vec![
"on_".to_string(),
"handle_".to_string(),
"notify_".to_string(),
]
}
impl Default for ObserverConfig {
fn default() -> Self {
Self {
interface_markers: default_interface_markers(),
registration_methods: default_registration_methods(),
method_prefixes: default_method_prefixes(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SingletonConfig {
#[serde(default = "default_true")]
pub detect_module_level: bool,
#[serde(default = "default_true")]
pub detect_new_override: bool,
#[serde(default = "default_true")]
pub detect_decorator: bool,
}
fn default_true() -> bool {
true
}
impl Default for SingletonConfig {
fn default() -> Self {
Self {
detect_module_level: true,
detect_new_override: true,
detect_decorator: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FactoryConfig {
#[serde(default = "default_true")]
pub detect_functions: bool,
#[serde(default = "default_true")]
pub detect_registries: bool,
#[serde(default = "default_factory_name_patterns")]
pub name_patterns: Vec<String>,
}
fn default_factory_name_patterns() -> Vec<String> {
vec![
"create_".to_string(),
"make_".to_string(),
"build_".to_string(),
"_factory".to_string(),
]
}
impl Default for FactoryConfig {
fn default() -> Self {
Self {
detect_functions: true,
detect_registries: true,
name_patterns: default_factory_name_patterns(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StrategyConfig {
#[serde(default = "default_true")]
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallbackConfig {
#[serde(default = "default_callback_decorators")]
pub decorator_patterns: Vec<String>,
}
fn default_callback_decorators() -> Vec<String> {
vec![
"route".to_string(),
"handler".to_string(),
"app.".to_string(),
]
}
impl Default for CallbackConfig {
fn default() -> Self {
Self {
decorator_patterns: default_callback_decorators(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TemplateMethodConfig {
#[serde(default = "default_true")]
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomPatternRule {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub method_pattern: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub class_pattern: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decorator_pattern: Option<String>,
#[serde(default = "default_confidence_threshold")]
pub confidence: f32,
}
impl Default for PatternConfig {
fn default() -> Self {
Self {
enabled: true,
observer: ObserverConfig::default(),
singleton: SingletonConfig::default(),
factory: FactoryConfig::default(),
strategy: StrategyConfig::default(),
callback: CallbackConfig::default(),
template_method: TemplateMethodConfig::default(),
custom_rules: Vec::new(),
confidence_threshold: 0.7,
}
}
}
impl PatternConfig {
pub fn load(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let config: Self = toml::from_str(&content)?;
Ok(config)
}
pub fn load_or_default(project_path: &Path) -> Self {
let config_path = project_path.join(".debtmap.toml");
if config_path.exists() {
Self::load(&config_path).unwrap_or_default()
} else {
Self::default()
}
}
pub fn validate(&self) -> Result<(), String> {
if !(0.0..=1.0).contains(&self.confidence_threshold) {
return Err(format!(
"confidence_threshold must be between 0.0 and 1.0, got {}",
self.confidence_threshold
));
}
for rule in &self.custom_rules {
if !(0.0..=1.0).contains(&rule.confidence) {
return Err(format!(
"Custom rule '{}' confidence must be between 0.0 and 1.0, got {}",
rule.name, rule.confidence
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = PatternConfig::default();
assert!(config.enabled);
assert_eq!(config.confidence_threshold, 0.7);
assert!(config
.observer
.interface_markers
.contains(&"ABC".to_string()));
}
#[test]
fn test_load_config_from_toml() {
let config_content = r#"
enabled = true
confidence_threshold = 0.8
[observer]
interface_markers = ["ABC", "Protocol", "CustomInterface"]
method_prefixes = ["on_", "handle_", "process_"]
[[custom_rules]]
name = "event_handler"
method_pattern = "^handle_.*_event$"
confidence = 0.85
"#;
let config: Result<PatternConfig, _> = toml::from_str(config_content);
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.confidence_threshold, 0.8);
assert_eq!(config.observer.interface_markers.len(), 3);
assert_eq!(config.custom_rules.len(), 1);
assert_eq!(config.custom_rules[0].name, "event_handler");
}
#[test]
fn test_validate_valid_config() {
let config = PatternConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_validate_invalid_threshold() {
let config = PatternConfig {
confidence_threshold: 1.5,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_validate_invalid_custom_rule_confidence() {
let mut config = PatternConfig::default();
config.custom_rules.push(CustomPatternRule {
name: "test".to_string(),
method_pattern: None,
class_pattern: None,
decorator_pattern: None,
confidence: 2.0,
});
assert!(config.validate().is_err());
}
}