1use service_builder::builder;
2use std::sync::Arc;
3use thiserror::Error;
4
5#[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
15pub trait DatabaseConnection: Send + Sync {
17 fn is_connected(&self) -> bool;
18 fn execute(&self, query: &str) -> Result<(), DatabaseError>;
19}
20
21pub trait Cache: Send + Sync {
23 fn get(&self, key: &str) -> Option<String>;
24 fn set(&self, key: &str, value: String) -> Result<(), CacheError>;
25}
26
27pub 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
64pub 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 pub fn validate(&self) -> Result<(), ContainerError> {
92 Ok(())
94 }
95
96 pub fn config(&self) -> Arc<crate::app_config::AppConfig> {
98 self.config.clone()
99 }
100
101 pub fn database(&self) -> Arc<dyn DatabaseConnection> {
103 self.database.clone()
104 }
105}
106
107pub 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 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 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 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 let optional = OptionalServices::new();
235 assert!(optional.cache.is_none());
236 assert!(optional.logger.is_none());
237 }
238}