spikard 0.3.6 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +674 -659
  4. data/ext/spikard_rb/Cargo.toml +17 -17
  5. data/ext/spikard_rb/extconf.rb +13 -10
  6. data/ext/spikard_rb/src/lib.rs +6 -6
  7. data/lib/spikard/app.rb +405 -386
  8. data/lib/spikard/background.rb +27 -27
  9. data/lib/spikard/config.rb +396 -396
  10. data/lib/spikard/converters.rb +13 -13
  11. data/lib/spikard/handler_wrapper.rb +113 -113
  12. data/lib/spikard/provide.rb +214 -214
  13. data/lib/spikard/response.rb +173 -173
  14. data/lib/spikard/schema.rb +243 -243
  15. data/lib/spikard/sse.rb +111 -111
  16. data/lib/spikard/streaming_response.rb +44 -44
  17. data/lib/spikard/testing.rb +256 -221
  18. data/lib/spikard/upload_file.rb +131 -131
  19. data/lib/spikard/version.rb +5 -5
  20. data/lib/spikard/websocket.rb +59 -59
  21. data/lib/spikard.rb +43 -43
  22. data/sig/spikard.rbs +366 -366
  23. data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
  24. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
  25. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
  26. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
  27. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
  28. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
  29. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
  30. data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
  31. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
  32. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
  33. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
  34. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
  35. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
  36. data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
  37. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
  38. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
  39. data/vendor/crates/spikard-core/Cargo.toml +40 -40
  40. data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
  41. data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
  42. data/vendor/crates/spikard-core/src/debug.rs +127 -63
  43. data/vendor/crates/spikard-core/src/di/container.rs +702 -726
  44. data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
  45. data/vendor/crates/spikard-core/src/di/error.rs +118 -118
  46. data/vendor/crates/spikard-core/src/di/factory.rs +534 -538
  47. data/vendor/crates/spikard-core/src/di/graph.rs +506 -545
  48. data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
  49. data/vendor/crates/spikard-core/src/di/resolved.rs +405 -411
  50. data/vendor/crates/spikard-core/src/di/value.rs +281 -283
  51. data/vendor/crates/spikard-core/src/errors.rs +69 -39
  52. data/vendor/crates/spikard-core/src/http.rs +415 -153
  53. data/vendor/crates/spikard-core/src/lib.rs +29 -29
  54. data/vendor/crates/spikard-core/src/lifecycle.rs +1186 -422
  55. data/vendor/crates/spikard-core/src/metadata.rs +389 -0
  56. data/vendor/crates/spikard-core/src/parameters.rs +2525 -722
  57. data/vendor/crates/spikard-core/src/problem.rs +344 -310
  58. data/vendor/crates/spikard-core/src/request_data.rs +1154 -189
  59. data/vendor/crates/spikard-core/src/router.rs +510 -249
  60. data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
  61. data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
  62. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +696 -0
  63. data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +457 -699
  64. data/vendor/crates/spikard-http/Cargo.toml +62 -68
  65. data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
  66. data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
  67. data/vendor/crates/spikard-http/src/auth.rs +296 -247
  68. data/vendor/crates/spikard-http/src/background.rs +1860 -249
  69. data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
  70. data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
  71. data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
  72. data/vendor/crates/spikard-http/src/cors.rs +1005 -490
  73. data/vendor/crates/spikard-http/src/debug.rs +128 -63
  74. data/vendor/crates/spikard-http/src/di_handler.rs +1668 -423
  75. data/vendor/crates/spikard-http/src/handler_response.rs +901 -190
  76. data/vendor/crates/spikard-http/src/handler_trait.rs +838 -228
  77. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +290 -284
  78. data/vendor/crates/spikard-http/src/lib.rs +534 -529
  79. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +230 -149
  80. data/vendor/crates/spikard-http/src/lifecycle.rs +1193 -428
  81. data/vendor/crates/spikard-http/src/middleware/mod.rs +560 -285
  82. data/vendor/crates/spikard-http/src/middleware/multipart.rs +912 -86
  83. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +513 -147
  84. data/vendor/crates/spikard-http/src/middleware/validation.rs +768 -287
  85. data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
  86. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -190
  87. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1363 -308
  88. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +665 -195
  89. data/vendor/crates/spikard-http/src/query_parser.rs +793 -369
  90. data/vendor/crates/spikard-http/src/response.rs +720 -399
  91. data/vendor/crates/spikard-http/src/server/handler.rs +1650 -87
  92. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +234 -98
  93. data/vendor/crates/spikard-http/src/server/mod.rs +1593 -805
  94. data/vendor/crates/spikard-http/src/server/request_extraction.rs +789 -119
  95. data/vendor/crates/spikard-http/src/server/routing_factory.rs +629 -0
  96. data/vendor/crates/spikard-http/src/sse.rs +1409 -447
  97. data/vendor/crates/spikard-http/src/testing/form.rs +52 -14
  98. data/vendor/crates/spikard-http/src/testing/multipart.rs +64 -60
  99. data/vendor/crates/spikard-http/src/testing/test_client.rs +311 -285
  100. data/vendor/crates/spikard-http/src/testing.rs +406 -377
  101. data/vendor/crates/spikard-http/src/websocket.rs +1404 -324
  102. data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
  103. data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
  104. data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
  105. data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
  106. data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
  107. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
  108. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
  109. data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
  110. data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
  111. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
  112. data/vendor/crates/spikard-rb/Cargo.toml +48 -42
  113. data/vendor/crates/spikard-rb/build.rs +199 -8
  114. data/vendor/crates/spikard-rb/src/background.rs +63 -63
  115. data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
  116. data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +285 -294
  117. data/vendor/crates/spikard-rb/src/conversion.rs +554 -453
  118. data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
  119. data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +375 -409
  120. data/vendor/crates/spikard-rb/src/handler.rs +618 -625
  121. data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
  122. data/vendor/crates/spikard-rb/src/lib.rs +1806 -2771
  123. data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -274
  124. data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
  125. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +442 -0
  126. data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
  127. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
  128. data/vendor/crates/spikard-rb/src/server.rs +305 -283
  129. data/vendor/crates/spikard-rb/src/sse.rs +231 -231
  130. data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +538 -404
  131. data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
  132. data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +143 -143
  133. data/vendor/crates/spikard-rb/src/testing/websocket.rs +608 -0
  134. data/vendor/crates/spikard-rb/src/websocket.rs +377 -233
  135. metadata +60 -13
  136. data/vendor/crates/spikard-http/src/parameters.rs +0 -1
  137. data/vendor/crates/spikard-http/src/problem.rs +0 -1
  138. data/vendor/crates/spikard-http/src/router.rs +0 -1
  139. data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
  140. data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
  141. data/vendor/crates/spikard-http/src/validation.rs +0 -1
  142. data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
@@ -1,423 +1,1668 @@
1
- //! Dependency Injection Handler Wrapper
2
- //!
3
- //! This module provides a handler wrapper that integrates the DI system with the HTTP
4
- //! handler pipeline. It follows the same composition pattern as `ValidatingHandler`.
5
- //!
6
- //! # Architecture
7
- //!
8
- //! The `DependencyInjectingHandler` wraps any `Handler` and:
9
- //! 1. Resolves required dependencies in parallel batches before calling the handler
10
- //! 2. Attaches resolved dependencies to `RequestData`
11
- //! 3. Calls the inner handler with the enriched request data
12
- //! 4. Cleans up dependencies after the handler completes (async Drop pattern)
13
- //!
14
- //! # Performance
15
- //!
16
- //! - **Zero overhead when no DI**: If no container is provided, DI is skipped entirely
17
- //! - **Parallel resolution**: Independent dependencies are resolved concurrently
18
- //! - **Efficient caching**: Singleton and per-request caching minimize redundant work
19
- //! - **Composable**: Works seamlessly with `ValidatingHandler` and lifecycle hooks
20
- //!
21
- //! # Examples
22
- //!
23
- //! ```ignore
24
- //! use spikard_http::di_handler::DependencyInjectingHandler;
25
- //! use spikard_core::di::DependencyContainer;
26
- //! use std::sync::Arc;
27
- //!
28
- //! # tokio_test::block_on(async {
29
- //! let container = Arc::new(DependencyContainer::new());
30
- //! let handler = Arc::new(MyHandler::new());
31
- //!
32
- //! let di_handler = DependencyInjectingHandler::new(
33
- //! handler,
34
- //! container,
35
- //! vec!["database".to_string(), "cache".to_string()],
36
- //! );
37
- //! # });
38
- //! ```
39
-
40
- use crate::handler_trait::{Handler, HandlerResult, RequestData};
41
- use axum::body::Body;
42
- use axum::http::{Request, StatusCode};
43
- use spikard_core::di::{DependencyContainer, DependencyError};
44
- use std::future::Future;
45
- use std::pin::Pin;
46
- use std::sync::Arc;
47
- use tracing::{debug, info_span, instrument};
48
-
49
- /// Handler wrapper that resolves dependencies before calling the inner handler
50
- ///
51
- /// This wrapper follows the composition pattern used by `ValidatingHandler`:
52
- /// it wraps an existing handler and enriches the request with resolved dependencies.
53
- ///
54
- /// # Thread Safety
55
- ///
56
- /// This struct is `Send + Sync` and can be safely shared across threads.
57
- /// The container is shared via `Arc`, and all dependencies must be `Send + Sync`.
58
- pub struct DependencyInjectingHandler {
59
- /// The wrapped handler that will receive the enriched request
60
- inner: Arc<dyn Handler>,
61
- /// Shared dependency container for resolution
62
- container: Arc<DependencyContainer>,
63
- /// List of dependency names required by this handler
64
- required_dependencies: Vec<String>,
65
- }
66
-
67
- impl DependencyInjectingHandler {
68
- /// Create a new dependency-injecting handler wrapper
69
- ///
70
- /// # Arguments
71
- ///
72
- /// * `handler` - The handler to wrap
73
- /// * `container` - Shared dependency container
74
- /// * `required_dependencies` - Names of dependencies to resolve for this handler
75
- ///
76
- /// # Examples
77
- ///
78
- /// ```ignore
79
- /// use spikard_http::di_handler::DependencyInjectingHandler;
80
- /// use spikard_core::di::DependencyContainer;
81
- /// use std::sync::Arc;
82
- ///
83
- /// # tokio_test::block_on(async {
84
- /// let container = Arc::new(DependencyContainer::new());
85
- /// let handler = Arc::new(MyHandler::new());
86
- ///
87
- /// let di_handler = DependencyInjectingHandler::new(
88
- /// handler,
89
- /// container,
90
- /// vec!["db".to_string()],
91
- /// );
92
- /// # });
93
- /// ```
94
- pub fn new(
95
- handler: Arc<dyn Handler>,
96
- container: Arc<DependencyContainer>,
97
- required_dependencies: Vec<String>,
98
- ) -> Self {
99
- Self {
100
- inner: handler,
101
- container,
102
- required_dependencies,
103
- }
104
- }
105
-
106
- /// Get the list of required dependencies
107
- pub fn required_dependencies(&self) -> &[String] {
108
- &self.required_dependencies
109
- }
110
- }
111
-
112
- impl Handler for DependencyInjectingHandler {
113
- #[instrument(
114
- skip(self, request, request_data),
115
- fields(
116
- required_deps = %self.required_dependencies.len(),
117
- deps = ?self.required_dependencies
118
- )
119
- )]
120
- fn call(
121
- &self,
122
- request: Request<Body>,
123
- mut request_data: RequestData,
124
- ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
125
- eprintln!(
126
- "[spikard-di] entering DI handler, required_deps={:?}",
127
- self.required_dependencies
128
- );
129
- let inner = self.inner.clone();
130
- let container = self.container.clone();
131
- let required_dependencies = self.required_dependencies.clone();
132
-
133
- Box::pin(async move {
134
- debug!(
135
- "DI handler invoked for {} deps; container keys: {:?}",
136
- required_dependencies.len(),
137
- container.keys()
138
- );
139
- // Span for dependency resolution timing
140
- let resolution_span = info_span!(
141
- "resolve_dependencies",
142
- count = %required_dependencies.len()
143
- );
144
- let _enter = resolution_span.enter();
145
-
146
- debug!(
147
- "Resolving {} dependencies: {:?}",
148
- required_dependencies.len(),
149
- required_dependencies
150
- );
151
-
152
- let start = std::time::Instant::now();
153
-
154
- // Convert RequestData to spikard_core::RequestData for DI
155
- let core_request_data = spikard_core::RequestData {
156
- path_params: Arc::clone(&request_data.path_params),
157
- query_params: request_data.query_params.clone(),
158
- raw_query_params: Arc::clone(&request_data.raw_query_params),
159
- body: request_data.body.clone(),
160
- raw_body: request_data.raw_body.clone(),
161
- headers: Arc::clone(&request_data.headers),
162
- cookies: Arc::clone(&request_data.cookies),
163
- method: request_data.method.clone(),
164
- path: request_data.path.clone(),
165
- #[cfg(feature = "di")]
166
- dependencies: None,
167
- };
168
-
169
- // Convert Request<Body> to Request<()> for DI (body not needed for resolution)
170
- let (parts, _body) = request.into_parts();
171
- let core_request = Request::from_parts(parts.clone(), ());
172
-
173
- // Restore original request for handler
174
- let request = Request::from_parts(parts, axum::body::Body::default());
175
-
176
- // Resolve dependencies in parallel batches
177
- let resolved = match container
178
- .resolve_for_handler(&required_dependencies, &core_request, &core_request_data)
179
- .await
180
- {
181
- Ok(resolved) => resolved,
182
- Err(e) => {
183
- debug!("DI error: {}", e);
184
-
185
- // Convert DI errors to proper JSON HTTP responses
186
- let (status, json_body) = match e {
187
- DependencyError::NotFound { ref key } => {
188
- let body = serde_json::json!({
189
- "detail": "Required dependency not found",
190
- "errors": [{
191
- "dependency_key": key,
192
- "msg": format!("Dependency '{}' is not registered", key),
193
- "type": "missing_dependency"
194
- }],
195
- "status": 500,
196
- "title": "Dependency Resolution Failed",
197
- "type": "https://spikard.dev/errors/dependency-error"
198
- });
199
- (StatusCode::INTERNAL_SERVER_ERROR, body)
200
- }
201
- DependencyError::CircularDependency { ref cycle } => {
202
- let body = serde_json::json!({
203
- "detail": "Circular dependency detected",
204
- "errors": [{
205
- "cycle": cycle,
206
- "msg": "Circular dependency detected in dependency graph",
207
- "type": "circular_dependency"
208
- }],
209
- "status": 500,
210
- "title": "Dependency Resolution Failed",
211
- "type": "https://spikard.dev/errors/dependency-error"
212
- });
213
- (StatusCode::INTERNAL_SERVER_ERROR, body)
214
- }
215
- DependencyError::ResolutionFailed { ref message } => {
216
- let body = serde_json::json!({
217
- "detail": "Dependency resolution failed",
218
- "errors": [{
219
- "msg": message,
220
- "type": "resolution_failed"
221
- }],
222
- "status": 503,
223
- "title": "Service Unavailable",
224
- "type": "https://spikard.dev/errors/dependency-error"
225
- });
226
- (StatusCode::SERVICE_UNAVAILABLE, body)
227
- }
228
- _ => {
229
- let body = serde_json::json!({
230
- "detail": "Dependency resolution failed",
231
- "errors": [{
232
- "msg": e.to_string(),
233
- "type": "unknown"
234
- }],
235
- "status": 500,
236
- "title": "Dependency Resolution Failed",
237
- "type": "https://spikard.dev/errors/dependency-error"
238
- });
239
- (StatusCode::INTERNAL_SERVER_ERROR, body)
240
- }
241
- };
242
-
243
- // Return JSON error response
244
- let response = axum::http::Response::builder()
245
- .status(status)
246
- .header("Content-Type", "application/json")
247
- .body(Body::from(json_body.to_string()))
248
- .unwrap();
249
-
250
- return Ok(response);
251
- }
252
- };
253
-
254
- let duration = start.elapsed();
255
- debug!(
256
- "Dependencies resolved in {:?} ({} dependencies)",
257
- duration,
258
- required_dependencies.len()
259
- );
260
-
261
- drop(_enter);
262
-
263
- // Attach resolved dependencies to request_data
264
- request_data.dependencies = Some(Arc::new(resolved));
265
-
266
- // Call the inner handler with enriched request data
267
- let result = inner.call(request, request_data.clone()).await;
268
-
269
- // Cleanup: Execute cleanup tasks after handler completes
270
- // This implements the async Drop pattern for generator-style dependencies
271
- if let Some(deps) = request_data.dependencies.take() {
272
- // Try to get exclusive ownership for cleanup
273
- if let Ok(deps) = Arc::try_unwrap(deps) {
274
- let cleanup_span = info_span!("cleanup_dependencies");
275
- let _enter = cleanup_span.enter();
276
-
277
- debug!("Running dependency cleanup tasks");
278
- deps.cleanup().await;
279
- } else {
280
- // Dependencies are still shared (shouldn't happen in normal flow)
281
- debug!("Skipping cleanup: dependencies still shared");
282
- }
283
- }
284
-
285
- result
286
- })
287
- }
288
- }
289
-
290
- #[cfg(test)]
291
- mod tests {
292
- use super::*;
293
- use crate::handler_trait::RequestData;
294
- use axum::http::Response;
295
- use spikard_core::di::ValueDependency;
296
- use std::collections::HashMap;
297
-
298
- /// Test handler that checks for dependency presence
299
- struct TestHandler;
300
-
301
- impl Handler for TestHandler {
302
- fn call(
303
- &self,
304
- _request: Request<Body>,
305
- request_data: RequestData,
306
- ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
307
- Box::pin(async move {
308
- // Verify dependencies are present
309
- if request_data.dependencies.is_some() {
310
- let response = Response::builder()
311
- .status(StatusCode::OK)
312
- .body(Body::from("dependencies present"))
313
- .unwrap();
314
- Ok(response)
315
- } else {
316
- Err((StatusCode::INTERNAL_SERVER_ERROR, "no dependencies".to_string()))
317
- }
318
- })
319
- }
320
- }
321
-
322
- #[tokio::test]
323
- async fn test_di_handler_resolves_dependencies() {
324
- // Setup
325
- let mut container = DependencyContainer::new();
326
- container
327
- .register(
328
- "config".to_string(),
329
- Arc::new(ValueDependency::new("config", "test_value")),
330
- )
331
- .unwrap();
332
-
333
- let handler = Arc::new(TestHandler);
334
- let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
335
-
336
- // Execute
337
- let request = Request::builder().body(Body::empty()).unwrap();
338
- let request_data = RequestData {
339
- path_params: Arc::new(HashMap::new()),
340
- query_params: serde_json::Value::Null,
341
- raw_query_params: Arc::new(HashMap::new()),
342
- body: serde_json::Value::Null,
343
- raw_body: None,
344
- headers: Arc::new(HashMap::new()),
345
- cookies: Arc::new(HashMap::new()),
346
- method: "GET".to_string(),
347
- path: "/".to_string(),
348
- #[cfg(feature = "di")]
349
- dependencies: None,
350
- };
351
-
352
- let result = di_handler.call(request, request_data).await;
353
-
354
- // Verify
355
- assert!(result.is_ok());
356
- let response = result.unwrap();
357
- assert_eq!(response.status(), StatusCode::OK);
358
- }
359
-
360
- #[tokio::test]
361
- async fn test_di_handler_error_on_missing_dependency() {
362
- // Setup: empty container, but handler requires "database"
363
- let container = DependencyContainer::new();
364
- let handler = Arc::new(TestHandler);
365
- let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["database".to_string()]);
366
-
367
- // Execute
368
- let request = Request::builder().body(Body::empty()).unwrap();
369
- let request_data = RequestData {
370
- path_params: Arc::new(HashMap::new()),
371
- query_params: serde_json::Value::Null,
372
- raw_query_params: Arc::new(HashMap::new()),
373
- body: serde_json::Value::Null,
374
- raw_body: None,
375
- headers: Arc::new(HashMap::new()),
376
- cookies: Arc::new(HashMap::new()),
377
- method: "GET".to_string(),
378
- path: "/".to_string(),
379
- #[cfg(feature = "di")]
380
- dependencies: None,
381
- };
382
-
383
- let result = di_handler.call(request, request_data).await;
384
-
385
- // Verify: should return structured error response
386
- assert!(result.is_ok());
387
- let response = result.unwrap();
388
- assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
389
- }
390
-
391
- #[tokio::test]
392
- async fn test_di_handler_empty_dependencies() {
393
- // Setup: no dependencies required
394
- let container = DependencyContainer::new();
395
- let handler = Arc::new(TestHandler);
396
- let di_handler = DependencyInjectingHandler::new(
397
- handler,
398
- Arc::new(container),
399
- vec![], // No dependencies
400
- );
401
-
402
- // Execute
403
- let request = Request::builder().body(Body::empty()).unwrap();
404
- let request_data = RequestData {
405
- path_params: Arc::new(HashMap::new()),
406
- query_params: serde_json::Value::Null,
407
- raw_query_params: Arc::new(HashMap::new()),
408
- body: serde_json::Value::Null,
409
- raw_body: None,
410
- headers: Arc::new(HashMap::new()),
411
- cookies: Arc::new(HashMap::new()),
412
- method: "GET".to_string(),
413
- path: "/".to_string(),
414
- #[cfg(feature = "di")]
415
- dependencies: None,
416
- };
417
-
418
- let result = di_handler.call(request, request_data).await;
419
-
420
- // Verify: should succeed even with empty dependencies
421
- assert!(result.is_ok());
422
- }
423
- }
1
+ //! Dependency Injection Handler Wrapper
2
+ //!
3
+ //! This module provides a handler wrapper that integrates the DI system with the HTTP
4
+ //! handler pipeline. It follows the same composition pattern as `ValidatingHandler`.
5
+ //!
6
+ //! # Architecture
7
+ //!
8
+ //! The `DependencyInjectingHandler` wraps any `Handler` and:
9
+ //! 1. Resolves required dependencies in parallel batches before calling the handler
10
+ //! 2. Attaches resolved dependencies to `RequestData`
11
+ //! 3. Calls the inner handler with the enriched request data
12
+ //! 4. Cleans up dependencies after the handler completes (async Drop pattern)
13
+ //!
14
+ //! # Performance
15
+ //!
16
+ //! - **Zero overhead when no DI**: If no container is provided, DI is skipped entirely
17
+ //! - **Parallel resolution**: Independent dependencies are resolved concurrently
18
+ //! - **Efficient caching**: Singleton and per-request caching minimize redundant work
19
+ //! - **Composable**: Works seamlessly with `ValidatingHandler` and lifecycle hooks
20
+ //!
21
+ //! # Examples
22
+ //!
23
+ //! ```ignore
24
+ //! use spikard_http::di_handler::DependencyInjectingHandler;
25
+ //! use spikard_core::di::DependencyContainer;
26
+ //! use std::sync::Arc;
27
+ //!
28
+ //! # tokio_test::block_on(async {
29
+ //! let container = Arc::new(DependencyContainer::new());
30
+ //! let handler = Arc::new(MyHandler::new());
31
+ //!
32
+ //! let di_handler = DependencyInjectingHandler::new(
33
+ //! handler,
34
+ //! container,
35
+ //! vec!["database".to_string(), "cache".to_string()],
36
+ //! );
37
+ //! # });
38
+ //! ```
39
+
40
+ use crate::handler_trait::{Handler, HandlerResult, RequestData};
41
+ use axum::body::Body;
42
+ use axum::http::{Request, StatusCode};
43
+ use spikard_core::di::{DependencyContainer, DependencyError};
44
+ use std::future::Future;
45
+ use std::pin::Pin;
46
+ use std::sync::Arc;
47
+ use tracing::{debug, info_span, instrument};
48
+
49
+ /// Handler wrapper that resolves dependencies before calling the inner handler
50
+ ///
51
+ /// This wrapper follows the composition pattern used by `ValidatingHandler`:
52
+ /// it wraps an existing handler and enriches the request with resolved dependencies.
53
+ ///
54
+ /// # Thread Safety
55
+ ///
56
+ /// This struct is `Send + Sync` and can be safely shared across threads.
57
+ /// The container is shared via `Arc`, and all dependencies must be `Send + Sync`.
58
+ pub struct DependencyInjectingHandler {
59
+ /// The wrapped handler that will receive the enriched request
60
+ inner: Arc<dyn Handler>,
61
+ /// Shared dependency container for resolution
62
+ container: Arc<DependencyContainer>,
63
+ /// List of dependency names required by this handler
64
+ required_dependencies: Vec<String>,
65
+ }
66
+
67
+ impl DependencyInjectingHandler {
68
+ /// Create a new dependency-injecting handler wrapper
69
+ ///
70
+ /// # Arguments
71
+ ///
72
+ /// * `handler` - The handler to wrap
73
+ /// * `container` - Shared dependency container
74
+ /// * `required_dependencies` - Names of dependencies to resolve for this handler
75
+ ///
76
+ /// # Examples
77
+ ///
78
+ /// ```ignore
79
+ /// use spikard_http::di_handler::DependencyInjectingHandler;
80
+ /// use spikard_core::di::DependencyContainer;
81
+ /// use std::sync::Arc;
82
+ ///
83
+ /// # tokio_test::block_on(async {
84
+ /// let container = Arc::new(DependencyContainer::new());
85
+ /// let handler = Arc::new(MyHandler::new());
86
+ ///
87
+ /// let di_handler = DependencyInjectingHandler::new(
88
+ /// handler,
89
+ /// container,
90
+ /// vec!["db".to_string()],
91
+ /// );
92
+ /// # });
93
+ /// ```
94
+ pub fn new(
95
+ handler: Arc<dyn Handler>,
96
+ container: Arc<DependencyContainer>,
97
+ required_dependencies: Vec<String>,
98
+ ) -> Self {
99
+ Self {
100
+ inner: handler,
101
+ container,
102
+ required_dependencies,
103
+ }
104
+ }
105
+
106
+ /// Get the list of required dependencies
107
+ pub fn required_dependencies(&self) -> &[String] {
108
+ &self.required_dependencies
109
+ }
110
+ }
111
+
112
+ impl Handler for DependencyInjectingHandler {
113
+ #[instrument(
114
+ skip(self, request, request_data),
115
+ fields(
116
+ required_deps = %self.required_dependencies.len(),
117
+ deps = ?self.required_dependencies
118
+ )
119
+ )]
120
+ fn call(
121
+ &self,
122
+ request: Request<Body>,
123
+ mut request_data: RequestData,
124
+ ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
125
+ tracing::debug!(
126
+ target = "spikard::di",
127
+ required_deps = ?self.required_dependencies,
128
+ "entering DI handler"
129
+ );
130
+ let inner = self.inner.clone();
131
+ let container = self.container.clone();
132
+ let required_dependencies = self.required_dependencies.clone();
133
+
134
+ Box::pin(async move {
135
+ debug!(
136
+ "DI handler invoked for {} deps; container keys: {:?}",
137
+ required_dependencies.len(),
138
+ container.keys()
139
+ );
140
+ let resolution_span = info_span!(
141
+ "resolve_dependencies",
142
+ count = %required_dependencies.len()
143
+ );
144
+ let _enter = resolution_span.enter();
145
+
146
+ debug!(
147
+ "Resolving {} dependencies: {:?}",
148
+ required_dependencies.len(),
149
+ required_dependencies
150
+ );
151
+
152
+ let start = std::time::Instant::now();
153
+
154
+ let core_request_data = spikard_core::RequestData {
155
+ path_params: Arc::clone(&request_data.path_params),
156
+ query_params: request_data.query_params.clone(),
157
+ validated_params: request_data.validated_params.clone(),
158
+ raw_query_params: Arc::clone(&request_data.raw_query_params),
159
+ body: request_data.body.clone(),
160
+ raw_body: request_data.raw_body.clone(),
161
+ headers: Arc::clone(&request_data.headers),
162
+ cookies: Arc::clone(&request_data.cookies),
163
+ method: request_data.method.clone(),
164
+ path: request_data.path.clone(),
165
+ #[cfg(feature = "di")]
166
+ dependencies: None,
167
+ };
168
+
169
+ let (parts, _body) = request.into_parts();
170
+ let core_request = Request::from_parts(parts.clone(), ());
171
+
172
+ let request = Request::from_parts(parts, axum::body::Body::default());
173
+
174
+ let resolved = match container
175
+ .resolve_for_handler(&required_dependencies, &core_request, &core_request_data)
176
+ .await
177
+ {
178
+ Ok(resolved) => resolved,
179
+ Err(e) => {
180
+ debug!("DI error: {}", e);
181
+
182
+ let (status, json_body) = match e {
183
+ DependencyError::NotFound { ref key } => {
184
+ let body = serde_json::json!({
185
+ "detail": "Required dependency not found",
186
+ "errors": [{
187
+ "dependency_key": key,
188
+ "msg": format!("Dependency '{}' is not registered", key),
189
+ "type": "missing_dependency"
190
+ }],
191
+ "status": 500,
192
+ "title": "Dependency Resolution Failed",
193
+ "type": "https://spikard.dev/errors/dependency-error"
194
+ });
195
+ (StatusCode::INTERNAL_SERVER_ERROR, body)
196
+ }
197
+ DependencyError::CircularDependency { ref cycle } => {
198
+ let body = serde_json::json!({
199
+ "detail": "Circular dependency detected",
200
+ "errors": [{
201
+ "cycle": cycle,
202
+ "msg": "Circular dependency detected in dependency graph",
203
+ "type": "circular_dependency"
204
+ }],
205
+ "status": 500,
206
+ "title": "Dependency Resolution Failed",
207
+ "type": "https://spikard.dev/errors/dependency-error"
208
+ });
209
+ (StatusCode::INTERNAL_SERVER_ERROR, body)
210
+ }
211
+ DependencyError::ResolutionFailed { ref message } => {
212
+ let body = serde_json::json!({
213
+ "detail": "Dependency resolution failed",
214
+ "errors": [{
215
+ "msg": message,
216
+ "type": "resolution_failed"
217
+ }],
218
+ "status": 503,
219
+ "title": "Service Unavailable",
220
+ "type": "https://spikard.dev/errors/dependency-error"
221
+ });
222
+ (StatusCode::SERVICE_UNAVAILABLE, body)
223
+ }
224
+ _ => {
225
+ let body = serde_json::json!({
226
+ "detail": "Dependency resolution failed",
227
+ "errors": [{
228
+ "msg": e.to_string(),
229
+ "type": "unknown"
230
+ }],
231
+ "status": 500,
232
+ "title": "Dependency Resolution Failed",
233
+ "type": "https://spikard.dev/errors/dependency-error"
234
+ });
235
+ (StatusCode::INTERNAL_SERVER_ERROR, body)
236
+ }
237
+ };
238
+
239
+ let response = axum::http::Response::builder()
240
+ .status(status)
241
+ .header("Content-Type", "application/json")
242
+ .body(Body::from(json_body.to_string()))
243
+ .unwrap();
244
+
245
+ return Ok(response);
246
+ }
247
+ };
248
+
249
+ let duration = start.elapsed();
250
+ debug!(
251
+ "Dependencies resolved in {:?} ({} dependencies)",
252
+ duration,
253
+ required_dependencies.len()
254
+ );
255
+
256
+ drop(_enter);
257
+
258
+ let deps = Arc::new(resolved);
259
+ request_data.dependencies = Some(Arc::clone(&deps));
260
+
261
+ let result = inner.call(request, request_data).await;
262
+
263
+ if let Ok(deps) = Arc::try_unwrap(deps) {
264
+ let cleanup_span = info_span!("cleanup_dependencies");
265
+ let _enter = cleanup_span.enter();
266
+
267
+ debug!("Running dependency cleanup tasks");
268
+ deps.cleanup().await;
269
+ } else {
270
+ debug!("Skipping cleanup: dependencies still shared");
271
+ }
272
+
273
+ result
274
+ })
275
+ }
276
+ }
277
+
278
+ #[cfg(test)]
279
+ mod tests {
280
+ use super::*;
281
+ use crate::handler_trait::RequestData;
282
+ use axum::http::Response;
283
+ use spikard_core::di::ValueDependency;
284
+ use std::collections::HashMap;
285
+
286
+ /// Test handler that checks for dependency presence
287
+ struct TestHandler;
288
+
289
+ impl Handler for TestHandler {
290
+ fn call(
291
+ &self,
292
+ _request: Request<Body>,
293
+ request_data: RequestData,
294
+ ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
295
+ Box::pin(async move {
296
+ if request_data.dependencies.is_some() {
297
+ let response = Response::builder()
298
+ .status(StatusCode::OK)
299
+ .body(Body::from("dependencies present"))
300
+ .unwrap();
301
+ Ok(response)
302
+ } else {
303
+ Err((StatusCode::INTERNAL_SERVER_ERROR, "no dependencies".to_string()))
304
+ }
305
+ })
306
+ }
307
+ }
308
+
309
+ /// Handler that returns error to test error propagation
310
+ struct ErrorHandler;
311
+
312
+ impl Handler for ErrorHandler {
313
+ fn call(
314
+ &self,
315
+ _request: Request<Body>,
316
+ _request_data: RequestData,
317
+ ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
318
+ Box::pin(async move { Err((StatusCode::INTERNAL_SERVER_ERROR, "inner handler error".to_string())) })
319
+ }
320
+ }
321
+
322
+ /// Handler that reads and validates dependency values
323
+ struct ReadDependencyHandler;
324
+
325
+ impl Handler for ReadDependencyHandler {
326
+ fn call(
327
+ &self,
328
+ _request: Request<Body>,
329
+ request_data: RequestData,
330
+ ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
331
+ Box::pin(async move {
332
+ if request_data.dependencies.is_some() {
333
+ let response = Response::builder()
334
+ .status(StatusCode::OK)
335
+ .body(Body::from("dependencies resolved and accessible"))
336
+ .unwrap();
337
+ Ok(response)
338
+ } else {
339
+ Err((StatusCode::INTERNAL_SERVER_ERROR, "no dependencies".to_string()))
340
+ }
341
+ })
342
+ }
343
+ }
344
+
345
+ /// Helper function to create a basic RequestData
346
+ fn create_request_data() -> RequestData {
347
+ RequestData {
348
+ path_params: Arc::new(HashMap::new()),
349
+ query_params: serde_json::Value::Null,
350
+ validated_params: None,
351
+ raw_query_params: Arc::new(HashMap::new()),
352
+ body: serde_json::Value::Null,
353
+ raw_body: None,
354
+ headers: Arc::new(HashMap::new()),
355
+ cookies: Arc::new(HashMap::new()),
356
+ method: "GET".to_string(),
357
+ path: "/".to_string(),
358
+ #[cfg(feature = "di")]
359
+ dependencies: None,
360
+ }
361
+ }
362
+
363
+ #[tokio::test]
364
+ async fn test_di_handler_resolves_dependencies() {
365
+ let mut container = DependencyContainer::new();
366
+ container
367
+ .register(
368
+ "config".to_string(),
369
+ Arc::new(ValueDependency::new("config", "test_value")),
370
+ )
371
+ .unwrap();
372
+
373
+ let handler = Arc::new(TestHandler);
374
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
375
+
376
+ let request = Request::builder().body(Body::empty()).unwrap();
377
+ let request_data = create_request_data();
378
+
379
+ let result = di_handler.call(request, request_data).await;
380
+
381
+ assert!(result.is_ok());
382
+ let response = result.unwrap();
383
+ assert_eq!(response.status(), StatusCode::OK);
384
+ }
385
+
386
+ #[tokio::test]
387
+ async fn test_di_handler_error_on_missing_dependency() {
388
+ let container = DependencyContainer::new();
389
+ let handler = Arc::new(TestHandler);
390
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["database".to_string()]);
391
+
392
+ let request = Request::builder().body(Body::empty()).unwrap();
393
+ let request_data = create_request_data();
394
+
395
+ let result = di_handler.call(request, request_data).await;
396
+
397
+ assert!(result.is_ok());
398
+ let response = result.unwrap();
399
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
400
+ }
401
+
402
+ #[tokio::test]
403
+ async fn test_di_handler_empty_dependencies() {
404
+ let container = DependencyContainer::new();
405
+ let handler = Arc::new(TestHandler);
406
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec![]);
407
+
408
+ let request = Request::builder().body(Body::empty()).unwrap();
409
+ let request_data = create_request_data();
410
+
411
+ let result = di_handler.call(request, request_data).await;
412
+
413
+ assert!(result.is_ok());
414
+ }
415
+
416
+ #[tokio::test]
417
+ async fn test_di_handler_multiple_dependencies() {
418
+ let mut container = DependencyContainer::new();
419
+ container
420
+ .register("db".to_string(), Arc::new(ValueDependency::new("db", "postgresql")))
421
+ .unwrap();
422
+ container
423
+ .register("cache".to_string(), Arc::new(ValueDependency::new("cache", "redis")))
424
+ .unwrap();
425
+ container
426
+ .register("logger".to_string(), Arc::new(ValueDependency::new("logger", "slog")))
427
+ .unwrap();
428
+ container
429
+ .register(
430
+ "config".to_string(),
431
+ Arc::new(ValueDependency::new("config", "config_data")),
432
+ )
433
+ .unwrap();
434
+
435
+ let handler = Arc::new(TestHandler);
436
+ let di_handler = DependencyInjectingHandler::new(
437
+ handler,
438
+ Arc::new(container),
439
+ vec![
440
+ "db".to_string(),
441
+ "cache".to_string(),
442
+ "logger".to_string(),
443
+ "config".to_string(),
444
+ ],
445
+ );
446
+
447
+ let request = Request::builder().body(Body::empty()).unwrap();
448
+ let request_data = create_request_data();
449
+ let result = di_handler.call(request, request_data).await;
450
+
451
+ assert!(result.is_ok());
452
+ let response = result.unwrap();
453
+ assert_eq!(response.status(), StatusCode::OK);
454
+ }
455
+
456
+ #[tokio::test]
457
+ async fn test_di_handler_required_dependencies_getter() {
458
+ let container = DependencyContainer::new();
459
+ let handler = Arc::new(TestHandler);
460
+ let deps = vec!["db".to_string(), "cache".to_string(), "logger".to_string()];
461
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), deps.clone());
462
+
463
+ assert_eq!(di_handler.required_dependencies(), deps.as_slice());
464
+ }
465
+
466
+ #[tokio::test]
467
+ async fn test_di_handler_handler_error_propagation() {
468
+ let mut container = DependencyContainer::new();
469
+ container
470
+ .register(
471
+ "config".to_string(),
472
+ Arc::new(ValueDependency::new("config", "test_value")),
473
+ )
474
+ .unwrap();
475
+
476
+ let handler = Arc::new(ErrorHandler);
477
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
478
+
479
+ let request = Request::builder().body(Body::empty()).unwrap();
480
+ let request_data = create_request_data();
481
+ let result = di_handler.call(request, request_data).await;
482
+
483
+ assert!(result.is_err());
484
+ let (status, msg) = result.unwrap_err();
485
+ assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
486
+ assert!(msg.contains("inner handler error"));
487
+ }
488
+
489
+ #[tokio::test]
490
+ async fn test_di_handler_request_data_enrichment() {
491
+ let mut container = DependencyContainer::new();
492
+ container
493
+ .register(
494
+ "service".to_string(),
495
+ Arc::new(ValueDependency::new("service", "my_service")),
496
+ )
497
+ .unwrap();
498
+
499
+ let handler = Arc::new(ReadDependencyHandler);
500
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["service".to_string()]);
501
+
502
+ let request = Request::builder().body(Body::empty()).unwrap();
503
+ let request_data = create_request_data();
504
+ let result = di_handler.call(request, request_data).await;
505
+
506
+ assert!(result.is_ok());
507
+ let response = result.unwrap();
508
+ assert_eq!(response.status(), StatusCode::OK);
509
+ }
510
+
511
+ #[tokio::test]
512
+ async fn test_di_handler_missing_dependency_json_structure() {
513
+ let container = DependencyContainer::new();
514
+ let handler = Arc::new(TestHandler);
515
+ let di_handler =
516
+ DependencyInjectingHandler::new(handler, Arc::new(container), vec!["missing_service".to_string()]);
517
+
518
+ let request = Request::builder().body(Body::empty()).unwrap();
519
+ let request_data = create_request_data();
520
+ let result = di_handler.call(request, request_data).await;
521
+
522
+ assert!(result.is_ok());
523
+ let response = result.unwrap();
524
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
525
+
526
+ let content_type = response.headers().get("Content-Type").and_then(|v| v.to_str().ok());
527
+ assert_eq!(content_type, Some("application/json"));
528
+ }
529
+
530
+ #[tokio::test]
531
+ async fn test_di_handler_partial_dependencies_present() {
532
+ let mut container = DependencyContainer::new();
533
+ container
534
+ .register("db".to_string(), Arc::new(ValueDependency::new("db", "postgresql")))
535
+ .unwrap();
536
+
537
+ let handler = Arc::new(TestHandler);
538
+ let di_handler = DependencyInjectingHandler::new(
539
+ handler,
540
+ Arc::new(container),
541
+ vec!["db".to_string(), "cache".to_string()],
542
+ );
543
+
544
+ let request = Request::builder().body(Body::empty()).unwrap();
545
+ let request_data = create_request_data();
546
+ let result = di_handler.call(request, request_data).await;
547
+
548
+ assert!(result.is_ok());
549
+ let response = result.unwrap();
550
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
551
+ }
552
+
553
+ #[tokio::test]
554
+ async fn test_di_handler_cleanup_executed() {
555
+ let mut container = DependencyContainer::new();
556
+
557
+ container
558
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
559
+ .unwrap();
560
+
561
+ let handler = Arc::new(TestHandler);
562
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
563
+
564
+ let request = Request::builder().body(Body::empty()).unwrap();
565
+ let request_data = create_request_data();
566
+
567
+ let result = di_handler.call(request, request_data).await;
568
+
569
+ assert!(result.is_ok());
570
+ let response = result.unwrap();
571
+ assert_eq!(response.status(), StatusCode::OK);
572
+ }
573
+
574
+ #[tokio::test]
575
+ async fn test_di_handler_dependent_dependencies() {
576
+ let mut container = DependencyContainer::new();
577
+
578
+ container
579
+ .register(
580
+ "config".to_string(),
581
+ Arc::new(ValueDependency::new("config", "base_config")),
582
+ )
583
+ .unwrap();
584
+
585
+ container
586
+ .register(
587
+ "database".to_string(),
588
+ Arc::new(ValueDependency::new("database", "db_from_config")),
589
+ )
590
+ .unwrap();
591
+
592
+ let handler = Arc::new(TestHandler);
593
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["database".to_string()]);
594
+
595
+ let request = Request::builder().body(Body::empty()).unwrap();
596
+ let request_data = create_request_data();
597
+ let result = di_handler.call(request, request_data).await;
598
+
599
+ assert!(result.is_ok());
600
+ let response = result.unwrap();
601
+ assert_eq!(response.status(), StatusCode::OK);
602
+ }
603
+
604
+ #[tokio::test]
605
+ async fn test_di_handler_parallel_independent_dependencies() {
606
+ let mut container = DependencyContainer::new();
607
+
608
+ container
609
+ .register(
610
+ "service_a".to_string(),
611
+ Arc::new(ValueDependency::new("service_a", "svc_a")),
612
+ )
613
+ .unwrap();
614
+ container
615
+ .register(
616
+ "service_b".to_string(),
617
+ Arc::new(ValueDependency::new("service_b", "svc_b")),
618
+ )
619
+ .unwrap();
620
+ container
621
+ .register(
622
+ "service_c".to_string(),
623
+ Arc::new(ValueDependency::new("service_c", "svc_c")),
624
+ )
625
+ .unwrap();
626
+
627
+ let handler = Arc::new(TestHandler);
628
+ let di_handler = DependencyInjectingHandler::new(
629
+ handler,
630
+ Arc::new(container),
631
+ vec![
632
+ "service_a".to_string(),
633
+ "service_b".to_string(),
634
+ "service_c".to_string(),
635
+ ],
636
+ );
637
+
638
+ let request = Request::builder().body(Body::empty()).unwrap();
639
+ let request_data = create_request_data();
640
+ let result = di_handler.call(request, request_data).await;
641
+
642
+ assert!(result.is_ok());
643
+ let response = result.unwrap();
644
+ assert_eq!(response.status(), StatusCode::OK);
645
+ }
646
+
647
+ #[tokio::test]
648
+ async fn test_di_handler_request_method_preserved() {
649
+ let mut container = DependencyContainer::new();
650
+ container
651
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
652
+ .unwrap();
653
+
654
+ let handler = Arc::new(TestHandler);
655
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
656
+
657
+ let request = Request::builder().method("POST").body(Body::empty()).unwrap();
658
+ let mut request_data = create_request_data();
659
+ request_data.method = "POST".to_string();
660
+
661
+ let result = di_handler.call(request, request_data).await;
662
+
663
+ assert!(result.is_ok());
664
+ let response = result.unwrap();
665
+ assert_eq!(response.status(), StatusCode::OK);
666
+ }
667
+
668
+ #[tokio::test]
669
+ async fn test_di_handler_complex_scenario_multiple_deps_with_error() {
670
+ let mut container = DependencyContainer::new();
671
+
672
+ for i in 1..=5 {
673
+ container
674
+ .register(
675
+ format!("service_{}", i),
676
+ Arc::new(ValueDependency::new(&format!("service_{}", i), format!("svc_{}", i))),
677
+ )
678
+ .unwrap();
679
+ }
680
+
681
+ let handler = Arc::new(ErrorHandler);
682
+ let di_handler = DependencyInjectingHandler::new(
683
+ handler,
684
+ Arc::new(container),
685
+ vec![
686
+ "service_1".to_string(),
687
+ "service_2".to_string(),
688
+ "service_3".to_string(),
689
+ "service_4".to_string(),
690
+ "service_5".to_string(),
691
+ ],
692
+ );
693
+
694
+ let request = Request::builder().body(Body::empty()).unwrap();
695
+ let request_data = create_request_data();
696
+ let result = di_handler.call(request, request_data).await;
697
+
698
+ assert!(result.is_err());
699
+ let (status, _msg) = result.unwrap_err();
700
+ assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
701
+ }
702
+
703
+ #[tokio::test]
704
+ async fn test_di_handler_empty_request_body_with_deps() {
705
+ let mut container = DependencyContainer::new();
706
+ container
707
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
708
+ .unwrap();
709
+
710
+ let handler = Arc::new(TestHandler);
711
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
712
+
713
+ let request = Request::builder().body(Body::empty()).unwrap();
714
+ let request_data = create_request_data();
715
+
716
+ let result = di_handler.call(request, request_data).await;
717
+
718
+ assert!(result.is_ok());
719
+ let response = result.unwrap();
720
+ assert_eq!(response.status(), StatusCode::OK);
721
+ }
722
+
723
+ #[tokio::test]
724
+ async fn test_di_handler_shared_container_across_handlers() {
725
+ let mut container = DependencyContainer::new();
726
+ container
727
+ .register(
728
+ "shared_config".to_string(),
729
+ Arc::new(ValueDependency::new("shared_config", "shared_value")),
730
+ )
731
+ .unwrap();
732
+
733
+ let shared_container = Arc::new(container);
734
+
735
+ let handler1 = Arc::new(TestHandler);
736
+ let di_handler1 = DependencyInjectingHandler::new(
737
+ handler1,
738
+ Arc::clone(&shared_container),
739
+ vec!["shared_config".to_string()],
740
+ );
741
+
742
+ let handler2 = Arc::new(TestHandler);
743
+ let di_handler2 = DependencyInjectingHandler::new(
744
+ handler2,
745
+ Arc::clone(&shared_container),
746
+ vec!["shared_config".to_string()],
747
+ );
748
+
749
+ let request1 = Request::builder().body(Body::empty()).unwrap();
750
+ let request_data1 = create_request_data();
751
+ let result1 = di_handler1.call(request1, request_data1).await;
752
+
753
+ let request2 = Request::builder().body(Body::empty()).unwrap();
754
+ let request_data2 = create_request_data();
755
+ let result2 = di_handler2.call(request2, request_data2).await;
756
+
757
+ assert!(result1.is_ok());
758
+ assert!(result2.is_ok());
759
+ }
760
+
761
+ #[tokio::test]
762
+ async fn test_concurrent_requests_same_handler_no_race() {
763
+ let mut container = DependencyContainer::new();
764
+ container
765
+ .register(
766
+ "config".to_string(),
767
+ Arc::new(ValueDependency::new("config", "test_config")),
768
+ )
769
+ .unwrap();
770
+
771
+ let shared_container = Arc::new(container);
772
+ let handler = Arc::new(TestHandler);
773
+ let di_handler = Arc::new(DependencyInjectingHandler::new(
774
+ handler,
775
+ shared_container,
776
+ vec!["config".to_string()],
777
+ ));
778
+
779
+ let handles: Vec<_> = (0..10)
780
+ .map(|_| {
781
+ let di_handler = Arc::clone(&di_handler);
782
+ tokio::spawn(async move {
783
+ let request = Request::builder().body(Body::empty()).unwrap();
784
+ let request_data = create_request_data();
785
+ di_handler.call(request, request_data).await
786
+ })
787
+ })
788
+ .collect();
789
+
790
+ for handle in handles {
791
+ let result = handle.await.unwrap();
792
+ assert!(result.is_ok());
793
+ let response = result.unwrap();
794
+ assert_eq!(response.status(), StatusCode::OK);
795
+ }
796
+ }
797
+
798
+ #[tokio::test]
799
+ async fn test_concurrent_different_handlers_shared_container() {
800
+ let mut container = DependencyContainer::new();
801
+ container
802
+ .register("db".to_string(), Arc::new(ValueDependency::new("db", "postgres")))
803
+ .unwrap();
804
+ container
805
+ .register("cache".to_string(), Arc::new(ValueDependency::new("cache", "redis")))
806
+ .unwrap();
807
+
808
+ let shared_container = Arc::new(container);
809
+
810
+ let mut handles = vec![];
811
+ for i in 0..5 {
812
+ let container = Arc::clone(&shared_container);
813
+ let handler = Arc::new(TestHandler);
814
+ let di_handler = DependencyInjectingHandler::new(
815
+ handler,
816
+ container,
817
+ if i % 2 == 0 {
818
+ vec!["db".to_string()]
819
+ } else {
820
+ vec!["cache".to_string()]
821
+ },
822
+ );
823
+
824
+ let handle = tokio::spawn(async move {
825
+ let request = Request::builder().body(Body::empty()).unwrap();
826
+ let request_data = create_request_data();
827
+ di_handler.call(request, request_data).await
828
+ });
829
+ handles.push(handle);
830
+ }
831
+
832
+ for handle in handles {
833
+ let result = handle.await.unwrap();
834
+ assert!(result.is_ok());
835
+ }
836
+ }
837
+
838
+ #[tokio::test]
839
+ async fn test_missing_dependency_multiple_concurrent_requests() {
840
+ let container = DependencyContainer::new();
841
+ let shared_container = Arc::new(container);
842
+ let handler = Arc::new(TestHandler);
843
+ let di_handler = Arc::new(DependencyInjectingHandler::new(
844
+ handler,
845
+ shared_container,
846
+ vec!["nonexistent".to_string()],
847
+ ));
848
+
849
+ let handles: Vec<_> = (0..5)
850
+ .map(|_| {
851
+ let di_handler = Arc::clone(&di_handler);
852
+ tokio::spawn(async move {
853
+ let request = Request::builder().body(Body::empty()).unwrap();
854
+ let request_data = create_request_data();
855
+ di_handler.call(request, request_data).await
856
+ })
857
+ })
858
+ .collect();
859
+
860
+ for handle in handles {
861
+ let result = handle.await.unwrap();
862
+ assert!(result.is_ok());
863
+ let response = result.unwrap();
864
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
865
+ }
866
+ }
867
+
868
+ #[tokio::test]
869
+ async fn test_large_dependency_tree_resolution() {
870
+ let mut container = DependencyContainer::new();
871
+ for i in 0..20 {
872
+ container
873
+ .register(
874
+ format!("dep_{}", i),
875
+ Arc::new(ValueDependency::new(&format!("dep_{}", i), format!("value_{}", i))),
876
+ )
877
+ .unwrap();
878
+ }
879
+
880
+ let handler = Arc::new(TestHandler);
881
+ let mut required = vec![];
882
+ for i in 0..20 {
883
+ required.push(format!("dep_{}", i));
884
+ }
885
+
886
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), required);
887
+
888
+ let request = Request::builder().body(Body::empty()).unwrap();
889
+ let request_data = create_request_data();
890
+ let result = di_handler.call(request, request_data).await;
891
+
892
+ assert!(result.is_ok());
893
+ let response = result.unwrap();
894
+ assert_eq!(response.status(), StatusCode::OK);
895
+ }
896
+
897
+ #[tokio::test]
898
+ async fn test_handler_error_does_not_prevent_cleanup() {
899
+ let mut container = DependencyContainer::new();
900
+ container
901
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
902
+ .unwrap();
903
+
904
+ let handler = Arc::new(ErrorHandler);
905
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
906
+
907
+ let request = Request::builder().body(Body::empty()).unwrap();
908
+ let request_data = create_request_data();
909
+ let result = di_handler.call(request, request_data).await;
910
+
911
+ assert!(result.is_err());
912
+ let (status, msg) = result.unwrap_err();
913
+ assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
914
+ assert!(msg.contains("inner handler error"));
915
+ }
916
+
917
+ #[tokio::test]
918
+ async fn test_partial_dependency_resolution_failure() {
919
+ let mut container = DependencyContainer::new();
920
+ container
921
+ .register(
922
+ "service_a".to_string(),
923
+ Arc::new(ValueDependency::new("service_a", "svc_a")),
924
+ )
925
+ .unwrap();
926
+
927
+ let handler = Arc::new(TestHandler);
928
+ let di_handler = DependencyInjectingHandler::new(
929
+ handler,
930
+ Arc::new(container),
931
+ vec!["service_a".to_string(), "service_b".to_string()],
932
+ );
933
+
934
+ let request = Request::builder().body(Body::empty()).unwrap();
935
+ let request_data = create_request_data();
936
+ let result = di_handler.call(request, request_data).await;
937
+
938
+ assert!(result.is_ok());
939
+ let response = result.unwrap();
940
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
941
+ }
942
+
943
+ #[tokio::test]
944
+ async fn test_circular_dependency_detection() {
945
+ let mut container = DependencyContainer::new();
946
+ container
947
+ .register(
948
+ "service_a".to_string(),
949
+ Arc::new(ValueDependency::new("service_a", "svc_a")),
950
+ )
951
+ .unwrap();
952
+
953
+ let handler = Arc::new(TestHandler);
954
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["service_a".to_string()]);
955
+
956
+ let request = Request::builder().body(Body::empty()).unwrap();
957
+ let request_data = create_request_data();
958
+ let result = di_handler.call(request, request_data).await;
959
+
960
+ assert!(result.is_ok());
961
+ let response = result.unwrap();
962
+ assert_eq!(response.status(), StatusCode::OK);
963
+ }
964
+
965
+ #[tokio::test]
966
+ async fn test_empty_required_dependencies_with_multiple_registered() {
967
+ let mut container = DependencyContainer::new();
968
+ for i in 0..5 {
969
+ container
970
+ .register(
971
+ format!("unused_{}", i),
972
+ Arc::new(ValueDependency::new(&format!("unused_{}", i), format!("val_{}", i))),
973
+ )
974
+ .unwrap();
975
+ }
976
+
977
+ let handler = Arc::new(TestHandler);
978
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec![]);
979
+
980
+ let request = Request::builder().body(Body::empty()).unwrap();
981
+ let request_data = create_request_data();
982
+ let result = di_handler.call(request, request_data).await;
983
+
984
+ assert!(result.is_ok());
985
+ }
986
+
987
+ #[tokio::test]
988
+ async fn test_concurrent_resolution_with_varying_dependency_counts() {
989
+ let mut container = DependencyContainer::new();
990
+ for i in 0..10 {
991
+ container
992
+ .register(
993
+ format!("svc_{}", i),
994
+ Arc::new(ValueDependency::new(&format!("svc_{}", i), format!("s_{}", i))),
995
+ )
996
+ .unwrap();
997
+ }
998
+
999
+ let shared_container = Arc::new(container);
1000
+
1001
+ let mut handles = vec![];
1002
+ for i in 0..10 {
1003
+ let container = Arc::clone(&shared_container);
1004
+ let handler = Arc::new(TestHandler);
1005
+
1006
+ let required: Vec<String> = (0..=(i % 5)).map(|j| format!("svc_{}", j)).collect();
1007
+
1008
+ let di_handler = DependencyInjectingHandler::new(handler, container, required);
1009
+
1010
+ let handle = tokio::spawn(async move {
1011
+ let request = Request::builder().body(Body::empty()).unwrap();
1012
+ let request_data = create_request_data();
1013
+ di_handler.call(request, request_data).await
1014
+ });
1015
+ handles.push(handle);
1016
+ }
1017
+
1018
+ for handle in handles {
1019
+ let result = handle.await.unwrap();
1020
+ assert!(result.is_ok());
1021
+ }
1022
+ }
1023
+
1024
+ #[tokio::test]
1025
+ async fn test_request_data_isolation_across_concurrent_requests() {
1026
+ let mut container = DependencyContainer::new();
1027
+ container
1028
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
1029
+ .unwrap();
1030
+
1031
+ let shared_container = Arc::new(container);
1032
+ let handler = Arc::new(TestHandler);
1033
+ let di_handler = Arc::new(DependencyInjectingHandler::new(
1034
+ handler,
1035
+ shared_container,
1036
+ vec!["config".to_string()],
1037
+ ));
1038
+
1039
+ let mut handles = vec![];
1040
+ for i in 0..10 {
1041
+ let di_handler = Arc::clone(&di_handler);
1042
+ let handle = tokio::spawn(async move {
1043
+ let request = Request::builder().body(Body::empty()).unwrap();
1044
+ let mut request_data = create_request_data();
1045
+ request_data.path = format!("/path/{}", i);
1046
+ di_handler.call(request, request_data).await
1047
+ });
1048
+ handles.push(handle);
1049
+ }
1050
+
1051
+ for handle in handles {
1052
+ let result = handle.await.unwrap();
1053
+ assert!(result.is_ok());
1054
+ }
1055
+ }
1056
+
1057
+ #[tokio::test]
1058
+ async fn test_missing_dependency_error_json_format() {
1059
+ let container = DependencyContainer::new();
1060
+ let handler = Arc::new(TestHandler);
1061
+ let di_handler =
1062
+ DependencyInjectingHandler::new(handler, Arc::new(container), vec!["missing_service".to_string()]);
1063
+
1064
+ let request = Request::builder().body(Body::empty()).unwrap();
1065
+ let request_data = create_request_data();
1066
+ let result = di_handler.call(request, request_data).await;
1067
+
1068
+ assert!(result.is_ok());
1069
+ let response = result.unwrap();
1070
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
1071
+ assert_eq!(
1072
+ response.headers().get("Content-Type").and_then(|v| v.to_str().ok()),
1073
+ Some("application/json")
1074
+ );
1075
+ }
1076
+
1077
+ #[tokio::test]
1078
+ async fn test_many_sequential_requests_same_handler_state() {
1079
+ let mut container = DependencyContainer::new();
1080
+ container
1081
+ .register("state".to_string(), Arc::new(ValueDependency::new("state", "initial")))
1082
+ .unwrap();
1083
+
1084
+ let handler = Arc::new(TestHandler);
1085
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["state".to_string()]);
1086
+
1087
+ for _ in 0..50 {
1088
+ let request = Request::builder().body(Body::empty()).unwrap();
1089
+ let request_data = create_request_data();
1090
+ let result = di_handler.call(request, request_data).await;
1091
+
1092
+ assert!(result.is_ok());
1093
+ let response = result.unwrap();
1094
+ assert_eq!(response.status(), StatusCode::OK);
1095
+ }
1096
+ }
1097
+
1098
+ #[tokio::test]
1099
+ async fn test_dependency_availability_after_resolution() {
1100
+ let mut container = DependencyContainer::new();
1101
+ container
1102
+ .register(
1103
+ "service".to_string(),
1104
+ Arc::new(ValueDependency::new("service", "my_service")),
1105
+ )
1106
+ .unwrap();
1107
+
1108
+ let handler = Arc::new(ReadDependencyHandler);
1109
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["service".to_string()]);
1110
+
1111
+ let request = Request::builder().body(Body::empty()).unwrap();
1112
+ let request_data = create_request_data();
1113
+ let result = di_handler.call(request, request_data).await;
1114
+
1115
+ assert!(result.is_ok());
1116
+ let response = result.unwrap();
1117
+ assert_eq!(response.status(), StatusCode::OK);
1118
+ }
1119
+
1120
+ #[tokio::test]
1121
+ async fn test_container_keys_availability_during_resolution() {
1122
+ let mut container = DependencyContainer::new();
1123
+ container
1124
+ .register("key1".to_string(), Arc::new(ValueDependency::new("key1", "val1")))
1125
+ .unwrap();
1126
+ container
1127
+ .register("key2".to_string(), Arc::new(ValueDependency::new("key2", "val2")))
1128
+ .unwrap();
1129
+
1130
+ let handler = Arc::new(TestHandler);
1131
+ let di_handler = DependencyInjectingHandler::new(
1132
+ handler,
1133
+ Arc::new(container),
1134
+ vec!["key1".to_string(), "key2".to_string()],
1135
+ );
1136
+
1137
+ let request = Request::builder().body(Body::empty()).unwrap();
1138
+ let request_data = create_request_data();
1139
+ let result = di_handler.call(request, request_data).await;
1140
+
1141
+ assert!(result.is_ok());
1142
+ let response = result.unwrap();
1143
+ assert_eq!(response.status(), StatusCode::OK);
1144
+ }
1145
+
1146
+ #[tokio::test]
1147
+ async fn test_post_request_with_dependencies() {
1148
+ let mut container = DependencyContainer::new();
1149
+ container
1150
+ .register(
1151
+ "validator".to_string(),
1152
+ Arc::new(ValueDependency::new("validator", "strict_mode")),
1153
+ )
1154
+ .unwrap();
1155
+
1156
+ let handler = Arc::new(TestHandler);
1157
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["validator".to_string()]);
1158
+
1159
+ let request = Request::builder()
1160
+ .method("POST")
1161
+ .header("Content-Type", "application/json")
1162
+ .body(Body::from(r#"{"key":"value"}"#))
1163
+ .unwrap();
1164
+ let mut request_data = create_request_data();
1165
+ request_data.method = "POST".to_string();
1166
+ request_data.body = serde_json::json!({"key": "value"});
1167
+
1168
+ let result = di_handler.call(request, request_data).await;
1169
+
1170
+ assert!(result.is_ok());
1171
+ let response = result.unwrap();
1172
+ assert_eq!(response.status(), StatusCode::OK);
1173
+ }
1174
+
1175
+ #[tokio::test]
1176
+ async fn test_delete_request_with_authorization_dependency() {
1177
+ let mut container = DependencyContainer::new();
1178
+ container
1179
+ .register(
1180
+ "auth".to_string(),
1181
+ Arc::new(ValueDependency::new("auth", "bearer_token")),
1182
+ )
1183
+ .unwrap();
1184
+
1185
+ let handler = Arc::new(TestHandler);
1186
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["auth".to_string()]);
1187
+
1188
+ let request = Request::builder().method("DELETE").body(Body::empty()).unwrap();
1189
+ let mut request_data = create_request_data();
1190
+ request_data.method = "DELETE".to_string();
1191
+ request_data.path = "/resource/123".to_string();
1192
+
1193
+ let result = di_handler.call(request, request_data).await;
1194
+
1195
+ assert!(result.is_ok());
1196
+ let response = result.unwrap();
1197
+ assert_eq!(response.status(), StatusCode::OK);
1198
+ }
1199
+
1200
+ #[tokio::test]
1201
+ async fn test_very_large_number_of_dependencies_in_single_handler() {
1202
+ let mut container = DependencyContainer::new();
1203
+ let mut required_deps = vec![];
1204
+ for i in 0..50 {
1205
+ let key = format!("dep_{}", i);
1206
+ container
1207
+ .register(
1208
+ key.clone(),
1209
+ Arc::new(ValueDependency::new(&key, format!("value_{}", i))),
1210
+ )
1211
+ .unwrap();
1212
+ required_deps.push(key);
1213
+ }
1214
+
1215
+ let handler = Arc::new(TestHandler);
1216
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), required_deps);
1217
+
1218
+ let request = Request::builder().body(Body::empty()).unwrap();
1219
+ let request_data = create_request_data();
1220
+ let result = di_handler.call(request, request_data).await;
1221
+
1222
+ assert!(result.is_ok());
1223
+ let response = result.unwrap();
1224
+ assert_eq!(response.status(), StatusCode::OK);
1225
+ }
1226
+
1227
+ #[tokio::test]
1228
+ async fn test_handler_cloning_with_same_container() {
1229
+ let mut container = DependencyContainer::new();
1230
+ container
1231
+ .register("svc".to_string(), Arc::new(ValueDependency::new("svc", "service")))
1232
+ .unwrap();
1233
+
1234
+ let shared_container = Arc::new(container);
1235
+ let base_handler: Arc<dyn Handler> = Arc::new(TestHandler);
1236
+
1237
+ let di_handler1 = Arc::new(DependencyInjectingHandler::new(
1238
+ base_handler.clone(),
1239
+ Arc::clone(&shared_container),
1240
+ vec!["svc".to_string()],
1241
+ ));
1242
+
1243
+ let di_handler2 = Arc::new(DependencyInjectingHandler::new(
1244
+ base_handler.clone(),
1245
+ Arc::clone(&shared_container),
1246
+ vec!["svc".to_string()],
1247
+ ));
1248
+
1249
+ let handle1 = tokio::spawn({
1250
+ let dih = Arc::clone(&di_handler1);
1251
+ async move {
1252
+ let request = Request::builder().body(Body::empty()).unwrap();
1253
+ let request_data = create_request_data();
1254
+ dih.call(request, request_data).await
1255
+ }
1256
+ });
1257
+
1258
+ let handle2 = tokio::spawn({
1259
+ let dih = Arc::clone(&di_handler2);
1260
+ async move {
1261
+ let request = Request::builder().body(Body::empty()).unwrap();
1262
+ let request_data = create_request_data();
1263
+ dih.call(request, request_data).await
1264
+ }
1265
+ });
1266
+
1267
+ assert!(handle1.await.unwrap().is_ok());
1268
+ assert!(handle2.await.unwrap().is_ok());
1269
+ }
1270
+
1271
+ #[tokio::test]
1272
+ async fn test_request_parts_reconstruction_correctness() {
1273
+ let mut container = DependencyContainer::new();
1274
+ container
1275
+ .register("config".to_string(), Arc::new(ValueDependency::new("config", "test")))
1276
+ .unwrap();
1277
+
1278
+ let handler = Arc::new(TestHandler);
1279
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
1280
+
1281
+ let request = Request::builder()
1282
+ .method("GET")
1283
+ .header("User-Agent", "test-client")
1284
+ .header("Accept", "application/json")
1285
+ .body(Body::empty())
1286
+ .unwrap();
1287
+ let mut request_data = create_request_data();
1288
+ request_data.method = "GET".to_string();
1289
+
1290
+ let result = di_handler.call(request, request_data).await;
1291
+
1292
+ assert!(result.is_ok());
1293
+ }
1294
+
1295
+ #[tokio::test]
1296
+ async fn test_resolution_failure_returns_service_unavailable() {
1297
+ let mut container = DependencyContainer::new();
1298
+ container
1299
+ .register(
1300
+ "external_api".to_string(),
1301
+ Arc::new(ValueDependency::new("external_api", "unavailable")),
1302
+ )
1303
+ .unwrap();
1304
+
1305
+ let handler = Arc::new(TestHandler);
1306
+ let di_handler =
1307
+ DependencyInjectingHandler::new(handler, Arc::new(container), vec!["external_api".to_string()]);
1308
+
1309
+ let request = Request::builder().body(Body::empty()).unwrap();
1310
+ let request_data = create_request_data();
1311
+ let result = di_handler.call(request, request_data).await;
1312
+
1313
+ assert!(result.is_ok());
1314
+ }
1315
+
1316
+ #[tokio::test]
1317
+ async fn test_multiple_missing_dependencies_reports_first() {
1318
+ let container = DependencyContainer::new();
1319
+ let handler = Arc::new(TestHandler);
1320
+ let di_handler = DependencyInjectingHandler::new(
1321
+ handler,
1322
+ Arc::new(container),
1323
+ vec![
1324
+ "missing_a".to_string(),
1325
+ "missing_b".to_string(),
1326
+ "missing_c".to_string(),
1327
+ ],
1328
+ );
1329
+
1330
+ let request = Request::builder().body(Body::empty()).unwrap();
1331
+ let request_data = create_request_data();
1332
+ let result = di_handler.call(request, request_data).await;
1333
+
1334
+ assert!(result.is_ok());
1335
+ let response = result.unwrap();
1336
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
1337
+ }
1338
+
1339
+ #[tokio::test]
1340
+ async fn test_required_dependencies_getter_consistency() {
1341
+ let deps = vec![
1342
+ "dep_a".to_string(),
1343
+ "dep_b".to_string(),
1344
+ "dep_c".to_string(),
1345
+ "dep_d".to_string(),
1346
+ ];
1347
+ let container = DependencyContainer::new();
1348
+ let handler = Arc::new(TestHandler);
1349
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), deps.clone());
1350
+
1351
+ let returned_deps = di_handler.required_dependencies();
1352
+ assert_eq!(returned_deps.len(), 4);
1353
+ assert_eq!(returned_deps, deps.as_slice());
1354
+ }
1355
+
1356
+ #[tokio::test]
1357
+ async fn test_concurrent_error_handlers_isolation() {
1358
+ let container = DependencyContainer::new();
1359
+ let handler = Arc::new(ErrorHandler);
1360
+ let di_handler = Arc::new(DependencyInjectingHandler::new(handler, Arc::new(container), vec![]));
1361
+
1362
+ let handles: Vec<_> = (0..10)
1363
+ .map(|_| {
1364
+ let dih = Arc::clone(&di_handler);
1365
+ tokio::spawn(async move {
1366
+ let request = Request::builder().body(Body::empty()).unwrap();
1367
+ let request_data = create_request_data();
1368
+ dih.call(request, request_data).await
1369
+ })
1370
+ })
1371
+ .collect();
1372
+
1373
+ for handle in handles {
1374
+ let result = handle.await.unwrap();
1375
+ assert!(result.is_err());
1376
+ let (status, msg) = result.unwrap_err();
1377
+ assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
1378
+ assert!(msg.contains("inner handler error"));
1379
+ }
1380
+ }
1381
+
1382
+ #[tokio::test]
1383
+ async fn test_patch_request_with_dependencies() {
1384
+ let mut container = DependencyContainer::new();
1385
+ container
1386
+ .register(
1387
+ "merger".to_string(),
1388
+ Arc::new(ValueDependency::new("merger", "strategic_merge")),
1389
+ )
1390
+ .unwrap();
1391
+
1392
+ let handler = Arc::new(TestHandler);
1393
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["merger".to_string()]);
1394
+
1395
+ let request = Request::builder().method("PATCH").body(Body::empty()).unwrap();
1396
+ let mut request_data = create_request_data();
1397
+ request_data.method = "PATCH".to_string();
1398
+
1399
+ let result = di_handler.call(request, request_data).await;
1400
+
1401
+ assert!(result.is_ok());
1402
+ let response = result.unwrap();
1403
+ assert_eq!(response.status(), StatusCode::OK);
1404
+ }
1405
+
1406
+ #[tokio::test]
1407
+ async fn test_handler_receives_enriched_request_data_with_multiple_deps() {
1408
+ let mut container = DependencyContainer::new();
1409
+ for i in 0..5 {
1410
+ container
1411
+ .register(
1412
+ format!("svc_{}", i),
1413
+ Arc::new(ValueDependency::new(&format!("svc_{}", i), format!("s_{}", i))),
1414
+ )
1415
+ .unwrap();
1416
+ }
1417
+
1418
+ let handler = Arc::new(ReadDependencyHandler);
1419
+ let required: Vec<String> = (0..5).map(|i| format!("svc_{}", i)).collect();
1420
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), required);
1421
+
1422
+ let request = Request::builder().body(Body::empty()).unwrap();
1423
+ let request_data = create_request_data();
1424
+ let result = di_handler.call(request, request_data).await;
1425
+
1426
+ assert!(result.is_ok());
1427
+ let response = result.unwrap();
1428
+ assert_eq!(response.status(), StatusCode::OK);
1429
+ }
1430
+
1431
+ #[tokio::test]
1432
+ async fn test_arc_try_unwrap_cleanup_branch() {
1433
+ let mut container = DependencyContainer::new();
1434
+ container
1435
+ .register(
1436
+ "resource".to_string(),
1437
+ Arc::new(ValueDependency::new("resource", "allocated")),
1438
+ )
1439
+ .unwrap();
1440
+
1441
+ let handler = Arc::new(TestHandler);
1442
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["resource".to_string()]);
1443
+
1444
+ let request = Request::builder().body(Body::empty()).unwrap();
1445
+ let request_data = create_request_data();
1446
+ let result = di_handler.call(request, request_data).await;
1447
+
1448
+ assert!(result.is_ok());
1449
+ let response = result.unwrap();
1450
+ assert_eq!(response.status(), StatusCode::OK);
1451
+ }
1452
+
1453
+ #[tokio::test]
1454
+ async fn test_head_request_with_dependencies() {
1455
+ let mut container = DependencyContainer::new();
1456
+ container
1457
+ .register(
1458
+ "metadata".to_string(),
1459
+ Arc::new(ValueDependency::new("metadata", "headers_only")),
1460
+ )
1461
+ .unwrap();
1462
+
1463
+ let handler = Arc::new(TestHandler);
1464
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["metadata".to_string()]);
1465
+
1466
+ let request = Request::builder().method("HEAD").body(Body::empty()).unwrap();
1467
+ let mut request_data = create_request_data();
1468
+ request_data.method = "HEAD".to_string();
1469
+
1470
+ let result = di_handler.call(request, request_data).await;
1471
+
1472
+ assert!(result.is_ok());
1473
+ let response = result.unwrap();
1474
+ assert_eq!(response.status(), StatusCode::OK);
1475
+ }
1476
+
1477
+ #[tokio::test]
1478
+ async fn test_options_request_with_dependencies() {
1479
+ let mut container = DependencyContainer::new();
1480
+ container
1481
+ .register("cors".to_string(), Arc::new(ValueDependency::new("cors", "permissive")))
1482
+ .unwrap();
1483
+
1484
+ let handler = Arc::new(TestHandler);
1485
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["cors".to_string()]);
1486
+
1487
+ let request = Request::builder().method("OPTIONS").body(Body::empty()).unwrap();
1488
+ let mut request_data = create_request_data();
1489
+ request_data.method = "OPTIONS".to_string();
1490
+
1491
+ let result = di_handler.call(request, request_data).await;
1492
+
1493
+ assert!(result.is_ok());
1494
+ let response = result.unwrap();
1495
+ assert_eq!(response.status(), StatusCode::OK);
1496
+ }
1497
+
1498
+ #[tokio::test]
1499
+ async fn test_circular_dependency_error_json_structure() {
1500
+ let container = DependencyContainer::new();
1501
+ let handler = Arc::new(TestHandler);
1502
+
1503
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["missing".to_string()]);
1504
+
1505
+ let request = Request::builder().body(Body::empty()).unwrap();
1506
+ let request_data = create_request_data();
1507
+ let result = di_handler.call(request, request_data).await;
1508
+
1509
+ assert!(result.is_ok());
1510
+ let response = result.unwrap();
1511
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
1512
+
1513
+ let content_type = response.headers().get("Content-Type").and_then(|v| v.to_str().ok());
1514
+ assert_eq!(content_type, Some("application/json"));
1515
+
1516
+ let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap();
1517
+ let json_body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
1518
+
1519
+ assert!(json_body.get("type").is_some(), "type field must be present");
1520
+ assert!(json_body.get("title").is_some(), "title field must be present");
1521
+ assert!(json_body.get("detail").is_some(), "detail field must be present");
1522
+ assert!(json_body.get("status").is_some(), "status field must be present");
1523
+
1524
+ assert_eq!(json_body.get("status").and_then(|v| v.as_i64()), Some(500));
1525
+ assert_eq!(
1526
+ json_body.get("type").and_then(|v| v.as_str()),
1527
+ Some("https://spikard.dev/errors/dependency-error")
1528
+ );
1529
+ }
1530
+
1531
+ #[tokio::test]
1532
+ async fn test_request_data_is_cloned_not_moved_to_handler() {
1533
+ let mut container = DependencyContainer::new();
1534
+ container
1535
+ .register(
1536
+ "service".to_string(),
1537
+ Arc::new(ValueDependency::new("service", "test_service")),
1538
+ )
1539
+ .unwrap();
1540
+
1541
+ let handler = Arc::new(ReadDependencyHandler);
1542
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["service".to_string()]);
1543
+
1544
+ let mut original_request_data = create_request_data();
1545
+ original_request_data.path = "/api/test".to_string();
1546
+ original_request_data.method = "POST".to_string();
1547
+
1548
+ let mut headers = HashMap::new();
1549
+ headers.insert("X-Custom-Header".to_string(), "custom-value".to_string());
1550
+ original_request_data.headers = Arc::new(headers.clone());
1551
+
1552
+ let mut cookies = HashMap::new();
1553
+ cookies.insert("session_id".to_string(), "test-session".to_string());
1554
+ original_request_data.cookies = Arc::new(cookies.clone());
1555
+
1556
+ let original_path = original_request_data.path.clone();
1557
+ let original_method = original_request_data.method.clone();
1558
+
1559
+ let request = Request::builder().method("POST").body(Body::empty()).unwrap();
1560
+ let request_data_clone = original_request_data.clone();
1561
+ let result = di_handler.call(request, original_request_data).await;
1562
+
1563
+ assert!(result.is_ok());
1564
+
1565
+ assert_eq!(request_data_clone.path, original_path);
1566
+ assert_eq!(request_data_clone.method, original_method);
1567
+
1568
+ assert!(request_data_clone.dependencies.is_none());
1569
+
1570
+ assert_eq!(*request_data_clone.headers, headers);
1571
+ assert_eq!(*request_data_clone.cookies, cookies);
1572
+ }
1573
+
1574
+ #[tokio::test]
1575
+ async fn test_core_request_data_conversion_preserves_all_fields() {
1576
+ let mut container = DependencyContainer::new();
1577
+ container
1578
+ .register(
1579
+ "config".to_string(),
1580
+ Arc::new(ValueDependency::new("config", "test_config")),
1581
+ )
1582
+ .unwrap();
1583
+
1584
+ let handler = Arc::new(TestHandler);
1585
+ let di_handler = DependencyInjectingHandler::new(handler, Arc::new(container), vec!["config".to_string()]);
1586
+
1587
+ let mut path_params = HashMap::new();
1588
+ path_params.insert("id".to_string(), "123".to_string());
1589
+ path_params.insert("resource".to_string(), "users".to_string());
1590
+
1591
+ let mut raw_query_params = HashMap::new();
1592
+ raw_query_params.insert("filter".to_string(), vec!["active".to_string()]);
1593
+ raw_query_params.insert("sort".to_string(), vec!["name".to_string(), "asc".to_string()]);
1594
+
1595
+ let mut headers = HashMap::new();
1596
+ headers.insert("Authorization".to_string(), "Bearer token123".to_string());
1597
+ headers.insert("Content-Type".to_string(), "application/json".to_string());
1598
+
1599
+ let mut cookies = HashMap::new();
1600
+ cookies.insert("session".to_string(), "abc123".to_string());
1601
+ cookies.insert("preferences".to_string(), "dark_mode".to_string());
1602
+
1603
+ let request_data = RequestData {
1604
+ path_params: Arc::new(path_params.clone()),
1605
+ query_params: serde_json::json!({"filter": "active", "sort": "name"}),
1606
+ validated_params: None,
1607
+ raw_query_params: Arc::new(raw_query_params.clone()),
1608
+ body: serde_json::json!({"name": "John", "email": "john@example.com"}),
1609
+ raw_body: Some(bytes::Bytes::from(r#"{"name":"John","email":"john@example.com"}"#)),
1610
+ headers: Arc::new(headers.clone()),
1611
+ cookies: Arc::new(cookies.clone()),
1612
+ method: "POST".to_string(),
1613
+ path: "/api/users/123".to_string(),
1614
+ #[cfg(feature = "di")]
1615
+ dependencies: None,
1616
+ };
1617
+
1618
+ let original_path = request_data.path.clone();
1619
+ let original_method = request_data.method.clone();
1620
+ let original_body = request_data.body.clone();
1621
+ let original_query_params = request_data.query_params.clone();
1622
+
1623
+ let request = Request::builder().method("POST").body(Body::empty()).unwrap();
1624
+ let result = di_handler.call(request, request_data.clone()).await;
1625
+
1626
+ assert!(result.is_ok());
1627
+ let response = result.unwrap();
1628
+ assert_eq!(response.status(), StatusCode::OK);
1629
+
1630
+ assert_eq!(request_data.path, original_path, "path field must be preserved");
1631
+ assert_eq!(request_data.method, original_method, "method field must be preserved");
1632
+ assert_eq!(request_data.body, original_body, "body field must be preserved");
1633
+ assert_eq!(
1634
+ request_data.query_params, original_query_params,
1635
+ "query_params must be preserved"
1636
+ );
1637
+
1638
+ assert_eq!(request_data.path_params.get("id"), Some(&"123".to_string()));
1639
+ assert_eq!(request_data.path_params.get("resource"), Some(&"users".to_string()));
1640
+
1641
+ assert_eq!(
1642
+ request_data.raw_query_params.get("filter"),
1643
+ Some(&vec!["active".to_string()])
1644
+ );
1645
+ assert_eq!(
1646
+ request_data.raw_query_params.get("sort"),
1647
+ Some(&vec!["name".to_string(), "asc".to_string()])
1648
+ );
1649
+
1650
+ assert_eq!(
1651
+ request_data.headers.get("Authorization"),
1652
+ Some(&"Bearer token123".to_string())
1653
+ );
1654
+ assert_eq!(
1655
+ request_data.headers.get("Content-Type"),
1656
+ Some(&"application/json".to_string())
1657
+ );
1658
+
1659
+ assert_eq!(request_data.cookies.get("session"), Some(&"abc123".to_string()));
1660
+ assert_eq!(request_data.cookies.get("preferences"), Some(&"dark_mode".to_string()));
1661
+
1662
+ assert!(request_data.raw_body.is_some());
1663
+ assert_eq!(
1664
+ request_data.raw_body.as_ref().unwrap().as_ref(),
1665
+ r#"{"name":"John","email":"john@example.com"}"#.as_bytes()
1666
+ );
1667
+ }
1668
+ }