mecha10_core/context/
config.rs1use super::{Context, InfrastructureConfig};
4use crate::error::{Mecha10Error, Result};
5use anyhow::Context as _;
6
7impl Context {
8 pub(super) fn load_infrastructure_config() -> Result<InfrastructureConfig> {
17 use crate::config::load_config;
18 use std::env;
19 use std::path::PathBuf;
20
21 if let Ok(redis_url) = Self::try_load_redis_from_mecha10_json() {
22 return Ok(InfrastructureConfig {
23 redis: crate::context::infra_config::RedisConfig {
24 url: redis_url,
25 max_connections: 10,
26 connection_timeout_ms: 5000,
27 },
28 control_plane_redis: None,
29 bridge: None,
30 postgres: None,
31 mlflow: None,
32 minio: None,
33 });
34 }
35
36 if let Ok(redis_url) = env::var("MECHA10_REDIS_URL").or_else(|_| env::var("REDIS_URL")) {
37 return Ok(InfrastructureConfig {
38 redis: crate::context::infra_config::RedisConfig {
39 url: redis_url,
40 max_connections: 10,
41 connection_timeout_ms: 5000,
42 },
43 control_plane_redis: None,
44 bridge: None,
45 postgres: None,
46 mlflow: None,
47 minio: None,
48 });
49 }
50
51 let environment = env::var("MECHA10_ENVIRONMENT").unwrap_or_else(|_| "development".to_string());
52
53 let config_filename = format!("infrastructure.{}.yaml", environment);
54
55 let paths_to_try = vec![
56 PathBuf::from(format!("config/{}", config_filename)),
57 PathBuf::from(format!("../../config/{}", config_filename)),
58 PathBuf::from(format!("../../../../config/{}", config_filename)),
59 ];
60
61 for path in &paths_to_try {
62 if path.exists() {
63 return load_config(path)
64 .with_context(|| {
65 format!(
66 "Failed to load infrastructure config for environment '{}' from '{}'",
67 environment,
68 path.display()
69 )
70 })
71 .map_err(|e| Mecha10Error::Configuration(format!("{:#}", e)));
72 }
73 }
74
75 Err(Mecha10Error::Configuration(format!(
76 "Failed to load infrastructure config for environment '{}'. \
77 Tried paths: {}. \
78 Make sure the file exists, set MECHA10_REDIS_URL environment variable, \
79 or configure redis.url in mecha10.json.",
80 environment,
81 paths_to_try
82 .iter()
83 .map(|p| p.display().to_string())
84 .collect::<Vec<_>>()
85 .join(", ")
86 )))
87 }
88
89 fn try_load_redis_from_mecha10_json() -> Result<String> {
95 use std::env;
96
97 let current_dir = env::current_dir()
98 .map_err(|e| Mecha10Error::Configuration(format!("Failed to get current directory: {}", e)))?;
99
100 let project_root = crate::fs_utils::find_project_root(¤t_dir).ok_or_else(|| {
101 Mecha10Error::Configuration("No mecha10.json found with redis.url configuration".to_string())
102 })?;
103
104 let path = project_root.join(crate::fs_utils::PROJECT_CONFIG_FILE);
105
106 let content = std::fs::read_to_string(&path)
107 .map_err(|e| Mecha10Error::Configuration(format!("Failed to read {}: {}", path.display(), e)))?;
108
109 let json: serde_json::Value = serde_json::from_str(&content)
110 .map_err(|e| Mecha10Error::Configuration(format!("Failed to parse {}: {}", path.display(), e)))?;
111
112 json.get("redis")
113 .and_then(|r| r.get("url"))
114 .and_then(|u| u.as_str())
115 .map(|s| s.to_string())
116 .ok_or_else(|| {
117 Mecha10Error::Configuration("No mecha10.json found with redis.url configuration".to_string())
118 })
119 }
120
121 pub(super) fn get_redis_url_from_config(config: &InfrastructureConfig) -> Result<String> {
123 if let Ok(url) = std::env::var("MECHA10_REDIS_URL") {
124 return Ok(url);
125 }
126 if let Ok(url) = std::env::var("REDIS_URL") {
127 return Ok(url);
128 }
129
130 Ok(config.redis.url.clone())
131 }
132
133 pub fn get_redis_url() -> Result<String> {
135 let config = Self::load_infrastructure_config()?;
136 Self::get_redis_url_from_config(&config)
137 }
138
139 pub fn get_control_plane_redis_url() -> Result<Option<String>> {
141 let config = Self::load_infrastructure_config()?;
142 Ok(config.control_plane_redis.map(|c| c.url))
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use serial_test::serial;
150 use std::fs;
151
152 #[test]
153 #[serial]
154 fn try_load_redis_from_mecha10_json_finds_config_several_levels_up() {
155 let original_dir = std::env::current_dir().unwrap();
156
157 let project = tempfile::tempdir().unwrap();
158 fs::write(
159 project.path().join("mecha10.json"),
160 r#"{"redis": {"url": "redis://nested-project:6379"}}"#,
161 )
162 .unwrap();
163
164 let nested = project.path().join("a").join("b").join("c");
165 fs::create_dir_all(&nested).unwrap();
166 std::env::set_current_dir(&nested).unwrap();
167
168 let result = Context::try_load_redis_from_mecha10_json();
169
170 std::env::set_current_dir(original_dir).unwrap();
171
172 assert_eq!(result.unwrap(), "redis://nested-project:6379");
173 }
174
175 #[test]
176 #[serial]
177 fn try_load_redis_from_mecha10_json_errs_when_not_found() {
178 let original_dir = std::env::current_dir().unwrap();
179
180 let dir = tempfile::tempdir().unwrap();
181 std::env::set_current_dir(dir.path()).unwrap();
182
183 let result = Context::try_load_redis_from_mecha10_json();
184
185 std::env::set_current_dir(original_dir).unwrap();
186
187 assert!(result.is_err());
188 }
189}