#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::commands::{ConfigCommands, ConfigFormat};
use crate::services::configuration_service::{configuration, PmatConfig};
use anyhow::Result;
use std::path::PathBuf;
pub struct ConfigCommand {}
impl ConfigCommand {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn new(_config_path: PathBuf) -> Self {
Self {}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn show(&self, format: ConfigFormat) -> Result<String> {
let config_service = configuration();
let config = config_service.get_config()?;
match format {
ConfigFormat::Json => Ok(serde_json::to_string_pretty(&config)?),
ConfigFormat::Toml => Ok(toml::to_string_pretty(&config)?),
ConfigFormat::Env => Ok(self.to_env_format(&config)?),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn get(&self, key: &str) -> Result<String> {
let config_service = configuration();
let config = config_service.get_config()?;
self.get_config_value(&config, key)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn validate(&self) -> Result<ValidationResult> {
let config_service = configuration();
let config = config_service.get_config()?;
let mut errors = Vec::new();
let warnings = Vec::new();
if config.quality.max_complexity == 0 {
errors.push("Quality: max_complexity must be > 0".to_string());
}
if config.quality.min_coverage > 100.0 || config.quality.min_coverage < 0.0 {
errors.push("Quality: min_coverage must be between 0 and 100".to_string());
}
if config.system.project_name.is_empty() {
errors.push("System: project_name cannot be empty".to_string());
}
if config.system.max_concurrent_operations == 0 {
errors.push("System: max_concurrent_operations must be > 0".to_string());
}
Ok(ValidationResult {
is_valid: errors.is_empty(),
errors,
warnings,
})
}
fn to_env_format(&self, config: &PmatConfig) -> Result<String> {
let mut env_vars = Vec::new();
env_vars.push("PMAT_HOOKS_ENABLED=true".to_string());
env_vars.push("PMAT_HOOKS_AUTO_INSTALL=true".to_string());
env_vars.push(format!(
"PMAT_MAX_CYCLOMATIC_COMPLEXITY={}",
config.quality.max_complexity
));
env_vars.push(format!(
"PMAT_MAX_COGNITIVE_COMPLEXITY={}",
config.quality.max_cognitive_complexity
));
env_vars.push("PMAT_MAX_SATD_COMMENTS=5".to_string());
env_vars.push(format!(
"PMAT_MIN_TEST_COVERAGE={}",
config.quality.min_coverage as u32
));
Ok(env_vars.join("\n"))
}
fn get_config_value(&self, config: &PmatConfig, key: &str) -> Result<String> {
let parts: Vec<&str> = key.split('.').collect();
match parts.as_slice() {
["hooks", "auto_install"] => Ok("true".to_string()),
["hooks", "quality_gates", "max_cyclomatic_complexity"] => {
Ok(config.quality.max_complexity.to_string())
}
["hooks", "quality_gates", "max_cognitive_complexity"] => {
Ok(config.quality.max_cognitive_complexity.to_string())
}
["hooks", "quality_gates", "min_test_coverage"] => {
Ok(config.quality.min_coverage.to_string())
}
["hooks", "documentation", "task_id_pattern"] => Ok("PMAT-[0-9]{4}".to_string()),
_ => Err(anyhow::anyhow!("Configuration key '{key}' not found")),
}
}
}
#[derive(Debug, PartialEq)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_config_command(cmd: &ConfigCommands) -> Result<()> {
match cmd {
ConfigCommands::Show { format } => handle_show(format).await,
ConfigCommands::Get { key } => handle_get(key).await,
ConfigCommands::Validate { fix } => handle_validate(*fix).await,
ConfigCommands::Sources => handle_sources(),
}
}
async fn handle_show(format: &ConfigFormat) -> Result<()> {
let config_path = std::env::current_dir()?.join("pmat.toml");
let config_cmd = ConfigCommand::new(config_path);
let result = config_cmd.show(format.clone()).await?;
println!("{result}");
Ok(())
}
async fn handle_get(key: &str) -> Result<()> {
let config_path = std::env::current_dir()?.join("pmat.toml");
let config_cmd = ConfigCommand::new(config_path);
let result = config_cmd.get(key).await?;
println!("{result}");
Ok(())
}
#[derive(Debug, Clone)]
struct ConfigFixInfo {
field_name: String,
_new_value: String,
description: String,
}
fn extract_config_error_handler(error_msg: &str) -> Option<ConfigFixInfo> {
if error_msg.contains("max_complexity must be > 0") {
return Some(ConfigFixInfo {
field_name: "quality.max_complexity".to_string(),
_new_value: "20".to_string(),
description: "Set max_complexity to 20".to_string(),
});
}
if error_msg.contains("min_coverage must be between 0 and 100") {
return Some(ConfigFixInfo {
field_name: "quality.min_coverage".to_string(),
_new_value: "clamp(0.0, 100.0)".to_string(),
description: "Clamped min_coverage to valid range".to_string(),
});
}
if error_msg.contains("project_name cannot be empty") {
return Some(ConfigFixInfo {
field_name: "system.project_name".to_string(),
_new_value: "pmat-project".to_string(),
description: "Set default project name".to_string(),
});
}
if error_msg.contains("max_concurrent_operations must be > 0") {
return Some(ConfigFixInfo {
field_name: "system.max_concurrent_operations".to_string(),
_new_value: "4".to_string(),
description: "Set max_concurrent_operations to 4".to_string(),
});
}
None
}
async fn apply_config_fixes(errors: &[String], config: &mut PmatConfig) -> Result<Vec<String>> {
let mut fixed_issues = Vec::new();
for error in errors {
if let Some(fix_info) = extract_config_error_handler(error) {
apply_single_fix(&fix_info, config);
fixed_issues.push(fix_info.description);
}
}
Ok(fixed_issues)
}
fn apply_single_fix(fix_info: &ConfigFixInfo, config: &mut PmatConfig) {
match fix_info.field_name.as_str() {
"quality.max_complexity" => {
config.quality.max_complexity = 20;
}
"quality.min_coverage" => {
config.quality.min_coverage = config.quality.min_coverage.clamp(0.0, 100.0);
}
"system.project_name" => {
if config.system.project_name.is_empty() {
config.system.project_name = "pmat-project".to_string();
}
}
"system.max_concurrent_operations" => {
if config.system.max_concurrent_operations == 0 {
config.system.max_concurrent_operations = 4;
}
}
_ => {} }
}
async fn save_config_changes(config: &PmatConfig, fixed_issues: &[String]) -> Result<()> {
if fixed_issues.is_empty() {
return Ok(());
}
println!("✅ Fixed issues: {}", fixed_issues.join(", "));
let config_path = std::env::current_dir()?.join("pmat.toml");
let toml_content = toml::to_string_pretty(config)?;
std::fs::write(&config_path, toml_content)?;
println!("📝 Updated configuration file: {}", config_path.display());
Ok(())
}
async fn handle_validate(fix: bool) -> Result<()> {
let config_path = std::env::current_dir()?.join("pmat.toml");
let config_cmd = ConfigCommand::new(config_path);
let result = config_cmd.validate().await?;
if fix && !result.errors.is_empty() {
println!("🔧 Auto-fix enabled, attempting to fix configuration issues...");
let config_service = configuration();
let mut config = config_service.get_config()?;
let fixed_issues = apply_config_fixes(&result.errors, &mut config).await?;
save_config_changes(&config, &fixed_issues).await?;
}
print_validation_result(&result)?;
Ok(())
}
fn print_validation_result(result: &ValidationResult) -> Result<()> {
if result.is_valid {
println!("✅ Configuration is valid");
} else {
println!("❌ Configuration validation failed:");
for error in &result.errors {
println!(" - {error}");
}
return Err(anyhow::anyhow!("Configuration validation failed"));
}
Ok(())
}
fn handle_sources() -> Result<()> {
println!("📍 Configuration Sources (in precedence order):");
println!(" 1. pmat.toml (current directory)");
println!(" 2. Default configuration");
Ok(())
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_config() -> (TempDir, PathBuf) {
let temp_dir = tempfile::tempdir().unwrap();
let config_path = temp_dir.path().join("pmat.toml");
let sample_config = r#"
[system]
project_name = "test"
max_concurrent_operations = 4
[quality]
max_complexity = 30
max_cognitive_complexity = 25
min_coverage = 80.0
[analysis]
max_file_size = 1048576
timeout_seconds = 300
[performance]
test_iterations = 3
[mcp]
server_name = "pmat"
request_timeout_seconds = 30
"#;
std::fs::write(&config_path, sample_config).unwrap();
(temp_dir, config_path)
}
#[tokio::test]
async fn test_config_show_formats() {
let (_temp_dir, config_path) = create_test_config();
let config_cmd = ConfigCommand::new(config_path);
let json_result = config_cmd.show(ConfigFormat::Json).await;
assert!(json_result.is_ok());
let toml_result = config_cmd.show(ConfigFormat::Toml).await;
assert!(toml_result.is_ok());
let env_result = config_cmd.show(ConfigFormat::Env).await;
assert!(env_result.is_ok());
}
#[tokio::test]
async fn test_config_get_values() {
let (_temp_dir, config_path) = create_test_config();
let config_cmd = ConfigCommand::new(config_path);
let result = config_cmd
.get("hooks.quality_gates.max_cyclomatic_complexity")
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_config_validation() {
let (_temp_dir, config_path) = create_test_config();
let config_cmd = ConfigCommand::new(config_path);
let result = config_cmd.validate().await;
assert!(result.is_ok());
let validation = result.unwrap();
assert!(validation.is_valid);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}