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
@@ -179,16 +179,15 @@ mod tests {
179
179
  }
180
180
  });
181
181
 
182
- let handles: Vec<_> = (0..10)
182
+ let validators: Vec<_> = (0..10)
183
183
  .map(|_| {
184
184
  let registry = StdArc::clone(&registry);
185
185
  let schema = schema.clone();
186
186
  thread::spawn(move || registry.get_or_compile(&schema).unwrap())
187
187
  })
188
+ .map(|h| h.join().unwrap())
188
189
  .collect();
189
190
 
190
- let validators: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
191
-
192
191
  for i in 1..validators.len() {
193
192
  assert!(Arc::ptr_eq(&validators[0], &validators[i]));
194
193
  }
@@ -206,7 +206,7 @@ pub fn auto_generate_parameter_schema(route_path: &str) -> Option<Value> {
206
206
  /// "required": ["count"]
207
207
  /// });
208
208
  ///
209
- /// let merged = merge_parameter_schemas(auto_schema, explicit_schema);
209
+ /// let merged = merge_parameter_schemas(&auto_schema, &explicit_schema);
210
210
  /// // Result: auto-generated id + explicit count with constraints
211
211
  /// ```
212
212
  #[must_use]
@@ -235,6 +235,7 @@ pub fn merge_parameter_schemas(auto_schema: &Value, explicit_schema: &Value) ->
235
235
  result
236
236
  }
237
237
 
238
+ #[allow(clippy::literal_string_with_formatting_args)]
238
239
  #[cfg(test)]
239
240
  mod tests {
240
241
  use super::*;
@@ -302,7 +303,7 @@ mod tests {
302
303
  "required": ["count"]
303
304
  });
304
305
 
305
- let merged = merge_parameter_schemas(auto_schema, explicit_schema);
306
+ let merged = merge_parameter_schemas(&auto_schema, &explicit_schema);
306
307
  assert!(merged["properties"]["id"].is_object());
307
308
  assert!(merged["properties"]["count"].is_object());
308
309
  assert_eq!(merged["properties"]["count"]["minimum"], 1);
@@ -675,7 +675,7 @@ mod tests {
675
675
  let condition = ErrorCondition::EmailFormat;
676
676
  let (error_type, msg, ctx) = ErrorMapper::map_error(&condition, &schema, "", "");
677
677
  assert_eq!(error_type, "string_pattern_mismatch");
678
- assert!(msg.contains("@"));
678
+ assert!(msg.contains('@'));
679
679
  assert!(ctx.is_some());
680
680
  }
681
681
 
@@ -460,7 +460,7 @@ mod tests {
460
460
  });
461
461
 
462
462
  let result = validator.validate(&data);
463
- eprintln!("Validation result: {:?}", result);
463
+ eprintln!("Validation result: {result:?}");
464
464
 
465
465
  assert!(result.is_err(), "Should have validation errors");
466
466
  let err = result.unwrap_err();
@@ -20,7 +20,7 @@ impl Dependency for DummyDependency {
20
20
  Box::pin(async move { Ok(Arc::new(()) as Arc<dyn Any + Send + Sync>) })
21
21
  }
22
22
 
23
- fn key(&self) -> &str {
23
+ fn key(&self) -> &'static str {
24
24
  "dummy"
25
25
  }
26
26
 
@@ -466,7 +466,7 @@ fn test_error_context_includes_constraints() {
466
466
  let err = result.unwrap_err();
467
467
  assert!(err.errors[0].ctx.is_some());
468
468
  let ctx = err.errors[0].ctx.as_ref().unwrap();
469
- assert_eq!(ctx.get("min_length").and_then(|v| v.as_u64()), Some(5));
469
+ assert_eq!(ctx.get("min_length").and_then(serde_json::Value::as_u64), Some(5));
470
470
  }
471
471
 
472
472
  #[test]
@@ -631,7 +631,7 @@ fn test_error_messages_are_user_friendly() {
631
631
  let msg = &err.errors[0].msg;
632
632
  assert!(msg.contains("18") || msg.contains("minimum"));
633
633
  assert!(!msg.contains("exclusiveMinimum"));
634
- assert!(!msg.contains("$"));
634
+ assert!(!msg.contains('$'));
635
635
  }
636
636
 
637
637
  #[test]
@@ -39,7 +39,7 @@ fn test_query_boolean_empty_string_coerces_to_false() {
39
39
  let validator = ParameterValidator::new(schema).expect("validator");
40
40
 
41
41
  let mut raw_query_params = HashMap::new();
42
- raw_query_params.insert("flag".to_string(), vec!["".to_string()]);
42
+ raw_query_params.insert("flag".to_string(), vec![String::new()]);
43
43
 
44
44
  let extracted = validator.validate_and_extract(
45
45
  &json!({}),
@@ -1,7 +1,7 @@
1
1
  //! Comprehensive parameter validation tests
2
2
  //!
3
3
  //! These tests cover header, cookie, and query parameter validation scenarios
4
- //! using the ParameterValidator from spikard-core.
4
+ //! using the `ParameterValidator` from spikard-core.
5
5
 
6
6
  use serde_json::json;
7
7
  use spikard_core::parameters::ParameterValidator;
@@ -203,7 +203,7 @@ fn boolean_empty_string_is_coerced_to_false() {
203
203
 
204
204
  let validator = ParameterValidator::new(schema).expect("validator");
205
205
  let mut raw_query = HashMap::new();
206
- raw_query.insert("flag".to_string(), vec!["".to_string()]);
206
+ raw_query.insert("flag".to_string(), vec![String::new()]);
207
207
 
208
208
  let extracted = validator
209
209
  .validate_and_extract(
@@ -32,10 +32,10 @@ fn validator_preprocesses_binary_file_objects_recursively() {
32
32
  });
33
33
 
34
34
  let data = json!({
35
- "file": file_object.clone(),
36
- "files": [file_object.clone()],
35
+ "file": &file_object,
36
+ "files": [&file_object],
37
37
  "nested": {
38
- "inner": file_object,
38
+ "inner": &file_object,
39
39
  "other": 1
40
40
  }
41
41
  });
@@ -242,7 +242,7 @@ fn error_mapper_uses_schema_constraints_when_present() {
242
242
 
243
243
  let (ty, _msg, ctx) = ErrorMapper::map_error(&ErrorCondition::EmailFormat, &schema, "/properties/value", "generic");
244
244
  assert_eq!(ty, "string_pattern_mismatch");
245
- assert!(ctx.as_ref().unwrap()["pattern"].as_str().unwrap().contains("@"));
245
+ assert!(ctx.as_ref().unwrap()["pattern"].as_str().unwrap().contains('@'));
246
246
 
247
247
  let (ty, _msg, ctx) = ErrorMapper::map_error(&ErrorCondition::UuidFormat, &schema, "/properties/value", "generic");
248
248
  assert_eq!(ty, "uuid_parsing");
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-http"
3
- version = "0.8.3"
3
+ version = "0.10.2"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -16,6 +16,7 @@ readme = "README.md"
16
16
  axum = { version = "0.8", features = ["multipart", "ws"] }
17
17
  tokio = { version = "1", features = ["full"] }
18
18
  tokio-util = "0.7"
19
+ tokio-stream = "0.1"
19
20
  tower = "0.5"
20
21
  tower-http = { version = "0.6.8", features = ["fs", "trace", "compression-gzip", "compression-br", "compression-deflate", "cors", "request-id", "limit", "timeout", "set-header", "sensitive-headers"] }
21
22
  tower_governor = "0.8"
@@ -40,7 +41,8 @@ urlencoding = "2.1"
40
41
  url = "2.5"
41
42
  mime = "0.3"
42
43
  jiff = "0.2"
43
- uuid = "1.19"
44
+ uuid = "1.20"
45
+ ahash = "0.8"
44
46
  bytes = "1.11"
45
47
  http-body-util = "0.1"
46
48
  http-body = "1.0"
@@ -56,15 +56,16 @@ fn is_method_allowed(method: &str, allowed_methods: &[String]) -> bool {
56
56
  ///
57
57
  /// # Returns
58
58
  /// `true` if all requested headers are allowed, `false` if any header is not allowed
59
- fn are_headers_allowed(requested: &[&str], allowed: &[String]) -> bool {
59
+ fn are_headers_allowed(requested: impl IntoIterator<Item = impl AsRef<str>>, allowed: &[String]) -> bool {
60
60
  if allowed.iter().any(|h| h == "*") {
61
61
  return true;
62
62
  }
63
63
 
64
- requested.iter().all(|req_header| {
64
+ requested.into_iter().all(|req_header| {
65
+ let req_str = req_header.as_ref();
65
66
  allowed
66
67
  .iter()
67
- .any(|allowed_header| allowed_header.eq_ignore_ascii_case(req_header))
68
+ .any(|allowed_header| allowed_header.eq_ignore_ascii_case(req_str))
68
69
  })
69
70
  }
70
71
 
@@ -133,9 +134,8 @@ pub fn handle_preflight(headers: &HeaderMap, cors_config: &CorsConfig) -> Result
133
134
  .and_then(|v| v.to_str().ok());
134
135
 
135
136
  if let Some(req_headers) = requested_headers_str {
136
- let requested_headers: Vec<&str> = req_headers.split(',').map(|h| h.trim()).collect();
137
-
138
- if !are_headers_allowed(&requested_headers, &cors_config.allowed_headers) {
137
+ // Pass iterator directly without collecting into Vec
138
+ if !are_headers_allowed(req_headers.split(',').map(|h| h.trim()), &cors_config.allowed_headers) {
139
139
  return Err(Box::new((StatusCode::FORBIDDEN).into_response()));
140
140
  }
141
141
  }
@@ -162,16 +162,16 @@ pub fn handle_preflight(headers: &HeaderMap, cors_config: &CorsConfig) -> Result
162
162
  HeaderValue::from_str(origin).unwrap_or_else(|_| HeaderValue::from_static("*")),
163
163
  );
164
164
 
165
- let methods = cors_config.allowed_methods.join(", ");
165
+ let methods = cors_config.allowed_methods_joined();
166
166
  headers_mut.insert(
167
167
  "access-control-allow-methods",
168
- HeaderValue::from_str(&methods).unwrap_or_else(|_| HeaderValue::from_static("*")),
168
+ HeaderValue::from_str(methods).unwrap_or_else(|_| HeaderValue::from_static("*")),
169
169
  );
170
170
 
171
- let allowed_headers = cors_config.allowed_headers.join(", ");
171
+ let allowed_headers = cors_config.allowed_headers_joined();
172
172
  headers_mut.insert(
173
173
  "access-control-allow-headers",
174
- HeaderValue::from_str(&allowed_headers).unwrap_or_else(|_| HeaderValue::from_static("*")),
174
+ HeaderValue::from_str(allowed_headers).unwrap_or_else(|_| HeaderValue::from_static("*")),
175
175
  );
176
176
 
177
177
  if let Some(max_age) = cors_config.max_age
@@ -291,6 +291,7 @@ mod tests {
291
291
  expose_headers: Some(vec!["x-custom-header".to_string()]),
292
292
  max_age: Some(3600),
293
293
  allow_credentials: Some(true),
294
+ ..Default::default()
294
295
  }
295
296
  }
296
297
 
@@ -500,7 +501,8 @@ mod tests {
500
501
  allowed_headers: vec![],
501
502
  expose_headers: None,
502
503
  max_age: None,
503
- allow_credentials: Some(true), // SECURITY BUG: This should not be allowed with wildcard
504
+ allow_credentials: Some(true), // SECURITY BUG: This should not be allowed with wildcard,
505
+ ..Default::default()
504
506
  };
505
507
 
506
508
  let mut headers = HeaderMap::new();
@@ -538,6 +540,7 @@ mod tests {
538
540
  expose_headers: None,
539
541
  max_age: None,
540
542
  allow_credentials: None,
543
+ ..Default::default()
541
544
  };
542
545
 
543
546
  assert!(!is_origin_allowed("https://api.example.com", &config.allowed_origins));
@@ -561,6 +564,7 @@ mod tests {
561
564
  expose_headers: None,
562
565
  max_age: None,
563
566
  allow_credentials: None,
567
+ ..Default::default()
564
568
  };
565
569
 
566
570
  assert!(!is_origin_allowed("http://localhost:3001", &config.allowed_origins));
@@ -581,6 +585,7 @@ mod tests {
581
585
  expose_headers: None,
582
586
  max_age: None,
583
587
  allow_credentials: None,
588
+ ..Default::default()
584
589
  };
585
590
 
586
591
  assert!(!is_origin_allowed("http://example.com", &config.allowed_origins));
@@ -601,6 +606,7 @@ mod tests {
601
606
  expose_headers: None,
602
607
  max_age: None,
603
608
  allow_credentials: None,
609
+ ..Default::default()
604
610
  };
605
611
 
606
612
  assert!(!is_origin_allowed("https://example.com", &config.allowed_origins));
@@ -620,6 +626,7 @@ mod tests {
620
626
  expose_headers: None,
621
627
  max_age: None,
622
628
  allow_credentials: None,
629
+ ..Default::default()
623
630
  };
624
631
 
625
632
  assert!(!is_origin_allowed("https://example.com/", &config.allowed_origins));
@@ -640,6 +647,7 @@ mod tests {
640
647
  expose_headers: None,
641
648
  max_age: None,
642
649
  allow_credentials: None,
650
+ ..Default::default()
643
651
  };
644
652
 
645
653
  // SECURITY NOTE: "null" origin is allowed by wildcard in current implementation
@@ -652,6 +660,7 @@ mod tests {
652
660
  expose_headers: None,
653
661
  max_age: None,
654
662
  allow_credentials: None,
663
+ ..Default::default()
655
664
  };
656
665
  assert!(is_origin_allowed("null", &with_explicit_null.allowed_origins));
657
666
  }
@@ -666,6 +675,7 @@ mod tests {
666
675
  expose_headers: None,
667
676
  max_age: None,
668
677
  allow_credentials: None,
678
+ ..Default::default()
669
679
  };
670
680
  assert!(!is_origin_allowed("", &config_with_wildcard.allowed_origins));
671
681
 
@@ -676,6 +686,7 @@ mod tests {
676
686
  expose_headers: None,
677
687
  max_age: None,
678
688
  allow_credentials: None,
689
+ ..Default::default()
679
690
  };
680
691
  assert!(!is_origin_allowed("", &config_with_explicit.allowed_origins));
681
692
  }
@@ -705,6 +716,7 @@ mod tests {
705
716
  expose_headers: None,
706
717
  max_age: None,
707
718
  allow_credentials: None,
719
+ ..Default::default()
708
720
  };
709
721
 
710
722
  assert!(is_origin_allowed("https://trusted1.com", &config.allowed_origins));
@@ -728,6 +740,7 @@ mod tests {
728
740
  expose_headers: None,
729
741
  max_age: None,
730
742
  allow_credentials: None,
743
+ ..Default::default()
731
744
  };
732
745
 
733
746
  assert!(is_origin_allowed("https://example.com", &config.allowed_origins));
@@ -747,6 +760,7 @@ mod tests {
747
760
  expose_headers: None,
748
761
  max_age: Some(3600),
749
762
  allow_credentials: Some(false),
763
+ ..Default::default()
750
764
  };
751
765
 
752
766
  let mut headers = HeaderMap::new();
@@ -798,6 +812,7 @@ mod tests {
798
812
  expose_headers: None,
799
813
  max_age: None,
800
814
  allow_credentials: None,
815
+ ..Default::default()
801
816
  };
802
817
 
803
818
  let test_cases = vec![
@@ -836,6 +851,7 @@ mod tests {
836
851
  expose_headers: None,
837
852
  max_age: None,
838
853
  allow_credentials: None,
854
+ ..Default::default()
839
855
  };
840
856
 
841
857
  let test_cases = vec![
@@ -883,6 +899,7 @@ mod tests {
883
899
  expose_headers: Some(vec!["x-custom".to_string()]),
884
900
  max_age: None,
885
901
  allow_credentials: Some(true),
902
+ ..Default::default()
886
903
  };
887
904
 
888
905
  let mut response = Response::new(Body::empty());
@@ -908,6 +925,7 @@ mod tests {
908
925
  expose_headers: None,
909
926
  max_age: None,
910
927
  allow_credentials: None,
928
+ ..Default::default()
911
929
  };
912
930
 
913
931
  let mut headers = HeaderMap::new();
@@ -943,6 +961,7 @@ mod tests {
943
961
  expose_headers: None,
944
962
  max_age: None,
945
963
  allow_credentials: None,
964
+ ..Default::default()
946
965
  };
947
966
 
948
967
  let test_cases = vec!["GET", "get", "Get", "POST", "post"];
@@ -971,6 +990,7 @@ mod tests {
971
990
  expose_headers: None,
972
991
  max_age: Some(7200),
973
992
  allow_credentials: None,
993
+ ..Default::default()
974
994
  };
975
995
 
976
996
  let mut headers = HeaderMap::new();
@@ -995,6 +1015,7 @@ mod tests {
995
1015
  expose_headers: None,
996
1016
  max_age: None,
997
1017
  allow_credentials: None,
1018
+ ..Default::default()
998
1019
  };
999
1020
 
1000
1021
  assert!(!is_origin_allowed("https://api.example.com", &config.allowed_origins));
@@ -153,10 +153,14 @@ impl Handler for DependencyInjectingHandler {
153
153
 
154
154
  let core_request_data = spikard_core::RequestData {
155
155
  path_params: Arc::clone(&request_data.path_params),
156
- query_params: request_data.query_params.clone(),
157
- validated_params: request_data.validated_params.clone(),
156
+ query_params: Arc::try_unwrap(Arc::clone(&request_data.query_params))
157
+ .unwrap_or_else(|arc| (*arc).clone()),
158
+ validated_params: request_data
159
+ .validated_params
160
+ .as_ref()
161
+ .map(|arc| Arc::try_unwrap(Arc::clone(arc)).unwrap_or_else(|a| (*a).clone())),
158
162
  raw_query_params: Arc::clone(&request_data.raw_query_params),
159
- body: request_data.body.clone(),
163
+ body: Arc::try_unwrap(Arc::clone(&request_data.body)).unwrap_or_else(|arc| (*arc).clone()),
160
164
  raw_body: request_data.raw_body.clone(),
161
165
  headers: Arc::clone(&request_data.headers),
162
166
  cookies: Arc::clone(&request_data.cookies),
@@ -346,10 +350,10 @@ mod tests {
346
350
  fn create_request_data() -> RequestData {
347
351
  RequestData {
348
352
  path_params: Arc::new(HashMap::new()),
349
- query_params: serde_json::Value::Null,
353
+ query_params: Arc::new(serde_json::Value::Null),
350
354
  validated_params: None,
351
355
  raw_query_params: Arc::new(HashMap::new()),
352
- body: serde_json::Value::Null,
356
+ body: Arc::new(serde_json::Value::Null),
353
357
  raw_body: None,
354
358
  headers: Arc::new(HashMap::new()),
355
359
  cookies: Arc::new(HashMap::new()),
@@ -1163,7 +1167,7 @@ mod tests {
1163
1167
  .unwrap();
1164
1168
  let mut request_data = create_request_data();
1165
1169
  request_data.method = "POST".to_string();
1166
- request_data.body = serde_json::json!({"key": "value"});
1170
+ request_data.body = Arc::new(serde_json::json!({"key": "value"}));
1167
1171
 
1168
1172
  let result = di_handler.call(request, request_data).await;
1169
1173
 
@@ -1602,10 +1606,10 @@ mod tests {
1602
1606
 
1603
1607
  let request_data = RequestData {
1604
1608
  path_params: Arc::new(path_params.clone()),
1605
- query_params: serde_json::json!({"filter": "active", "sort": "name"}),
1609
+ query_params: Arc::new(serde_json::json!({"filter": "active", "sort": "name"})),
1606
1610
  validated_params: None,
1607
1611
  raw_query_params: Arc::new(raw_query_params.clone()),
1608
- body: serde_json::json!({"name": "John", "email": "john@example.com"}),
1612
+ body: Arc::new(serde_json::json!({"name": "John", "email": "john@example.com"})),
1609
1613
  raw_body: Some(bytes::Bytes::from(r#"{"name":"John","email":"john@example.com"}"#)),
1610
1614
  headers: Arc::new(headers.clone()),
1611
1615
  cookies: Arc::new(cookies.clone()),