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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -10
  3. data/ext/spikard_rb/Cargo.lock +234 -162
  4. data/ext/spikard_rb/Cargo.toml +2 -2
  5. data/ext/spikard_rb/extconf.rb +4 -3
  6. data/lib/spikard/config.rb +88 -12
  7. data/lib/spikard/testing.rb +3 -1
  8. data/lib/spikard/version.rb +1 -1
  9. data/lib/spikard.rb +11 -0
  10. data/vendor/crates/spikard-bindings-shared/Cargo.toml +3 -6
  11. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +8 -8
  12. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +2 -2
  13. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +4 -4
  14. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +10 -4
  15. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +3 -3
  16. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +10 -5
  17. data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +829 -0
  18. data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +587 -0
  19. data/vendor/crates/spikard-bindings-shared/src/lib.rs +7 -0
  20. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +11 -11
  21. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +9 -37
  22. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +436 -3
  23. data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +944 -0
  24. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +4 -4
  25. data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +3 -2
  26. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +13 -13
  27. data/vendor/crates/spikard-bindings-shared/tests/{comprehensive_coverage.rs → full_coverage.rs} +10 -5
  28. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +14 -14
  29. data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +669 -0
  30. data/vendor/crates/spikard-core/Cargo.toml +3 -3
  31. data/vendor/crates/spikard-core/src/di/container.rs +1 -1
  32. data/vendor/crates/spikard-core/src/di/factory.rs +2 -2
  33. data/vendor/crates/spikard-core/src/di/resolved.rs +2 -2
  34. data/vendor/crates/spikard-core/src/di/value.rs +1 -1
  35. data/vendor/crates/spikard-core/src/http.rs +75 -0
  36. data/vendor/crates/spikard-core/src/lifecycle.rs +43 -43
  37. data/vendor/crates/spikard-core/src/parameters.rs +14 -19
  38. data/vendor/crates/spikard-core/src/problem.rs +1 -1
  39. data/vendor/crates/spikard-core/src/request_data.rs +7 -16
  40. data/vendor/crates/spikard-core/src/router.rs +6 -0
  41. data/vendor/crates/spikard-core/src/schema_registry.rs +2 -3
  42. data/vendor/crates/spikard-core/src/type_hints.rs +3 -2
  43. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +1 -1
  44. data/vendor/crates/spikard-core/src/validation/mod.rs +1 -1
  45. data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +1 -1
  46. data/vendor/crates/spikard-core/tests/error_mapper.rs +2 -2
  47. data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +1 -1
  48. data/vendor/crates/spikard-core/tests/parameters_full.rs +1 -1
  49. data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +1 -1
  50. data/vendor/crates/spikard-core/tests/validation_coverage.rs +4 -4
  51. data/vendor/crates/spikard-http/Cargo.toml +4 -2
  52. data/vendor/crates/spikard-http/src/cors.rs +32 -11
  53. data/vendor/crates/spikard-http/src/di_handler.rs +12 -8
  54. data/vendor/crates/spikard-http/src/grpc/framing.rs +469 -0
  55. data/vendor/crates/spikard-http/src/grpc/handler.rs +887 -25
  56. data/vendor/crates/spikard-http/src/grpc/mod.rs +114 -22
  57. data/vendor/crates/spikard-http/src/grpc/service.rs +232 -2
  58. data/vendor/crates/spikard-http/src/grpc/streaming.rs +80 -2
  59. data/vendor/crates/spikard-http/src/handler_trait.rs +204 -27
  60. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +15 -15
  61. data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +2 -2
  62. data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2 -2
  63. data/vendor/crates/spikard-http/src/lib.rs +1 -1
  64. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +2 -2
  65. data/vendor/crates/spikard-http/src/lifecycle.rs +4 -4
  66. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +2 -0
  67. data/vendor/crates/spikard-http/src/server/fast_router.rs +186 -0
  68. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +324 -23
  69. data/vendor/crates/spikard-http/src/server/handler.rs +33 -22
  70. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +21 -2
  71. data/vendor/crates/spikard-http/src/server/mod.rs +125 -20
  72. data/vendor/crates/spikard-http/src/server/request_extraction.rs +126 -44
  73. data/vendor/crates/spikard-http/src/server/routing_factory.rs +80 -69
  74. data/vendor/crates/spikard-http/tests/common/handlers.rs +2 -2
  75. data/vendor/crates/spikard-http/tests/common/test_builders.rs +12 -12
  76. data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +2 -2
  77. data/vendor/crates/spikard-http/tests/di_integration.rs +6 -6
  78. data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +430 -0
  79. data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +738 -0
  80. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +13 -9
  81. data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +974 -0
  82. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +2 -2
  83. data/vendor/crates/spikard-http/tests/request_extraction_full.rs +4 -4
  84. data/vendor/crates/spikard-http/tests/server_config_builder.rs +2 -2
  85. data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +1 -0
  86. data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +140 -0
  87. data/vendor/crates/spikard-rb/Cargo.toml +3 -1
  88. data/vendor/crates/spikard-rb/src/conversion.rs +138 -4
  89. data/vendor/crates/spikard-rb/src/grpc/handler.rs +706 -229
  90. data/vendor/crates/spikard-rb/src/grpc/mod.rs +6 -2
  91. data/vendor/crates/spikard-rb/src/gvl.rs +2 -2
  92. data/vendor/crates/spikard-rb/src/handler.rs +169 -91
  93. data/vendor/crates/spikard-rb/src/lib.rs +444 -62
  94. data/vendor/crates/spikard-rb/src/lifecycle.rs +29 -1
  95. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +108 -43
  96. data/vendor/crates/spikard-rb/src/request.rs +117 -20
  97. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +52 -25
  98. data/vendor/crates/spikard-rb/src/server.rs +23 -14
  99. data/vendor/crates/spikard-rb/src/testing/client.rs +5 -4
  100. data/vendor/crates/spikard-rb/src/testing/sse.rs +1 -36
  101. data/vendor/crates/spikard-rb/src/testing/websocket.rs +3 -38
  102. data/vendor/crates/spikard-rb/src/websocket.rs +32 -23
  103. data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
  104. metadata +14 -4
  105. data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +0 -5
  106. 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 {}", config_value)) as Arc<dyn Any + Send + Sync>)
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!("{:?}", factory);
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.clone());
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.clone());
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!("{:?}", dep);
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!("{:?}", continue_result);
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!("{:?}", short_circuit_result);
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!("{:?}", hooks);
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("0"));
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.clone();
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>(&'a self, req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
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>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
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>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
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!("{:?}", hooks);
1163
+ let debug_str = format!("{hooks:?}");
1164
1164
  assert!(debug_str.contains("on_request_count"));
1165
- assert!(debug_str.contains("1"));
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 test_is_empty_comprehensive() {
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: {:?}", result);
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': {:?}", result);
861
+ eprintln!("Error for 'True': {result:?}");
862
862
  }
863
- assert!(result.is_ok(), "Validation should succeed for 'True': {:?}", result);
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': {:?}", result);
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': {:?}", result);
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': {:?}", result);
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: {:?}", result);
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: {:?}", result);
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!["".to_string()]);
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!["".to_string()]);
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: {:?}", result);
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!["".to_string()]);
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("".to_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(), "".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(), "".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(&"".to_string()));
1021
- assert_eq!(data.path_params.get("id"), Some(&"".to_string()));
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!("{:?}", data);
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
  };