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
108#[cfg(test)]
109mod test_implementations {
110    use super::*;
111    
112    pub fn create_test_config() -> crate::app_config::AppConfig {
113        crate::app_config::AppConfig {
114            name: "test-app".to_string(),
115            environment: crate::app_config::Environment::Testing,
116            database_url: "sqlite::memory:".to_string(),
117            jwt_secret: Some("test-secret".to_string()),
118            server: crate::app_config::ServerConfig {
119                host: "127.0.0.1".to_string(),
120                port: 8080,
121                workers: 4,
122            },
123            logging: crate::app_config::LoggingConfig {
124                level: "info".to_string(),
125                format: "compact".to_string(),
126            },
127        }
128    }
129    
130    pub struct TestDatabase {
131        connected: bool,
132    }
133    
134    impl TestDatabase {
135        pub fn new() -> Self {
136            Self { connected: true }
137        }
138    }
139    
140    impl DatabaseConnection for TestDatabase {
141        fn is_connected(&self) -> bool {
142            self.connected
143        }
144        
145        fn execute(&self, _query: &str) -> Result<(), DatabaseError> {
146            if self.connected {
147                Ok(())
148            } else {
149                Err(DatabaseError::ConnectionFailed {
150                    reason: "Database not connected".to_string(),
151                })
152            }
153        }
154    }
155    
156    pub struct TestLogger;
157    
158    impl Logger for TestLogger {
159        fn info(&self, message: &str) {
160            println!("[INFO] {}", message);
161        }
162        
163        fn error(&self, message: &str) {
164            eprintln!("[ERROR] {}", message);
165        }
166        
167        fn debug(&self, message: &str) {
168            println!("[DEBUG] {}", message);
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use super::test_implementations::*;
177    use std::sync::Arc;
178    
179    #[test]
180    fn test_container_builder() {
181        let config = Arc::new(test_implementations::create_test_config());
182        let database = Arc::new(TestDatabase::new()) as Arc<dyn DatabaseConnection>;
183        let logger = Arc::new(TestLogger) as Arc<dyn Logger>;
184        
185        let container = Container::builder()
186            .config(config)
187            .database(database)
188            .build()
189            .unwrap();
190            
191        assert!(container.validate().is_ok());
192        
193        let config = container.config();
194        assert_eq!(config.name, "test-app");
195        assert_eq!(config.environment, crate::app_config::Environment::Testing);
196        
197        let database = container.database();
198        assert!(database.is_connected());
199        
200        // Test optional services
201        let optional = OptionalServices::new().with_logger(logger);
202        if let Some(logger) = optional.logger {
203            logger.info("Container initialized successfully");
204        }
205    }
206    
207    #[test]
208    fn test_container_validation_missing_services() {
209        // With service-builder, missing required dependencies cause build() to fail
210        // This test verifies that we can't create invalid containers
211        let result = Container::builder().build();
212        assert!(result.is_err());
213    }
214    
215    #[test]
216    fn test_service_resolution() {
217        let config = Arc::new(test_implementations::create_test_config());
218        let database = Arc::new(TestDatabase::new()) as Arc<dyn DatabaseConnection>;
219        
220        let container = Container::builder()
221            .config(config)
222            .database(database)
223            .build()
224            .unwrap();
225            
226        // Test successful resolution
227        let resolved_config = container.config();
228        assert_eq!(resolved_config.name, "test-app");
229        assert_eq!(resolved_config.environment, crate::app_config::Environment::Testing);
230        
231        let resolved_database = container.database();
232        assert!(resolved_database.is_connected());
233        
234        // Test optional services
235        let optional = OptionalServices::new();
236        assert!(optional.cache.is_none());
237        assert!(optional.logger.is_none());
238    }
239}