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
@@ -206,7 +206,7 @@ mod tests {
206
206
  let metadata_204 = TestResponseMetadata::new(204, headers.clone(), 0, 50);
207
207
  let metadata_299 = TestResponseMetadata::new(299, headers.clone(), 100, 50);
208
208
  let metadata_300 = TestResponseMetadata::new(300, headers.clone(), 100, 50);
209
- let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
209
+ let metadata_400 = TestResponseMetadata::new(400, headers, 100, 50);
210
210
 
211
211
  assert!(metadata_200.is_success());
212
212
  assert!(metadata_201.is_success());
@@ -223,7 +223,7 @@ mod tests {
223
223
  let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
224
224
  let metadata_404 = TestResponseMetadata::new(404, headers.clone(), 100, 50);
225
225
  let metadata_499 = TestResponseMetadata::new(499, headers.clone(), 100, 50);
226
- let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
226
+ let metadata_500 = TestResponseMetadata::new(500, headers, 100, 50);
227
227
 
228
228
  assert!(!metadata_399.is_client_error());
229
229
  assert!(metadata_400.is_client_error());
@@ -239,7 +239,7 @@ mod tests {
239
239
  let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
240
240
  let metadata_502 = TestResponseMetadata::new(502, headers.clone(), 100, 50);
241
241
  let metadata_599 = TestResponseMetadata::new(599, headers.clone(), 100, 50);
242
- let metadata_600 = TestResponseMetadata::new(600, headers.clone(), 100, 50);
242
+ let metadata_600 = TestResponseMetadata::new(600, headers, 100, 50);
243
243
 
244
244
  assert!(!metadata_499.is_server_error());
245
245
  assert!(metadata_500.is_server_error());
@@ -252,7 +252,7 @@ mod tests {
252
252
  fn test_response_metadata_debug() {
253
253
  let headers = HashMap::new();
254
254
  let metadata = TestResponseMetadata::new(200, headers, 100, 50);
255
- let debug_str = format!("{:?}", metadata);
255
+ let debug_str = format!("{metadata:?}");
256
256
  assert!(debug_str.contains("200"));
257
257
  assert!(debug_str.contains("100"));
258
258
  assert!(debug_str.contains("50"));
@@ -7,6 +7,7 @@ struct JsonSource {
7
7
  }
8
8
 
9
9
  impl JsonSource {
10
+ #[allow(clippy::missing_const_for_fn)]
10
11
  fn new(value: serde_json::Value) -> Self {
11
12
  Self { value }
12
13
  }
@@ -44,7 +45,7 @@ impl ConfigSource for JsonSource {
44
45
 
45
46
  fn get_nested(&self, key: &str) -> Option<Box<dyn ConfigSource + '_>> {
46
47
  let nested = self.get(key)?.as_object()?;
47
- Some(Box::new(JsonSource {
48
+ Some(Box::new(Self {
48
49
  value: serde_json::Value::Object(nested.clone()),
49
50
  }))
50
51
  }
@@ -60,7 +61,7 @@ impl ConfigSource for JsonSource {
60
61
  fn get_array_element(&self, key: &str, index: usize) -> Option<Box<dyn ConfigSource + '_>> {
61
62
  let array = self.get(key)?.as_array()?;
62
63
  let element = array.get(index)?.as_object()?;
63
- Some(Box::new(JsonSource {
64
+ Some(Box::new(Self {
64
65
  value: serde_json::Value::Object(element.clone()),
65
66
  }))
66
67
  }
@@ -2,6 +2,12 @@
2
2
  //!
3
3
  //! These tests cover serialization edge cases and ensure fallback
4
4
  //! behavior works correctly when serialization fails.
5
+ #![allow(
6
+ clippy::redundant_clone,
7
+ clippy::uninlined_format_args,
8
+ clippy::doc_markdown,
9
+ reason = "Edge case tests for error handling"
10
+ )]
5
11
 
6
12
  use axum::http::StatusCode;
7
13
  use pretty_assertions::assert_eq;
@@ -99,7 +105,7 @@ fn test_from_structured_error_with_details() {
99
105
  json!({"field": "username", "reason": "already_exists"}),
100
106
  );
101
107
 
102
- let (status, body) = ErrorResponseBuilder::from_structured_error(error);
108
+ let (status, body) = ErrorResponseBuilder::from_structured_error(&error);
103
109
  assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
104
110
  let parsed: Value = serde_json::from_str(&body).unwrap();
105
111
  assert_eq!(parsed["code"], "custom_error");
@@ -201,23 +207,17 @@ fn test_all_convenience_methods_return_valid_json() {
201
207
  ];
202
208
 
203
209
  for (status, body) in test_cases {
204
- let parsed: Value = serde_json::from_str(&body)
205
- .unwrap_or_else(|_| panic!("Failed to parse JSON for status {}: {}", status, body));
210
+ let parsed: Value =
211
+ serde_json::from_str(&body).unwrap_or_else(|_| panic!("Failed to parse JSON for status {status}: {body}"));
206
212
 
207
213
  assert!(
208
214
  parsed.get("error").is_some(),
209
- "Missing 'error' field for status {}",
210
- status
211
- );
212
- assert!(
213
- parsed.get("code").is_some(),
214
- "Missing 'code' field for status {}",
215
- status
215
+ "Missing 'error' field for status {status}"
216
216
  );
217
+ assert!(parsed.get("code").is_some(), "Missing 'code' field for status {status}");
217
218
  assert!(
218
219
  parsed.get("details").is_some(),
219
- "Missing 'details' field for status {}",
220
- status
220
+ "Missing 'details' field for status {status}"
221
221
  );
222
222
  }
223
223
  }
@@ -336,7 +336,7 @@ fn test_error_message_types() {
336
336
  let (_, body) = ErrorResponseBuilder::bad_request("test");
337
337
  assert!(body.contains("test"));
338
338
 
339
- let (_, body) = ErrorResponseBuilder::bad_request(format!("Error: {}", 123));
339
+ let (_, body) = ErrorResponseBuilder::bad_request(format!("Error: {0}", 123));
340
340
  assert!(body.contains("Error: 123"));
341
341
  }
342
342
 
@@ -2,6 +2,13 @@
2
2
  //!
3
3
  //! This test file ensures full code coverage across all modules in the crate,
4
4
  //! testing edge cases, error paths, and integration scenarios.
5
+ #![allow(
6
+ clippy::doc_markdown,
7
+ clippy::items_after_statements,
8
+ clippy::uninlined_format_args,
9
+ clippy::redundant_clone,
10
+ reason = "Integration test with many coverage scenarios"
11
+ )]
5
12
 
6
13
  use axum::http::{Request, StatusCode};
7
14
  use pretty_assertions::assert_eq;
@@ -201,12 +208,10 @@ fn test_lifecycle_hook_types() {
201
208
  }
202
209
 
203
210
  let continue_result = HookResult::Continue;
204
- let cloned_continue = continue_result.clone();
205
- assert!(matches!(cloned_continue, HookResult::Continue));
211
+ assert!(matches!(continue_result.clone(), HookResult::Continue));
206
212
 
207
213
  let short_circuit = HookResult::ShortCircuit(json!({"status": "error"}));
208
- let cloned_short = short_circuit.clone();
209
- assert!(matches!(cloned_short, HookResult::ShortCircuit(_)));
214
+ assert!(matches!(short_circuit.clone(), HookResult::ShortCircuit(_)));
210
215
  }
211
216
 
212
217
  #[test]
@@ -413,7 +418,7 @@ fn test_conversion_traits_comprehensive() {
413
418
  fn from_any(value: &(dyn std::any::Any + Send + Sync)) -> Result<Self, Self::Error> {
414
419
  value
415
420
  .downcast_ref::<i32>()
416
- .map(|&v| CustomType { value: v })
421
+ .map(|&v| Self { value: v })
417
422
  .ok_or_else(|| "Type mismatch".to_string())
418
423
  }
419
424
  }
@@ -1,4 +1,4 @@
1
- //! Integration tests for handler_base module
1
+ //! Integration tests for `handler_base` module
2
2
  //!
3
3
  //! These tests cover the validation paths and error handling that aren't
4
4
  //! covered by unit tests in the module itself.
@@ -41,7 +41,7 @@ impl LanguageHandler for MockHandler {
41
41
  if should_fail {
42
42
  Err(HandlerError::Execution("Handler failed".to_string()))
43
43
  } else {
44
- Ok(format!("output:{}", input))
44
+ Ok(format!("output:{input}"))
45
45
  }
46
46
  })
47
47
  }
@@ -81,10 +81,10 @@ async fn test_handler_executor_with_validation_error() {
81
81
  let request = Request::builder().body(Body::empty()).unwrap();
82
82
  let request_data = RequestData {
83
83
  path_params: Arc::new(HashMap::new()),
84
- query_params: json!({}),
84
+ query_params: Arc::new(json!({})),
85
85
  validated_params: None,
86
86
  raw_query_params: Arc::new(HashMap::new()),
87
- body: json!({"username": "john"}),
87
+ body: Arc::new(json!({"username": "john"})),
88
88
  raw_body: None,
89
89
  headers: Arc::new(HashMap::new()),
90
90
  cookies: Arc::new(HashMap::new()),
@@ -110,10 +110,10 @@ async fn test_handler_executor_prepare_failure() {
110
110
  let request = Request::builder().body(Body::empty()).unwrap();
111
111
  let request_data = RequestData {
112
112
  path_params: Arc::new(HashMap::new()),
113
- query_params: json!({}),
113
+ query_params: Arc::new(json!({})),
114
114
  validated_params: None,
115
115
  raw_query_params: Arc::new(HashMap::new()),
116
- body: json!({}),
116
+ body: Arc::new(json!({})),
117
117
  raw_body: None,
118
118
  headers: Arc::new(HashMap::new()),
119
119
  cookies: Arc::new(HashMap::new()),
@@ -139,10 +139,10 @@ async fn test_handler_executor_invoke_failure() {
139
139
  let request = Request::builder().body(Body::empty()).unwrap();
140
140
  let request_data = RequestData {
141
141
  path_params: Arc::new(HashMap::new()),
142
- query_params: json!({}),
142
+ query_params: Arc::new(json!({})),
143
143
  validated_params: None,
144
144
  raw_query_params: Arc::new(HashMap::new()),
145
- body: json!({}),
145
+ body: Arc::new(json!({})),
146
146
  raw_body: None,
147
147
  headers: Arc::new(HashMap::new()),
148
148
  cookies: Arc::new(HashMap::new()),
@@ -168,10 +168,10 @@ async fn test_handler_executor_interpret_failure() {
168
168
  let request = Request::builder().body(Body::empty()).unwrap();
169
169
  let request_data = RequestData {
170
170
  path_params: Arc::new(HashMap::new()),
171
- query_params: json!({}),
171
+ query_params: Arc::new(json!({})),
172
172
  validated_params: None,
173
173
  raw_query_params: Arc::new(HashMap::new()),
174
- body: json!({}),
174
+ body: Arc::new(json!({})),
175
175
  raw_body: None,
176
176
  headers: Arc::new(HashMap::new()),
177
177
  cookies: Arc::new(HashMap::new()),
@@ -210,10 +210,10 @@ async fn test_handler_executor_with_request_validator() {
210
210
 
211
211
  let request_data = RequestData {
212
212
  path_params: Arc::new(HashMap::new()),
213
- query_params: json!({}),
213
+ query_params: Arc::new(json!({})),
214
214
  validated_params: None,
215
215
  raw_query_params: Arc::new(HashMap::new()),
216
- body: json!({"name": "test"}),
216
+ body: Arc::new(json!({"name": "test"})),
217
217
  raw_body: None,
218
218
  headers: Arc::new(headers),
219
219
  cookies: Arc::new(HashMap::new()),
@@ -263,10 +263,10 @@ async fn test_handler_executor_builder_pattern() {
263
263
  let request = Request::builder().body(Body::empty()).unwrap();
264
264
  let request_data = RequestData {
265
265
  path_params: Arc::new(HashMap::new()),
266
- query_params: json!({}),
266
+ query_params: Arc::new(json!({})),
267
267
  validated_params: None,
268
268
  raw_query_params: Arc::new(HashMap::new()),
269
- body: json!({"email": "test@example.com"}),
269
+ body: Arc::new(json!({"email": "test@example.com"})),
270
270
  raw_body: None,
271
271
  headers: Arc::new(HashMap::new()),
272
272
  cookies: Arc::new(HashMap::new()),