Skip to main content

mecha10_core/context/
config.rs

1//! Infrastructure configuration loading (Redis URLs, control plane, mecha10.json).
2
3use super::{Context, InfrastructureConfig};
4use crate::error::{Mecha10Error, Result};
5use anyhow::Context as _;
6
7impl Context {
8    /// Load infrastructure configuration based on environment
9    ///
10    /// Priority order:
11    /// 1. mecha10.json (project configuration) - redis.url field
12    /// 2. Environment variables (MECHA10_REDIS_URL or REDIS_URL)
13    /// 3. config/infrastructure.<environment>.yaml
14    ///
15    /// Set via MECHA10_ENVIRONMENT environment variable.
16    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        // 1. Try loading from mecha10.json FIRST (project configuration)
22        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        // 2. Check for environment variable overrides
38        // If REDIS_URL or MECHA10_REDIS_URL is set, use that and create minimal config
39        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        // 3. Load from infrastructure.yaml
55        let environment = env::var("MECHA10_ENVIRONMENT").unwrap_or_else(|_| "development".to_string());
56
57        let config_filename = format!("infrastructure.{}.yaml", environment);
58
59        // Try multiple paths in order:
60        // 1. Current directory (for running from workspace root)
61        // 2. Workspace root (for running tests from target/debug/deps)
62        // 3. Two levels up (for running tests from packages/core/target/debug/deps)
63        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        // If no config file found, return error with helpful message
84        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    /// Try to load Redis URL from mecha10.json
99    ///
100    /// Looks for mecha10.json in current directory and reads redis.url field.
101    fn try_load_redis_from_mecha10_json() -> Result<String> {
102        use std::path::PathBuf;
103
104        // Try multiple paths to find mecha10.json
105        let paths_to_try = vec![
106            PathBuf::from("mecha10.json"),
107            PathBuf::from("../mecha10.json"),
108            PathBuf::from("../../mecha10.json"),
109        ];
110
111        for path in &paths_to_try {
112            if !path.exists() {
113                continue;
114            }
115
116            // Read and parse mecha10.json
117            let content = std::fs::read_to_string(path)
118                .map_err(|e| Mecha10Error::Configuration(format!("Failed to read {}: {}", path.display(), e)))?;
119
120            let json: serde_json::Value = serde_json::from_str(&content)
121                .map_err(|e| Mecha10Error::Configuration(format!("Failed to parse {}: {}", path.display(), e)))?;
122
123            // Extract redis.url
124            if let Some(redis_url) = json.get("redis").and_then(|r| r.get("url")).and_then(|u| u.as_str()) {
125                return Ok(redis_url.to_string());
126            }
127        }
128
129        Err(Mecha10Error::Configuration(
130            "No mecha10.json found with redis.url configuration".to_string(),
131        ))
132    }
133
134    /// Get Redis URL from infrastructure configuration (helper for internal use)
135    pub(super) fn get_redis_url_from_config(config: &InfrastructureConfig) -> Result<String> {
136        // First try environment variables (for backward compatibility and testing)
137        if let Ok(url) = std::env::var("MECHA10_REDIS_URL") {
138            return Ok(url);
139        }
140        if let Ok(url) = std::env::var("REDIS_URL") {
141            return Ok(url);
142        }
143
144        // Use config
145        Ok(config.redis.url.clone())
146    }
147
148    /// Get Redis URL from infrastructure configuration
149    pub fn get_redis_url() -> Result<String> {
150        let config = Self::load_infrastructure_config()?;
151        Self::get_redis_url_from_config(&config)
152    }
153
154    /// Get control plane Redis URL from infrastructure configuration
155    pub fn get_control_plane_redis_url() -> Result<Option<String>> {
156        let config = Self::load_infrastructure_config()?;
157        Ok(config.control_plane_redis.map(|c| c.url))
158    }
159}