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.
- checksums.yaml +7 -0
- data/LICENSE +1 -0
- data/README.md +659 -0
- data/ext/spikard_rb/Cargo.toml +17 -0
- data/ext/spikard_rb/extconf.rb +10 -0
- data/ext/spikard_rb/src/lib.rs +6 -0
- data/lib/spikard/app.rb +405 -0
- data/lib/spikard/background.rb +27 -0
- data/lib/spikard/config.rb +396 -0
- data/lib/spikard/converters.rb +13 -0
- data/lib/spikard/handler_wrapper.rb +113 -0
- data/lib/spikard/provide.rb +214 -0
- data/lib/spikard/response.rb +173 -0
- data/lib/spikard/schema.rb +243 -0
- data/lib/spikard/sse.rb +111 -0
- data/lib/spikard/streaming_response.rb +44 -0
- data/lib/spikard/testing.rb +221 -0
- data/lib/spikard/upload_file.rb +131 -0
- data/lib/spikard/version.rb +5 -0
- data/lib/spikard/websocket.rb +59 -0
- data/lib/spikard.rb +43 -0
- data/sig/spikard.rbs +366 -0
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +139 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +561 -0
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +403 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +274 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +25 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +298 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +637 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +309 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +355 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +502 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +389 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +413 -0
- data/vendor/crates/spikard-core/Cargo.toml +40 -0
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/crates/spikard-core/src/debug.rs +63 -0
- data/vendor/crates/spikard-core/src/di/container.rs +726 -0
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/crates/spikard-core/src/di/error.rs +118 -0
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/crates/spikard-core/src/di/value.rs +283 -0
- data/vendor/crates/spikard-core/src/errors.rs +39 -0
- data/vendor/crates/spikard-core/src/http.rs +153 -0
- data/vendor/crates/spikard-core/src/lib.rs +29 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/crates/spikard-core/src/metadata.rs +397 -0
- data/vendor/crates/spikard-core/src/parameters.rs +723 -0
- data/vendor/crates/spikard-core/src/problem.rs +310 -0
- data/vendor/crates/spikard-core/src/request_data.rs +189 -0
- data/vendor/crates/spikard-core/src/router.rs +249 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +689 -0
- data/vendor/crates/spikard-core/src/validation/mod.rs +459 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +147 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +91 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +1562 -0
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/crates/spikard-http/src/cors.rs +490 -0
- data/vendor/crates/spikard-http/src/debug.rs +63 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1878 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +532 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +861 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +524 -0
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +930 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +541 -0
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +867 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +678 -0
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
- data/vendor/crates/spikard-http/src/response.rs +399 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1557 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +806 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +630 -0
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +497 -0
- data/vendor/crates/spikard-http/src/sse.rs +961 -0
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/crates/spikard-http/src/testing.rs +377 -0
- data/vendor/crates/spikard-http/src/websocket.rs +831 -0
- data/vendor/crates/spikard-http/tests/background_behavior.rs +918 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +308 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +21 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +202 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +4 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1135 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +688 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +324 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +728 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +724 -0
- data/vendor/crates/spikard-rb/Cargo.toml +43 -0
- data/vendor/crates/spikard-rb/build.rs +199 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/config/server_config.rs +283 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +459 -0
- data/vendor/crates/spikard-rb/src/di/builder.rs +105 -0
- data/vendor/crates/spikard-rb/src/di/mod.rs +413 -0
- data/vendor/crates/spikard-rb/src/handler.rs +612 -0
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +1857 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +427 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +326 -0
- data/vendor/crates/spikard-rb/src/server.rs +283 -0
- data/vendor/crates/spikard-rb/src/sse.rs +231 -0
- data/vendor/crates/spikard-rb/src/testing/client.rs +404 -0
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- data/vendor/crates/spikard-rb/tests/magnus_ffi_tests.rs +14 -0
- 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
|
+
}
|