spikard 0.4.0-x86_64-linux

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