pub struct ConfigurationService {
config: Arc<RwLock<PmatConfig>>,
config_path: PathBuf,
metrics: Arc<RwLock<ServiceMetrics>>,
watchers: Arc<RwLock<Vec<Box<dyn ConfigWatcher + Send + Sync>>>>,
}
pub trait ConfigWatcher {
fn on_config_changed(&self, config: &PmatConfig) -> Result<()>;
}
impl ConfigurationService {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn new(config_path: Option<PathBuf>) -> Self {
let default_path = config_path.unwrap_or_else(|| {
std::env::current_dir()
.unwrap_or_default()
.join("pmat.toml")
});
let config = Self::read_config_file(&default_path).unwrap_or_else(Self::default_config);
Self {
config: Arc::new(RwLock::new(config)),
config_path: default_path,
metrics: Arc::new(RwLock::new(ServiceMetrics::default())),
watchers: Arc::new(RwLock::new(Vec::new())),
}
}
fn read_config_file(path: &std::path::Path) -> Option<PmatConfig> {
if !path.exists() {
return None;
}
match std::fs::read_to_string(path) {
Ok(content) => match toml::from_str::<PmatConfig>(&content) {
Ok(config) => Some(config),
Err(e) => {
eprintln!(
"warning: {} is not valid pmat configuration ({e}); using defaults",
path.display()
);
None
}
},
Err(e) => {
eprintln!(
"warning: could not read {} ({e}); using defaults",
path.display()
);
None
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn load(&self) -> Result<()> {
if self.config_path.exists() {
let content = tokio::fs::read_to_string(&self.config_path).await?;
let config: PmatConfig = toml::from_str(&content)?;
{
let mut config_lock = self
.config
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire config write lock"))?;
*config_lock = config.clone();
}
self.notify_watchers(&config)?;
{
let mut metrics = self
.metrics
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?;
metrics.record_request(std::time::Duration::from_millis(1), true);
}
}
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn save(&self) -> Result<()> {
let config = {
self.config
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire config read lock"))?
.clone()
};
let content = toml::to_string_pretty(&config)?;
tokio::fs::write(&self.config_path, content).await?;
{
let mut metrics = self
.metrics
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?;
metrics.record_request(std::time::Duration::from_millis(1), true);
}
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_config(&self) -> Result<PmatConfig> {
Ok(self
.config
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire config read lock"))?
.clone())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn update_config<F>(&self, updater: F) -> Result<()>
where
F: FnOnce(&mut PmatConfig) -> Result<()>,
{
let config_clone = {
let mut config = self
.config
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire config write lock"))?;
updater(&mut config)?;
config.clone()
};
self.save().await?;
self.notify_watchers(&config_clone)?;
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn add_watcher(&self, watcher: Box<dyn ConfigWatcher + Send + Sync>) -> Result<()> {
let mut watchers = self
.watchers
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire watchers lock"))?;
watchers.push(watcher);
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_quality_config(&self) -> Result<QualityConfig> {
Ok(self.get_config()?.quality)
}
pub fn get_analysis_config(&self) -> Result<AnalysisConfig> {
Ok(self.get_config()?.analysis)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_performance_config(&self) -> Result<PerformanceConfig> {
Ok(self.get_config()?.performance)
}
pub fn get_mcp_config(&self) -> Result<McpConfig> {
Ok(self.get_config()?.mcp)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_roadmap_config(&self) -> Result<RoadmapConfig> {
Ok(self.get_config()?.roadmap)
}
pub fn get_telemetry_config(&self) -> Result<TelemetryConfig> {
Ok(self.get_config()?.telemetry)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_semantic_config(&self) -> Result<SemanticConfig> {
Ok(self.get_config()?.semantic)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_semantic_config_with_env_fallback(&self) -> Result<SemanticConfig> {
let mut config = self.get_semantic_config()?;
if config.workspace_path.is_none() {
config.workspace_path = std::env::var("PMAT_WORKSPACE")
.ok()
.map(PathBuf::from)
.or_else(|| std::env::current_dir().ok());
}
if config.vector_db_path.is_none() {
config.vector_db_path = std::env::var("PMAT_VECTOR_DB_PATH")
.ok()
.or_else(|| config.workspace_path.as_deref().map(default_vector_db_path));
}
Ok(config)
}
fn notify_watchers(&self, config: &PmatConfig) -> Result<()> {
let watchers = self
.watchers
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire watchers lock"))?;
for watcher in watchers.iter() {
if let Err(e) = watcher.on_config_changed(config) {
tracing::warn!("Configuration watcher failed: {}", e);
}
}
Ok(())
}
}
impl ConfigurationService {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn start(&self) -> Result<()> {
self.load().await?;
{
let mut metrics = self
.metrics
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?;
metrics.record_request(Duration::from_millis(10), true);
}
tracing::info!(
"Configuration service started with config at: {:?}",
self.config_path
);
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn stop(&self) -> Result<()> {
self.save().await?;
{
let mut metrics = self
.metrics
.write()
.map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?;
metrics.record_request(Duration::from_millis(5), true);
}
tracing::info!("Configuration service stopped");
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn status(&self) -> Result<String> {
let config_exists = self.config_path.exists();
let _config = self.get_config()?;
Ok(format!(
"Configuration service: {} (file: {}, sections: {})",
if config_exists { "loaded" } else { "default" },
self.config_path.display(),
8 ))
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn get_metrics(&self) -> Result<ServiceMetrics> {
Ok(self
.metrics
.read()
.map_err(|_| anyhow::anyhow!("Failed to acquire metrics lock"))?
.clone())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn health_check(&self) -> Result<bool> {
self.get_config().map(|_| true)
}
}
#[must_use]
pub fn default_vector_db_path(workspace: &std::path::Path) -> String {
workspace
.join(".pmat")
.join("embeddings.db")
.to_string_lossy()
.to_string()
}
#[cfg(test)]
mod new_reads_disk_tests {
use super::ConfigurationService;
#[test]
fn test_new_reports_the_values_on_disk() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("pmat.toml");
let mut on_disk = ConfigurationService::default_config();
on_disk.quality.max_complexity = 5;
on_disk.system.project_name = "written-by-set".to_string();
std::fs::write(&path, toml::to_string_pretty(&on_disk).unwrap()).unwrap();
let service = ConfigurationService::new(Some(path));
let config = service.get_config().unwrap();
assert_eq!(config.quality.max_complexity, 5);
assert_eq!(config.system.project_name, "written-by-set");
}
#[test]
fn test_new_falls_back_to_defaults_when_no_file() {
let temp = tempfile::tempdir().unwrap();
let service = ConfigurationService::new(Some(temp.path().join("absent.toml")));
let config = service.get_config().unwrap();
assert_eq!(
config.quality.max_complexity,
ConfigurationService::default_config().quality.max_complexity
);
}
#[test]
fn test_new_survives_a_malformed_file() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("pmat.toml");
std::fs::write(&path, "this is not toml {{{").unwrap();
let service = ConfigurationService::new(Some(path));
assert!(service.get_config().is_ok());
}
}
lazy_static::lazy_static! {
static ref CONFIGURATION: Arc<ConfigurationService> = Arc::new(ConfigurationService::new(None));
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn configuration() -> Arc<ConfigurationService> {
CONFIGURATION.clone()
}