spikard 0.8.3 → 0.10.2
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 +19 -10
- data/ext/spikard_rb/Cargo.lock +234 -162
- data/ext/spikard_rb/Cargo.toml +2 -2
- data/ext/spikard_rb/extconf.rb +4 -3
- data/lib/spikard/config.rb +88 -12
- data/lib/spikard/testing.rb +3 -1
- data/lib/spikard/version.rb +1 -1
- data/lib/spikard.rb +11 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +3 -6
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +8 -8
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +2 -2
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +10 -4
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +3 -3
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +10 -5
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +829 -0
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +587 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +7 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +11 -11
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +9 -37
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +436 -3
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +944 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +3 -2
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +13 -13
- data/vendor/crates/spikard-bindings-shared/tests/{comprehensive_coverage.rs → full_coverage.rs} +10 -5
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +14 -14
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +669 -0
- data/vendor/crates/spikard-core/Cargo.toml +3 -3
- data/vendor/crates/spikard-core/src/di/container.rs +1 -1
- data/vendor/crates/spikard-core/src/di/factory.rs +2 -2
- data/vendor/crates/spikard-core/src/di/resolved.rs +2 -2
- data/vendor/crates/spikard-core/src/di/value.rs +1 -1
- data/vendor/crates/spikard-core/src/http.rs +75 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +43 -43
- data/vendor/crates/spikard-core/src/parameters.rs +14 -19
- data/vendor/crates/spikard-core/src/problem.rs +1 -1
- data/vendor/crates/spikard-core/src/request_data.rs +7 -16
- data/vendor/crates/spikard-core/src/router.rs +6 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +2 -3
- data/vendor/crates/spikard-core/src/type_hints.rs +3 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +1 -1
- data/vendor/crates/spikard-core/src/validation/mod.rs +1 -1
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +1 -1
- data/vendor/crates/spikard-core/tests/error_mapper.rs +2 -2
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_full.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +1 -1
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +4 -4
- data/vendor/crates/spikard-http/Cargo.toml +4 -2
- data/vendor/crates/spikard-http/src/cors.rs +32 -11
- data/vendor/crates/spikard-http/src/di_handler.rs +12 -8
- data/vendor/crates/spikard-http/src/grpc/framing.rs +469 -0
- data/vendor/crates/spikard-http/src/grpc/handler.rs +887 -25
- data/vendor/crates/spikard-http/src/grpc/mod.rs +114 -22
- data/vendor/crates/spikard-http/src/grpc/service.rs +232 -2
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +80 -2
- data/vendor/crates/spikard-http/src/handler_trait.rs +204 -27
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +15 -15
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +2 -2
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2 -2
- data/vendor/crates/spikard-http/src/lib.rs +1 -1
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +2 -2
- data/vendor/crates/spikard-http/src/lifecycle.rs +4 -4
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +2 -0
- data/vendor/crates/spikard-http/src/server/fast_router.rs +186 -0
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +324 -23
- data/vendor/crates/spikard-http/src/server/handler.rs +33 -22
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +21 -2
- data/vendor/crates/spikard-http/src/server/mod.rs +125 -20
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +126 -44
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +80 -69
- data/vendor/crates/spikard-http/tests/common/handlers.rs +2 -2
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +12 -12
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +2 -2
- data/vendor/crates/spikard-http/tests/di_integration.rs +6 -6
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +430 -0
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +738 -0
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +13 -9
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +974 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +2 -2
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +4 -4
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +2 -2
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +1 -0
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +140 -0
- data/vendor/crates/spikard-rb/Cargo.toml +3 -1
- data/vendor/crates/spikard-rb/src/conversion.rs +138 -4
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +706 -229
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +6 -2
- data/vendor/crates/spikard-rb/src/gvl.rs +2 -2
- data/vendor/crates/spikard-rb/src/handler.rs +169 -91
- data/vendor/crates/spikard-rb/src/lib.rs +444 -62
- data/vendor/crates/spikard-rb/src/lifecycle.rs +29 -1
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +108 -43
- data/vendor/crates/spikard-rb/src/request.rs +117 -20
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +52 -25
- data/vendor/crates/spikard-rb/src/server.rs +23 -14
- data/vendor/crates/spikard-rb/src/testing/client.rs +5 -4
- data/vendor/crates/spikard-rb/src/testing/sse.rs +1 -36
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +3 -38
- data/vendor/crates/spikard-rb/src/websocket.rs +32 -23
- data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
- metadata +14 -4
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +0 -5
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +0 -2
|
@@ -440,7 +440,7 @@ mod tests {
|
|
|
440
440
|
let config: Option<Arc<String>> = resolved.get("config");
|
|
441
441
|
let config_value = config.map(|c| (*c).clone()).unwrap_or_default();
|
|
442
442
|
|
|
443
|
-
Ok(Arc::new(format!("Service using {}"
|
|
443
|
+
Ok(Arc::new(format!("Service using {config_value}")) as Arc<dyn Any + Send + Sync>)
|
|
444
444
|
})
|
|
445
445
|
})
|
|
446
446
|
.build();
|
|
@@ -524,7 +524,7 @@ mod tests {
|
|
|
524
524
|
.singleton(false)
|
|
525
525
|
.build();
|
|
526
526
|
|
|
527
|
-
let debug_str = format!("{:?}"
|
|
527
|
+
let debug_str = format!("{factory:?}");
|
|
528
528
|
assert!(debug_str.contains("FactoryDependency"));
|
|
529
529
|
assert!(debug_str.contains("test"));
|
|
530
530
|
assert!(debug_str.contains("dep1"));
|
|
@@ -31,7 +31,7 @@ type CleanupTask = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
|
|
|
31
31
|
///
|
|
32
32
|
/// // Insert a dependency
|
|
33
33
|
/// let value = Arc::new(42i32);
|
|
34
|
-
/// resolved.insert("answer".to_string(), value
|
|
34
|
+
/// resolved.insert("answer".to_string(), value);
|
|
35
35
|
///
|
|
36
36
|
/// // Retrieve with type safety
|
|
37
37
|
/// let retrieved: Option<Arc<i32>> = resolved.get("answer");
|
|
@@ -335,7 +335,7 @@ mod tests {
|
|
|
335
335
|
fn test_insert_and_get() {
|
|
336
336
|
let mut resolved = ResolvedDependencies::new();
|
|
337
337
|
let value = Arc::new(42i32);
|
|
338
|
-
resolved.insert("answer".to_string(), value
|
|
338
|
+
resolved.insert("answer".to_string(), value);
|
|
339
339
|
|
|
340
340
|
let retrieved: Option<Arc<i32>> = resolved.get("answer");
|
|
341
341
|
assert_eq!(retrieved.map(|v| *v), Some(42));
|
|
@@ -275,7 +275,7 @@ mod tests {
|
|
|
275
275
|
#[test]
|
|
276
276
|
fn test_debug() {
|
|
277
277
|
let dep = ValueDependency::new("test", 42i32);
|
|
278
|
-
let debug_str = format!("{:?}"
|
|
278
|
+
let debug_str = format!("{dep:?}");
|
|
279
279
|
assert!(debug_str.contains("ValueDependency"));
|
|
280
280
|
assert!(debug_str.contains("test"));
|
|
281
281
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use serde::{Deserialize, Serialize};
|
|
2
2
|
use serde_json::Value;
|
|
3
|
+
use std::sync::OnceLock;
|
|
3
4
|
|
|
4
5
|
/// HTTP method
|
|
5
6
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
@@ -67,6 +68,73 @@ pub struct CorsConfig {
|
|
|
67
68
|
pub max_age: Option<u32>,
|
|
68
69
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
69
70
|
pub allow_credentials: Option<bool>,
|
|
71
|
+
|
|
72
|
+
// Optimized caches (lazy-initialized on first use)
|
|
73
|
+
#[serde(skip)]
|
|
74
|
+
#[doc(hidden)]
|
|
75
|
+
pub methods_joined_cache: OnceLock<String>,
|
|
76
|
+
#[serde(skip)]
|
|
77
|
+
#[doc(hidden)]
|
|
78
|
+
pub headers_joined_cache: OnceLock<String>,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl CorsConfig {
|
|
82
|
+
/// Get the cached joined methods string for preflight responses
|
|
83
|
+
pub fn allowed_methods_joined(&self) -> &str {
|
|
84
|
+
self.methods_joined_cache
|
|
85
|
+
.get_or_init(|| self.allowed_methods.join(", "))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Get the cached joined headers string for preflight responses
|
|
89
|
+
pub fn allowed_headers_joined(&self) -> &str {
|
|
90
|
+
self.headers_joined_cache
|
|
91
|
+
.get_or_init(|| self.allowed_headers.join(", "))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Check if an origin is allowed (O(1) with wildcard, O(n) for exact match)
|
|
95
|
+
pub fn is_origin_allowed(&self, origin: &str) -> bool {
|
|
96
|
+
if origin.is_empty() {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
self.allowed_origins.iter().any(|o| o == "*" || o == origin)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Check if a method is allowed (O(1) with wildcard, O(n) for exact match)
|
|
103
|
+
pub fn is_method_allowed(&self, method: &str) -> bool {
|
|
104
|
+
self.allowed_methods
|
|
105
|
+
.iter()
|
|
106
|
+
.any(|m| m == "*" || m.eq_ignore_ascii_case(method))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Check if all requested headers are allowed (O(n) where n = num requested headers)
|
|
110
|
+
pub fn are_headers_allowed(&self, requested: &[&str]) -> bool {
|
|
111
|
+
// Check if wildcard is set
|
|
112
|
+
if self.allowed_headers.iter().any(|h| h == "*") {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check each requested header
|
|
117
|
+
requested.iter().all(|req_header| {
|
|
118
|
+
self.allowed_headers
|
|
119
|
+
.iter()
|
|
120
|
+
.any(|h| h.to_lowercase() == req_header.to_lowercase())
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
impl Default for CorsConfig {
|
|
126
|
+
fn default() -> Self {
|
|
127
|
+
Self {
|
|
128
|
+
allowed_origins: vec!["*".to_string()],
|
|
129
|
+
allowed_methods: vec!["*".to_string()],
|
|
130
|
+
allowed_headers: vec![],
|
|
131
|
+
expose_headers: None,
|
|
132
|
+
max_age: None,
|
|
133
|
+
allow_credentials: None,
|
|
134
|
+
methods_joined_cache: OnceLock::new(),
|
|
135
|
+
headers_joined_cache: OnceLock::new(),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
70
138
|
}
|
|
71
139
|
|
|
72
140
|
/// Route metadata extracted from bindings
|
|
@@ -93,6 +161,11 @@ pub struct RouteMetadata {
|
|
|
93
161
|
/// JSON-RPC method metadata (if this route is exposed as a JSON-RPC method)
|
|
94
162
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
95
163
|
pub jsonrpc_method: Option<Value>,
|
|
164
|
+
/// Optional static response configuration: `{"status": 200, "body": "OK", "content_type": "text/plain"}`
|
|
165
|
+
/// When present, the handler is replaced by a `StaticResponseHandler` that bypasses the full
|
|
166
|
+
/// middleware pipeline for maximum throughput.
|
|
167
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
168
|
+
pub static_response: Option<Value>,
|
|
96
169
|
}
|
|
97
170
|
|
|
98
171
|
/// Compression configuration shared across runtimes
|
|
@@ -386,6 +459,7 @@ mod tests {
|
|
|
386
459
|
expose_headers: None,
|
|
387
460
|
max_age: None,
|
|
388
461
|
allow_credentials: None,
|
|
462
|
+
..Default::default()
|
|
389
463
|
};
|
|
390
464
|
assert_eq!(cors.allowed_origins.len(), 1);
|
|
391
465
|
assert_eq!(cors.allowed_methods.len(), 2);
|
|
@@ -408,6 +482,7 @@ mod tests {
|
|
|
408
482
|
#[cfg(feature = "di")]
|
|
409
483
|
handler_dependencies: None,
|
|
410
484
|
jsonrpc_method: None,
|
|
485
|
+
static_response: None,
|
|
411
486
|
};
|
|
412
487
|
assert_eq!(metadata.method, "GET");
|
|
413
488
|
assert_eq!(metadata.path, "/api/users");
|
|
@@ -465,11 +465,11 @@ mod tests {
|
|
|
465
465
|
#[test]
|
|
466
466
|
fn test_hook_result_debug_format() {
|
|
467
467
|
let continue_result: HookResult<i32, String> = HookResult::Continue(100);
|
|
468
|
-
let debug_str = format!("{:?}"
|
|
468
|
+
let debug_str = format!("{continue_result:?}");
|
|
469
469
|
assert!(debug_str.contains("Continue"));
|
|
470
470
|
|
|
471
471
|
let short_circuit_result: HookResult<i32, String> = HookResult::ShortCircuit("err".to_string());
|
|
472
|
-
let debug_str = format!("{:?}"
|
|
472
|
+
let debug_str = format!("{short_circuit_result:?}");
|
|
473
473
|
assert!(debug_str.contains("ShortCircuit"));
|
|
474
474
|
}
|
|
475
475
|
|
|
@@ -494,16 +494,16 @@ mod tests {
|
|
|
494
494
|
#[test]
|
|
495
495
|
fn test_lifecycle_hooks_debug_format_empty() {
|
|
496
496
|
let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
|
|
497
|
-
let debug_str = format!("{:?}"
|
|
497
|
+
let debug_str = format!("{hooks:?}");
|
|
498
498
|
assert!(debug_str.contains("LifecycleHooks"));
|
|
499
499
|
assert!(debug_str.contains("on_request_count"));
|
|
500
|
-
assert!(debug_str.contains(
|
|
500
|
+
assert!(debug_str.contains('0'));
|
|
501
501
|
}
|
|
502
502
|
|
|
503
503
|
#[test]
|
|
504
504
|
fn test_lifecycle_hooks_clone() {
|
|
505
505
|
let hooks1: LifecycleHooks<String, String> = LifecycleHooks::default();
|
|
506
|
-
let hooks2 = hooks1
|
|
506
|
+
let hooks2 = hooks1;
|
|
507
507
|
assert!(hooks2.is_empty());
|
|
508
508
|
}
|
|
509
509
|
|
|
@@ -566,15 +566,15 @@ mod tests {
|
|
|
566
566
|
|
|
567
567
|
#[cfg(not(target_arch = "wasm32"))]
|
|
568
568
|
impl NativeLifecycleHook<String, String> for TestRequestHook {
|
|
569
|
-
fn name(&self) -> &str {
|
|
569
|
+
fn name(&self) -> &'static str {
|
|
570
570
|
"test_request_hook"
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
fn execute_request<'a>(&
|
|
573
|
+
fn execute_request<'a>(&self, req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
574
574
|
Box::pin(async move { Ok(HookResult::Continue(req + "_modified")) })
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
-
fn execute_response<'a>(&
|
|
577
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
578
578
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
579
579
|
}
|
|
580
580
|
}
|
|
@@ -588,11 +588,11 @@ mod tests {
|
|
|
588
588
|
"test_request_hook"
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
fn execute_request<'a>(&
|
|
591
|
+
fn execute_request<'a>(&self, req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
592
592
|
Box::pin(async move { Ok(HookResult::Continue(req + "_modified")) })
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
-
fn execute_response<'a>(&
|
|
595
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
596
596
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
597
597
|
}
|
|
598
598
|
}
|
|
@@ -628,15 +628,15 @@ mod tests {
|
|
|
628
628
|
|
|
629
629
|
#[cfg(not(target_arch = "wasm32"))]
|
|
630
630
|
impl NativeLifecycleHook<String, String> for TestResponseHook {
|
|
631
|
-
fn name(&self) -> &str {
|
|
631
|
+
fn name(&self) -> &'static str {
|
|
632
632
|
"test_response_hook"
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
-
fn execute_request<'a>(&
|
|
635
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
636
636
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
fn execute_response<'a>(&
|
|
639
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
640
640
|
Box::pin(async move { Ok(HookResult::Continue(resp + "_processed")) })
|
|
641
641
|
}
|
|
642
642
|
}
|
|
@@ -650,11 +650,11 @@ mod tests {
|
|
|
650
650
|
"test_response_hook"
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
-
fn execute_request<'a>(&
|
|
653
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
654
654
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
655
655
|
}
|
|
656
656
|
|
|
657
|
-
fn execute_response<'a>(&
|
|
657
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
658
658
|
Box::pin(async move { Ok(HookResult::Continue(resp + "_processed")) })
|
|
659
659
|
}
|
|
660
660
|
}
|
|
@@ -783,15 +783,15 @@ mod tests {
|
|
|
783
783
|
|
|
784
784
|
#[cfg(not(target_arch = "wasm32"))]
|
|
785
785
|
impl NativeLifecycleHook<String, String> for TestShortCircuitHook {
|
|
786
|
-
fn name(&self) -> &str {
|
|
786
|
+
fn name(&self) -> &'static str {
|
|
787
787
|
"short_circuit"
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
-
fn execute_request<'a>(&
|
|
790
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
791
791
|
Box::pin(async { Ok(HookResult::ShortCircuit("short_circuit_response".to_string())) })
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
-
fn execute_response<'a>(&
|
|
794
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
795
795
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
796
796
|
}
|
|
797
797
|
}
|
|
@@ -805,11 +805,11 @@ mod tests {
|
|
|
805
805
|
"short_circuit"
|
|
806
806
|
}
|
|
807
807
|
|
|
808
|
-
fn execute_request<'a>(&
|
|
808
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
809
809
|
Box::pin(async { Ok(HookResult::ShortCircuit("short_circuit_response".to_string())) })
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
-
fn execute_response<'a>(&
|
|
812
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
813
813
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
814
814
|
}
|
|
815
815
|
}
|
|
@@ -858,15 +858,15 @@ mod tests {
|
|
|
858
858
|
|
|
859
859
|
#[cfg(not(target_arch = "wasm32"))]
|
|
860
860
|
impl NativeLifecycleHook<String, String> for TestResponseShortCircuitHook {
|
|
861
|
-
fn name(&self) -> &str {
|
|
861
|
+
fn name(&self) -> &'static str {
|
|
862
862
|
"response_short_circuit"
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
-
fn execute_request<'a>(&
|
|
865
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
866
866
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
867
867
|
}
|
|
868
868
|
|
|
869
|
-
fn execute_response<'a>(&
|
|
869
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
870
870
|
Box::pin(async move { Ok(HookResult::ShortCircuit("short_circuit_".to_string() + &resp)) })
|
|
871
871
|
}
|
|
872
872
|
}
|
|
@@ -880,11 +880,11 @@ mod tests {
|
|
|
880
880
|
"response_short_circuit"
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
-
fn execute_request<'a>(&
|
|
883
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
884
884
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
885
885
|
}
|
|
886
886
|
|
|
887
|
-
fn execute_response<'a>(&
|
|
887
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
888
888
|
Box::pin(async move { Ok(HookResult::ShortCircuit("short_circuit_".to_string() + &resp)) })
|
|
889
889
|
}
|
|
890
890
|
}
|
|
@@ -938,16 +938,16 @@ mod tests {
|
|
|
938
938
|
|
|
939
939
|
#[cfg(not(target_arch = "wasm32"))]
|
|
940
940
|
impl NativeLifecycleHook<String, String> for TestAppendHook {
|
|
941
|
-
fn name(&self) -> &str {
|
|
941
|
+
fn name(&self) -> &'static str {
|
|
942
942
|
"append"
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
-
fn execute_request<'a>(&
|
|
945
|
+
fn execute_request<'a>(&self, req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
946
946
|
let suffix = self.0;
|
|
947
947
|
Box::pin(async move { Ok(HookResult::Continue(req + suffix)) })
|
|
948
948
|
}
|
|
949
949
|
|
|
950
|
-
fn execute_response<'a>(&
|
|
950
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
951
951
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
952
952
|
}
|
|
953
953
|
}
|
|
@@ -961,12 +961,12 @@ mod tests {
|
|
|
961
961
|
"append"
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
-
fn execute_request<'a>(&
|
|
964
|
+
fn execute_request<'a>(&self, req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
965
965
|
let suffix = self.0;
|
|
966
966
|
Box::pin(async move { Ok(HookResult::Continue(req + suffix)) })
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
-
fn execute_response<'a>(&
|
|
969
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
970
970
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
971
971
|
}
|
|
972
972
|
}
|
|
@@ -994,15 +994,15 @@ mod tests {
|
|
|
994
994
|
|
|
995
995
|
#[cfg(not(target_arch = "wasm32"))]
|
|
996
996
|
impl NativeLifecycleHook<String, String> for TestAppendResponseHook {
|
|
997
|
-
fn name(&self) -> &str {
|
|
997
|
+
fn name(&self) -> &'static str {
|
|
998
998
|
"append_response"
|
|
999
999
|
}
|
|
1000
1000
|
|
|
1001
|
-
fn execute_request<'a>(&
|
|
1001
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
1002
1002
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
-
fn execute_response<'a>(&
|
|
1005
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
1006
1006
|
let suffix = self.0;
|
|
1007
1007
|
Box::pin(async move { Ok(HookResult::Continue(resp + suffix)) })
|
|
1008
1008
|
}
|
|
@@ -1017,11 +1017,11 @@ mod tests {
|
|
|
1017
1017
|
"append_response"
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
fn execute_request<'a>(&
|
|
1020
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
1021
1021
|
Box::pin(async { Err("not implemented".to_string()) })
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
-
fn execute_response<'a>(&
|
|
1024
|
+
fn execute_response<'a>(&self, resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
1025
1025
|
let suffix = self.0;
|
|
1026
1026
|
Box::pin(async move { Ok(HookResult::Continue(resp + suffix)) })
|
|
1027
1027
|
}
|
|
@@ -1055,15 +1055,15 @@ mod tests {
|
|
|
1055
1055
|
|
|
1056
1056
|
#[cfg(not(target_arch = "wasm32"))]
|
|
1057
1057
|
impl NativeLifecycleHook<String, String> for TestErrorHook {
|
|
1058
|
-
fn name(&self) -> &str {
|
|
1058
|
+
fn name(&self) -> &'static str {
|
|
1059
1059
|
"error_hook"
|
|
1060
1060
|
}
|
|
1061
1061
|
|
|
1062
|
-
fn execute_request<'a>(&
|
|
1062
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureSend<'a, String, String> {
|
|
1063
1063
|
Box::pin(async { Err("hook_error".to_string()) })
|
|
1064
1064
|
}
|
|
1065
1065
|
|
|
1066
|
-
fn execute_response<'a>(&
|
|
1066
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureSend<'a, String> {
|
|
1067
1067
|
Box::pin(async { Err("hook_error".to_string()) })
|
|
1068
1068
|
}
|
|
1069
1069
|
}
|
|
@@ -1077,11 +1077,11 @@ mod tests {
|
|
|
1077
1077
|
"error_hook"
|
|
1078
1078
|
}
|
|
1079
1079
|
|
|
1080
|
-
fn execute_request<'a>(&
|
|
1080
|
+
fn execute_request<'a>(&self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
|
|
1081
1081
|
Box::pin(async { Err("hook_error".to_string()) })
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
|
-
fn execute_response<'a>(&
|
|
1084
|
+
fn execute_response<'a>(&self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
|
|
1085
1085
|
Box::pin(async { Err("hook_error".to_string()) })
|
|
1086
1086
|
}
|
|
1087
1087
|
}
|
|
@@ -1160,9 +1160,9 @@ mod tests {
|
|
|
1160
1160
|
#[cfg(target_arch = "wasm32")]
|
|
1161
1161
|
hooks.add_on_request(Arc::new(TestRequestHookLocal));
|
|
1162
1162
|
|
|
1163
|
-
let debug_str = format!("{:?}"
|
|
1163
|
+
let debug_str = format!("{hooks:?}");
|
|
1164
1164
|
assert!(debug_str.contains("on_request_count"));
|
|
1165
|
-
assert!(debug_str.contains(
|
|
1165
|
+
assert!(debug_str.contains('1'));
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
#[cfg(not(target_arch = "wasm32"))]
|
|
@@ -1243,7 +1243,7 @@ mod tests {
|
|
|
1243
1243
|
}
|
|
1244
1244
|
|
|
1245
1245
|
#[test]
|
|
1246
|
-
fn
|
|
1246
|
+
fn test_is_empty_before_and_after_adding_hooks() {
|
|
1247
1247
|
let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
|
|
1248
1248
|
assert!(hooks.is_empty());
|
|
1249
1249
|
|
|
@@ -824,7 +824,7 @@ mod tests {
|
|
|
824
824
|
&HashMap::new(),
|
|
825
825
|
&HashMap::new(),
|
|
826
826
|
);
|
|
827
|
-
assert!(result.is_ok(), "Validation should succeed: {:?}"
|
|
827
|
+
assert!(result.is_ok(), "Validation should succeed: {result:?}");
|
|
828
828
|
|
|
829
829
|
let params = result.unwrap();
|
|
830
830
|
assert_eq!(params, json!({"item_id": "foobar"}));
|
|
@@ -858,9 +858,9 @@ mod tests {
|
|
|
858
858
|
&HashMap::new(),
|
|
859
859
|
);
|
|
860
860
|
if result.is_err() {
|
|
861
|
-
eprintln!("Error for 'True': {:?}"
|
|
861
|
+
eprintln!("Error for 'True': {result:?}");
|
|
862
862
|
}
|
|
863
|
-
assert!(result.is_ok(), "Validation should succeed for 'True': {:?}"
|
|
863
|
+
assert!(result.is_ok(), "Validation should succeed for 'True': {result:?}");
|
|
864
864
|
let params = result.unwrap();
|
|
865
865
|
assert_eq!(params, json!({"value": true}));
|
|
866
866
|
|
|
@@ -873,7 +873,7 @@ mod tests {
|
|
|
873
873
|
&HashMap::new(),
|
|
874
874
|
&HashMap::new(),
|
|
875
875
|
);
|
|
876
|
-
assert!(result.is_ok(), "Validation should succeed for '1': {:?}"
|
|
876
|
+
assert!(result.is_ok(), "Validation should succeed for '1': {result:?}");
|
|
877
877
|
let params = result.unwrap();
|
|
878
878
|
assert_eq!(params, json!({"value": true}));
|
|
879
879
|
|
|
@@ -886,7 +886,7 @@ mod tests {
|
|
|
886
886
|
&HashMap::new(),
|
|
887
887
|
&HashMap::new(),
|
|
888
888
|
);
|
|
889
|
-
assert!(result.is_ok(), "Validation should succeed for 'false': {:?}"
|
|
889
|
+
assert!(result.is_ok(), "Validation should succeed for 'false': {result:?}");
|
|
890
890
|
let params = result.unwrap();
|
|
891
891
|
assert_eq!(params, json!({"value": false}));
|
|
892
892
|
|
|
@@ -899,7 +899,7 @@ mod tests {
|
|
|
899
899
|
&HashMap::new(),
|
|
900
900
|
&HashMap::new(),
|
|
901
901
|
);
|
|
902
|
-
assert!(result.is_ok(), "Validation should succeed for 'TRUE': {:?}"
|
|
902
|
+
assert!(result.is_ok(), "Validation should succeed for 'TRUE': {result:?}");
|
|
903
903
|
let params = result.unwrap();
|
|
904
904
|
assert_eq!(params, json!({"value": true}));
|
|
905
905
|
}
|
|
@@ -930,7 +930,7 @@ mod tests {
|
|
|
930
930
|
&HashMap::new(),
|
|
931
931
|
&HashMap::new(),
|
|
932
932
|
);
|
|
933
|
-
assert!(result.is_ok(), "Validation should succeed for integer 1: {:?}"
|
|
933
|
+
assert!(result.is_ok(), "Validation should succeed for integer 1: {result:?}");
|
|
934
934
|
let params = result.unwrap();
|
|
935
935
|
assert_eq!(params, json!({"flag": true}));
|
|
936
936
|
|
|
@@ -944,7 +944,7 @@ mod tests {
|
|
|
944
944
|
&HashMap::new(),
|
|
945
945
|
&HashMap::new(),
|
|
946
946
|
);
|
|
947
|
-
assert!(result.is_ok(), "Validation should succeed for integer 0: {:?}"
|
|
947
|
+
assert!(result.is_ok(), "Validation should succeed for integer 0: {result:?}");
|
|
948
948
|
let params = result.unwrap();
|
|
949
949
|
assert_eq!(params, json!({"flag": false}));
|
|
950
950
|
|
|
@@ -958,11 +958,7 @@ mod tests {
|
|
|
958
958
|
&HashMap::new(),
|
|
959
959
|
&HashMap::new(),
|
|
960
960
|
);
|
|
961
|
-
assert!(
|
|
962
|
-
result.is_ok(),
|
|
963
|
-
"Validation should succeed for boolean true: {:?}",
|
|
964
|
-
result
|
|
965
|
-
);
|
|
961
|
+
assert!(result.is_ok(), "Validation should succeed for boolean true: {result:?}");
|
|
966
962
|
let params = result.unwrap();
|
|
967
963
|
assert_eq!(params, json!({"flag": true}));
|
|
968
964
|
|
|
@@ -978,8 +974,7 @@ mod tests {
|
|
|
978
974
|
);
|
|
979
975
|
assert!(
|
|
980
976
|
result.is_ok(),
|
|
981
|
-
"Validation should succeed for boolean false: {:?}"
|
|
982
|
-
result
|
|
977
|
+
"Validation should succeed for boolean false: {result:?}"
|
|
983
978
|
);
|
|
984
979
|
let params = result.unwrap();
|
|
985
980
|
assert_eq!(params, json!({"flag": false}));
|
|
@@ -1191,7 +1186,7 @@ mod tests {
|
|
|
1191
1186
|
|
|
1192
1187
|
let validator = ParameterValidator::new(schema).unwrap();
|
|
1193
1188
|
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
1194
|
-
raw_query_params.insert("flag".to_string(), vec![
|
|
1189
|
+
raw_query_params.insert("flag".to_string(), vec![String::new()]);
|
|
1195
1190
|
|
|
1196
1191
|
let result = validator
|
|
1197
1192
|
.validate_and_extract(
|
|
@@ -1349,7 +1344,7 @@ mod tests {
|
|
|
1349
1344
|
|
|
1350
1345
|
let validator = ParameterValidator::new(schema).unwrap();
|
|
1351
1346
|
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
1352
|
-
raw_query_params.insert("flag".to_string(), vec![
|
|
1347
|
+
raw_query_params.insert("flag".to_string(), vec![String::new()]);
|
|
1353
1348
|
|
|
1354
1349
|
let result = validator.validate_and_extract(
|
|
1355
1350
|
&json!({"flag": ""}),
|
|
@@ -1793,7 +1788,7 @@ mod tests {
|
|
|
1793
1788
|
&HashMap::new(),
|
|
1794
1789
|
);
|
|
1795
1790
|
|
|
1796
|
-
assert!(result.is_ok(), "String parameter should pass: {:?}"
|
|
1791
|
+
assert!(result.is_ok(), "String parameter should pass: {result:?}");
|
|
1797
1792
|
let extracted = result.unwrap();
|
|
1798
1793
|
assert_eq!(extracted["start_time"], json!(time_string));
|
|
1799
1794
|
}
|
|
@@ -2386,7 +2381,7 @@ mod tests {
|
|
|
2386
2381
|
|
|
2387
2382
|
let validator = ParameterValidator::new(schema).unwrap();
|
|
2388
2383
|
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
2389
|
-
raw_query_params.insert("flag".to_string(), vec![
|
|
2384
|
+
raw_query_params.insert("flag".to_string(), vec![String::new()]);
|
|
2390
2385
|
|
|
2391
2386
|
let result = validator.validate_and_extract(
|
|
2392
2387
|
&json!({"flag": ""}),
|
|
@@ -247,7 +247,7 @@ mod tests {
|
|
|
247
247
|
error_type: "missing".to_string(),
|
|
248
248
|
loc: vec!["body".to_string(), "username".to_string()],
|
|
249
249
|
msg: "Field required".to_string(),
|
|
250
|
-
input: Value::String(
|
|
250
|
+
input: Value::String(String::new()),
|
|
251
251
|
ctx: None,
|
|
252
252
|
},
|
|
253
253
|
ValidationErrorDetail {
|
|
@@ -830,20 +830,11 @@ mod tests {
|
|
|
830
830
|
fn test_request_data_large_json_body() {
|
|
831
831
|
let mut large_obj = serde_json::Map::new();
|
|
832
832
|
for i in 0..100 {
|
|
833
|
-
large_obj.insert(
|
|
834
|
-
format!("field_{}", i),
|
|
835
|
-
json!({"value": i, "name": format!("item_{}", i)}),
|
|
836
|
-
);
|
|
833
|
+
large_obj.insert(format!("field_{i}"), json!({"value": i, "name": format!("item_{}", i)}));
|
|
837
834
|
}
|
|
838
835
|
let body = json!(large_obj);
|
|
839
836
|
|
|
840
|
-
let data = create_request_data(
|
|
841
|
-
"/api/batch",
|
|
842
|
-
"POST",
|
|
843
|
-
body.clone(),
|
|
844
|
-
json!({}),
|
|
845
|
-
RequestDataCollections::default(),
|
|
846
|
-
);
|
|
837
|
+
let data = create_request_data("/api/batch", "POST", body, json!({}), RequestDataCollections::default());
|
|
847
838
|
|
|
848
839
|
assert!(data.body.is_object());
|
|
849
840
|
assert_eq!(data.body.as_object().unwrap().len(), 100);
|
|
@@ -1000,10 +991,10 @@ mod tests {
|
|
|
1000
991
|
#[test]
|
|
1001
992
|
fn test_request_data_empty_string_values() {
|
|
1002
993
|
let mut headers = HashMap::new();
|
|
1003
|
-
headers.insert("empty-header".to_string(),
|
|
994
|
+
headers.insert("empty-header".to_string(), String::new());
|
|
1004
995
|
|
|
1005
996
|
let mut path_params = HashMap::new();
|
|
1006
|
-
path_params.insert("id".to_string(),
|
|
997
|
+
path_params.insert("id".to_string(), String::new());
|
|
1007
998
|
|
|
1008
999
|
let data = create_request_data(
|
|
1009
1000
|
"/api/test",
|
|
@@ -1017,8 +1008,8 @@ mod tests {
|
|
|
1017
1008
|
},
|
|
1018
1009
|
);
|
|
1019
1010
|
|
|
1020
|
-
assert_eq!(data.headers.get("empty-header"), Some(&
|
|
1021
|
-
assert_eq!(data.path_params.get("id"), Some(&
|
|
1011
|
+
assert_eq!(data.headers.get("empty-header"), Some(&String::new()));
|
|
1012
|
+
assert_eq!(data.path_params.get("id"), Some(&String::new()));
|
|
1022
1013
|
}
|
|
1023
1014
|
|
|
1024
1015
|
#[test]
|
|
@@ -1092,7 +1083,7 @@ mod tests {
|
|
|
1092
1083
|
RequestDataCollections::default(),
|
|
1093
1084
|
);
|
|
1094
1085
|
|
|
1095
|
-
let debug_str = format!("{:?}"
|
|
1086
|
+
let debug_str = format!("{data:?}");
|
|
1096
1087
|
assert!(debug_str.contains("RequestData"));
|
|
1097
1088
|
assert!(debug_str.contains("/api/test"));
|
|
1098
1089
|
}
|
|
@@ -278,6 +278,7 @@ mod tests {
|
|
|
278
278
|
cors: None,
|
|
279
279
|
body_param_name: None,
|
|
280
280
|
jsonrpc_method: None,
|
|
281
|
+
static_response: None,
|
|
281
282
|
#[cfg(feature = "di")]
|
|
282
283
|
handler_dependencies: None,
|
|
283
284
|
};
|
|
@@ -312,6 +313,7 @@ mod tests {
|
|
|
312
313
|
cors: None,
|
|
313
314
|
body_param_name: None,
|
|
314
315
|
jsonrpc_method: None,
|
|
316
|
+
static_response: None,
|
|
315
317
|
#[cfg(feature = "di")]
|
|
316
318
|
handler_dependencies: None,
|
|
317
319
|
};
|
|
@@ -344,6 +346,7 @@ mod tests {
|
|
|
344
346
|
cors: None,
|
|
345
347
|
body_param_name: None,
|
|
346
348
|
jsonrpc_method: None,
|
|
349
|
+
static_response: None,
|
|
347
350
|
#[cfg(feature = "di")]
|
|
348
351
|
handler_dependencies: None,
|
|
349
352
|
};
|
|
@@ -360,6 +363,7 @@ mod tests {
|
|
|
360
363
|
cors: None,
|
|
361
364
|
body_param_name: None,
|
|
362
365
|
jsonrpc_method: None,
|
|
366
|
+
static_response: None,
|
|
363
367
|
#[cfg(feature = "di")]
|
|
364
368
|
handler_dependencies: None,
|
|
365
369
|
};
|
|
@@ -438,6 +442,7 @@ mod tests {
|
|
|
438
442
|
cors: None,
|
|
439
443
|
body_param_name: None,
|
|
440
444
|
jsonrpc_method: None,
|
|
445
|
+
static_response: None,
|
|
441
446
|
#[cfg(feature = "di")]
|
|
442
447
|
handler_dependencies: None,
|
|
443
448
|
};
|
|
@@ -511,6 +516,7 @@ mod tests {
|
|
|
511
516
|
cors: None,
|
|
512
517
|
body_param_name: None,
|
|
513
518
|
jsonrpc_method: None,
|
|
519
|
+
static_response: None,
|
|
514
520
|
#[cfg(feature = "di")]
|
|
515
521
|
handler_dependencies: None,
|
|
516
522
|
};
|