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