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