use anyhow::{Context as _, Result};
use serde_json::Value;
use std::path::{Path, PathBuf};
pub async fn load_behavior_config(behavior_name: &str, project_root: &Path, environment: &str) -> Result<Value> {
let env_config_path = project_root
.join("configs")
.join(environment)
.join("behaviors")
.join(format!("{}.json", behavior_name));
let template_path = project_root.join("behaviors").join(format!("{}.json", behavior_name));
let base_config = load_json_file(&template_path).await.with_context(|| {
format!(
"Failed to load behavior template '{}' from {}",
behavior_name,
template_path.display()
)
})?;
let final_config = if env_config_path.exists() {
let env_config = load_json_file(&env_config_path).await?;
merge_configs(&base_config, &env_config)
} else {
base_config
};
tracing::debug!(
"Loaded behavior config '{}' for environment '{}' (template: {}, env: {})",
behavior_name,
environment,
template_path.exists(),
env_config_path.exists()
);
Ok(final_config)
}
async fn load_json_file(path: &Path) -> Result<Value> {
let content = tokio::fs::read_to_string(path)
.await
.with_context(|| format!("Failed to read file: {}", path.display()))?;
serde_json::from_str(&content).with_context(|| format!("Failed to parse JSON from: {}", path.display()))
}
fn merge_configs(base: &Value, override_val: &Value) -> Value {
match (base, override_val) {
(Value::Object(base_map), Value::Object(override_map)) => {
let mut result = base_map.clone();
for (key, value) in override_map {
result.insert(
key.clone(),
if let Some(base_value) = base_map.get(key) {
merge_configs(base_value, value)
} else {
value.clone()
},
);
}
Value::Object(result)
}
_ => override_val.clone(),
}
}
pub fn get_current_environment() -> String {
std::env::var("MECHA10_ENVIRONMENT").unwrap_or_else(|_| "dev".to_string())
}
pub fn detect_project_root() -> Result<PathBuf> {
let mut current = std::env::current_dir()?;
loop {
let mecha10_json = current.join("mecha10.json");
if mecha10_json.exists() {
return Ok(current);
}
if !current.pop() {
anyhow::bail!("Could not find project root (no mecha10.json found in parent directories)");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_merge_configs_objects() {
let base = json!({
"name": "test",
"config": {
"speed": 1.0,
"angle": 0.5
}
});
let override_val = json!({
"config": {
"speed": 2.0
}
});
let result = merge_configs(&base, &override_val);
assert_eq!(
result,
json!({
"name": "test",
"config": {
"speed": 2.0,
"angle": 0.5
}
})
);
}
#[test]
fn test_merge_configs_arrays_replaced() {
let base = json!({
"items": [1, 2, 3]
});
let override_val = json!({
"items": [4, 5]
});
let result = merge_configs(&base, &override_val);
assert_eq!(
result,
json!({
"items": [4, 5]
})
);
}
#[test]
fn test_get_current_environment_default() {
std::env::remove_var("MECHA10_ENVIRONMENT");
assert_eq!(get_current_environment(), "dev");
}
}