elif_core/
container.rs

1use service_builder::builder;
2use std::sync::Arc;
3use thiserror::Error;
4
5/// Container for managing application services using service-builder
6#[builder]
7pub struct Container {
8    #[builder(getter, setter)]
9    pub config: Arc<crate::app_config::AppConfig>,
10    
11    #[builder(getter, setter)]
12    pub database: Arc<dyn DatabaseConnection>,
13}
14
15/// Database connection trait  
16pub trait DatabaseConnection: Send + Sync {
17    fn is_connected(&self) -> bool;
18    fn execute(&self, query: &str) -> Result<(), DatabaseError>;
19}
20
21/// Cache service trait
22pub trait Cache: Send + Sync {
23    fn get(&self, key: &str) -> Option<String>;
24    fn set(&self, key: &str, value: String) -> Result<(), CacheError>;
25}
26
27/// Logger service trait
28pub trait Logger: Send + Sync {
29    fn info(&self, message: &str);
30    fn error(&self, message: &str);
31    fn debug(&self, message: &str);
32}
33
34#[derive(Error, Debug)]
35pub enum ContainerError {
36    #[error("Service not found: {service}")]
37    ServiceNotFound { service: String },
38    
39    #[error("Database error: {message}")]
40    DatabaseError { message: String },
41    
42    #[error("Cache error: {message}")]
43    CacheError { message: String },
44    
45    #[error("Configuration error: {message}")]
46    ConfigError { message: String },
47}
48
49#[derive(Error, Debug)]
50pub enum DatabaseError {
51    #[error("Connection failed: {reason}")]
52    ConnectionFailed { reason: String },
53    
54    #[error("Query failed: {reason}")]
55    QueryFailed { reason: String },
56}
57
58#[derive(Error, Debug)]  
59pub enum CacheError {
60    #[error("Cache operation failed: {reason}")]
61    OperationFailed { reason: String },
62}
63
64/// Optional services container for services not required at startup
65pub struct OptionalServices {
66    pub cache: Option<Arc<dyn Cache>>,
67    pub logger: Option<Arc<dyn Logger>>,
68}
69
70impl OptionalServices {
71    pub fn new() -> Self {
72        Self {
73            cache: None,
74            logger: None,
75        }
76    }
77    
78    pub fn with_cache(mut self, cache: Arc<dyn Cache>) -> Self {
79        self.cache = Some(cache);
80        self
81    }
82    
83    pub fn with_logger(mut self, logger: Arc<dyn Logger>) -> Self {
84        self.logger = Some(logger);
85        self
86    }
87}
88
89impl Container {
90    /// Check if all required services are available (always true with required fields)
91    pub fn validate(&self) -> Result<(), ContainerError> {
92        // All required services are guaranteed by the builder pattern
93        Ok(())
94    }
95    
96    /// Get configuration service
97    pub fn config(&self) -> Arc<crate::app_config::AppConfig> {
98        self.config.clone()
99    }
100    
101    /// Get database connection
102    pub fn database(&self) -> Arc<dyn DatabaseConnection> {
103        self.database.clone()
104    }
105}
106
107// Default implementations for testing
108pub mod test_implementations {
109    use super::*;
110    
111    pub fn create_test_config() -> crate::app_config::AppConfig {
112        crate::app_config::AppConfig {
113            name: "test-app".to_string(),
114            environment: crate::app_config::Environment::Testing,
115            database_url: "sqlite::memory:".to_string(),
116            jwt_secret: Some("test-secret".to_string()),
117            server: crate::app_config::ServerConfig {
118                host: "127.0.0.1".to_string(),
119                port: 8080,
120                workers: 4,
121            },
122            logging: crate::app_config::LoggingConfig {
123                level: "info".to_string(),
124                format: "compact".to_string(),
125            },
126        }
127    }
128    
129    pub struct TestDatabase {
130        connected: bool,
131    }
132    
133    impl TestDatabase {
134        pub fn new() -> Self {
135            Self { connected: true }
136        }
137    }
138    
139    impl DatabaseConnection for TestDatabase {
140        fn is_connected(&self) -> bool {
141            self.connected
142        }
143        
144        fn execute(&self, _query: &str) -> Result<(), DatabaseError> {
145            if self.connected {
146                Ok(())
147            } else {
148                Err(DatabaseError::ConnectionFailed {
149                    reason: "Database not connected".to_string(),
150                })
151            }
152        }
153    }
154    
155    pub struct TestLogger;
156    
157    impl Logger for TestLogger {
158        fn info(&self, message: &str) {
159            println!("[INFO] {}", message);
160        }
161        
162        fn error(&self, message: &str) {
163            eprintln!("[ERROR] {}", message);
164        }
165        
166        fn debug(&self, message: &str) {
167            println!("[DEBUG] {}", message);
168        }
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use super::test_implementations::*;
176    use std::sync::Arc;
177    
178    #[test]
179    fn test_container_builder() {
180        let config = Arc::new(test_implementations::create_test_config());
181        let database = Arc::new(TestDatabase::new()) as Arc<dyn DatabaseConnection>;
182        let logger = Arc::new(TestLogger) as Arc<dyn Logger>;
183        
184        let container = Container::builder()
185            .config(config)
186            .database(database)
187            .build()
188            .unwrap();
189            
190        assert!(container.validate().is_ok());
191        
192        let config = container.config();
193        assert_eq!(config.name, "test-app");
194        assert_eq!(config.environment, crate::app_config::Environment::Testing);
195        
196        let database = container.database();
197        assert!(database.is_connected());
198        
199        // Test optional services
200        let optional = OptionalServices::new().with_logger(logger);
201        if let Some(logger) = optional.logger {
202            logger.info("Container initialized successfully");
203        }
204    }
205    
206    #[test]
207    fn test_container_validation_missing_services() {
208        // With service-builder, missing required dependencies cause build() to fail
209        // This test verifies that we can't create invalid containers
210        let result = Container::builder().build();
211        assert!(result.is_err());
212    }
213    
214    #[test]
215    fn test_service_resolution() {
216        let config = Arc::new(test_implementations::create_test_config());
217        let database = Arc::new(TestDatabase::new()) as Arc<dyn DatabaseConnection>;
218        
219        let container = Container::builder()
220            .config(config)
221            .database(database)
222            .build()
223            .unwrap();
224            
225        // Test successful resolution
226        let resolved_config = container.config();
227        assert_eq!(resolved_config.name, "test-app");
228        assert_eq!(resolved_config.environment, crate::app_config::Environment::Testing);
229        
230        let resolved_database = container.database();
231        assert!(resolved_database.is_connected());
232        
233        // Test optional services
234        let optional = OptionalServices::new();
235        assert!(optional.cache.is_none());
236        assert!(optional.logger.is_none());
237    }
238}