spikard 0.8.1 → 0.8.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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.toml +1 -1
  3. data/lib/spikard/grpc.rb +5 -5
  4. data/lib/spikard/version.rb +1 -1
  5. data/vendor/crates/spikard-bindings-shared/Cargo.toml +1 -1
  6. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +3 -3
  7. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  8. data/vendor/crates/spikard-core/src/metadata.rs +3 -14
  9. data/vendor/crates/spikard-http/Cargo.toml +1 -1
  10. data/vendor/crates/spikard-http/src/grpc/mod.rs +1 -1
  11. data/vendor/crates/spikard-http/src/grpc/service.rs +11 -11
  12. data/vendor/crates/spikard-http/src/grpc/streaming.rs +5 -1
  13. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +59 -20
  14. data/vendor/crates/spikard-http/src/server/routing_factory.rs +179 -201
  15. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +49 -60
  16. data/vendor/crates/spikard-http/tests/common/handlers.rs +5 -5
  17. data/vendor/crates/spikard-http/tests/common/mod.rs +7 -8
  18. data/vendor/crates/spikard-http/tests/common/test_builders.rs +14 -19
  19. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +68 -69
  20. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +1 -3
  21. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +98 -84
  22. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +76 -57
  23. data/vendor/crates/spikard-rb/Cargo.toml +1 -1
  24. data/vendor/crates/spikard-rb/src/grpc/handler.rs +30 -25
  25. data/vendor/crates/spikard-rb/src/lib.rs +1 -2
  26. data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
  27. metadata +1 -1
@@ -40,13 +40,13 @@
40
40
  //! ```
41
41
 
42
42
  use bytes::Bytes;
43
- use serde_json::{json, Value};
43
+ use serde_json::{Value, json};
44
44
  use spikard_http::grpc::{GrpcHandler, GrpcHandlerResult, GrpcRequestData, GrpcResponseData};
45
45
  use std::future::Future;
46
46
  use std::pin::Pin;
47
47
  use std::sync::Arc;
48
- use tonic::metadata::MetadataMap;
49
48
  use tonic::Code;
49
+ use tonic::metadata::MetadataMap;
50
50
 
51
51
  /// Test server for gRPC integration testing
52
52
  ///
@@ -82,7 +82,7 @@ impl GrpcTestServer {
82
82
  ///
83
83
  /// # Arguments
84
84
  ///
85
- /// * `url` - The base URL for the server (e.g., "http://localhost:8080")
85
+ /// * `url` - The base URL for the server (e.g., <http://localhost:8080>)
86
86
  pub fn with_url(url: impl Into<String>) -> Self {
87
87
  Self {
88
88
  handlers: Arc::new(std::sync::Mutex::new(Vec::new())),
@@ -102,7 +102,7 @@ impl GrpcTestServer {
102
102
  /// let mut server = GrpcTestServer::new();
103
103
  /// server.register_service(Arc::new(MyHandler));
104
104
  /// ```
105
- pub fn register_service(&mut self, handler: Arc<dyn GrpcHandler>) {
105
+ pub fn register_service(&self, handler: Arc<dyn GrpcHandler>) {
106
106
  let mut handlers = self.handlers.lock().unwrap();
107
107
  handlers.push(handler);
108
108
  }
@@ -127,10 +127,7 @@ impl GrpcTestServer {
127
127
  /// * `service_name` - The fully qualified service name to look up
128
128
  pub fn get_handler(&self, service_name: &str) -> Option<Arc<dyn GrpcHandler>> {
129
129
  let handlers = self.handlers.lock().unwrap();
130
- handlers
131
- .iter()
132
- .find(|h| h.service_name() == service_name)
133
- .cloned()
130
+ handlers.iter().find(|h| h.service_name() == service_name).cloned()
134
131
  }
135
132
 
136
133
  /// Check if a service is registered
@@ -170,7 +167,7 @@ pub struct GrpcTestClient;
170
167
 
171
168
  impl GrpcTestClient {
172
169
  /// Create a new test client
173
- pub fn new() -> Self {
170
+ pub const fn new() -> Self {
174
171
  Self
175
172
  }
176
173
  }
@@ -182,7 +179,7 @@ impl Default for GrpcTestClient {
182
179
  }
183
180
 
184
181
  /// Factory function to create a gRPC test client
185
- pub fn create_grpc_test_client() -> GrpcTestClient {
182
+ pub const fn create_grpc_test_client() -> GrpcTestClient {
186
183
  GrpcTestClient::new()
187
184
  }
188
185
 
@@ -196,7 +193,7 @@ pub fn create_grpc_test_client() -> GrpcTestClient {
196
193
  ///
197
194
  /// * `server` - The test server instance
198
195
  /// * `service` - Fully qualified service name (e.g., "mypackage.UserService")
199
- /// * `method` - Method name (e.g., "GetUser")
196
+ /// * `method` - Method name (e.g., `GetUser`)
200
197
  /// * `payload` - Serialized protobuf message bytes
201
198
  /// * `metadata` - gRPC metadata (headers) to include in the request
202
199
  ///
@@ -227,12 +224,12 @@ pub async fn send_unary_request(
227
224
  payload: Bytes,
228
225
  metadata: MetadataMap,
229
226
  ) -> Result<GrpcResponseData, Box<dyn std::error::Error>> {
230
- let handler = server
231
- .get_handler(service)
232
- .ok_or_else(|| Box::new(std::io::Error::new(
227
+ let handler = server.get_handler(service).ok_or_else(|| {
228
+ Box::new(std::io::Error::new(
233
229
  std::io::ErrorKind::NotFound,
234
- format!("Service not found: {}", service),
235
- )) as Box<dyn std::error::Error>)?;
230
+ format!("Service not found: {service}"),
231
+ )) as Box<dyn std::error::Error>
232
+ })?;
236
233
 
237
234
  let request = GrpcRequestData {
238
235
  service_name: service.to_string(),
@@ -264,14 +261,12 @@ pub async fn send_unary_request(
264
261
  /// let response = send_unary_request(...).await?;
265
262
  /// assert_grpc_response(response, json!({"id": 1, "name": "Alice"}));
266
263
  /// ```
267
- pub fn assert_grpc_response(response: GrpcResponseData, expected: Value) {
268
- let actual = serde_json::from_slice::<Value>(&response.payload)
269
- .expect("Failed to parse response payload as JSON");
264
+ pub fn assert_grpc_response(response: &GrpcResponseData, expected: &Value) {
265
+ let actual = serde_json::from_slice::<Value>(&response.payload).expect("Failed to parse response payload as JSON");
270
266
 
271
267
  assert_eq!(
272
- actual, expected,
273
- "Response payload mismatch.\nExpected: {}\nActual: {}",
274
- expected, actual
268
+ actual, *expected,
269
+ "Response payload mismatch.\nExpected: {expected}\nActual: {actual}",
275
270
  );
276
271
  }
277
272
 
@@ -286,7 +281,7 @@ pub fn assert_grpc_response(response: GrpcResponseData, expected: Value) {
286
281
  /// # Arguments
287
282
  ///
288
283
  /// * `result` - The gRPC handler result
289
- /// * `expected_status` - The expected tonic::Code
284
+ /// * `expected_status` - The expected `tonic::Code`
290
285
  ///
291
286
  /// # Example
292
287
  ///
@@ -307,10 +302,7 @@ pub fn assert_grpc_status(result: &GrpcHandlerResult, expected_status: Code) {
307
302
  );
308
303
  }
309
304
  Ok(_) => {
310
- panic!(
311
- "Expected error status {:?} but got success response",
312
- expected_status
313
- );
305
+ panic!("Expected error status {expected_status:?} but got success response");
314
306
  }
315
307
  }
316
308
  }
@@ -363,15 +355,13 @@ impl ProtobufMessageBuilder {
363
355
 
364
356
  /// Add a string field to the message
365
357
  pub fn add_string_field(&mut self, name: &str, value: &str) -> &mut Self {
366
- self.fields
367
- .insert(name.to_string(), Value::String(value.to_string()));
358
+ self.fields.insert(name.to_string(), Value::String(value.to_string()));
368
359
  self
369
360
  }
370
361
 
371
362
  /// Add an integer field to the message
372
363
  pub fn add_int_field(&mut self, name: &str, value: i64) -> &mut Self {
373
- self.fields
374
- .insert(name.to_string(), json!(value));
364
+ self.fields.insert(name.to_string(), json!(value));
375
365
  self
376
366
  }
377
367
 
@@ -446,11 +436,11 @@ pub fn create_test_metadata() -> MetadataMap {
446
436
 
447
437
  /// Create test metadata with custom headers
448
438
  ///
449
- /// Allows building metadata from a HashMap of key-value pairs.
439
+ /// Allows building metadata from a `HashMap` of key-value pairs.
450
440
  ///
451
441
  /// # Arguments
452
442
  ///
453
- /// * `headers` - HashMap of header names to values (String-based)
443
+ /// * `headers` - `HashMap` of header names to values (String-based)
454
444
  ///
455
445
  /// # Example
456
446
  ///
@@ -475,7 +465,7 @@ pub fn create_test_metadata_with_headers(
475
465
  Ok(metadata)
476
466
  }
477
467
 
478
- /// Add authentication metadata to an existing MetadataMap
468
+ /// Add authentication metadata to an existing `MetadataMap`
479
469
  ///
480
470
  /// Adds a standard Bearer token authorization header.
481
471
  ///
@@ -491,12 +481,12 @@ pub fn create_test_metadata_with_headers(
491
481
  /// add_auth_metadata(&mut metadata, "secret_token_123");
492
482
  /// ```
493
483
  pub fn add_auth_metadata(metadata: &mut MetadataMap, token: &str) -> Result<(), Box<dyn std::error::Error>> {
494
- let auth_value = format!("Bearer {}", token);
484
+ let auth_value = format!("Bearer {token}");
495
485
  metadata.insert("authorization", auth_value.parse()?);
496
486
  Ok(())
497
487
  }
498
488
 
499
- /// Add custom metadata header to an existing MetadataMap
489
+ /// Add custom metadata header to an existing `MetadataMap`
500
490
  ///
501
491
  /// # Arguments
502
492
  ///
@@ -545,8 +535,8 @@ impl MockGrpcHandler {
545
535
  }
546
536
 
547
537
  /// Create a mock handler that returns JSON
548
- pub fn with_json(service_name: &'static str, json: Value) -> Self {
549
- let bytes = serde_json::to_vec(&json).unwrap_or_default();
538
+ pub fn with_json(service_name: &'static str, json: &Value) -> Self {
539
+ let bytes = serde_json::to_vec(json).unwrap_or_default();
550
540
  Self::new(service_name, Bytes::from(bytes))
551
541
  }
552
542
  }
@@ -643,6 +633,22 @@ impl GrpcHandler for EchoMockHandler {
643
633
  mod tests {
644
634
  use super::*;
645
635
 
636
+ // Mock handler for testing gRPC functionality
637
+ struct TestHandler;
638
+ impl GrpcHandler for TestHandler {
639
+ fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
640
+ Box::pin(async {
641
+ Ok(GrpcResponseData {
642
+ payload: serde_json::to_vec(&json!({"result": "success"})).unwrap().into(),
643
+ metadata: MetadataMap::new(),
644
+ })
645
+ })
646
+ }
647
+ fn service_name(&self) -> &'static str {
648
+ "test.TestService"
649
+ }
650
+ }
651
+
646
652
  #[test]
647
653
  fn test_grpc_test_server_new() {
648
654
  let server = GrpcTestServer::new();
@@ -659,7 +665,7 @@ mod tests {
659
665
 
660
666
  #[test]
661
667
  fn test_grpc_test_server_register_service() {
662
- let mut server = GrpcTestServer::new();
668
+ let server = GrpcTestServer::new();
663
669
  let handler = Arc::new(MockGrpcHandler::new("test.Service", "response"));
664
670
 
665
671
  server.register_service(handler);
@@ -670,7 +676,7 @@ mod tests {
670
676
 
671
677
  #[test]
672
678
  fn test_grpc_test_server_register_multiple_services() {
673
- let mut server = GrpcTestServer::new();
679
+ let server = GrpcTestServer::new();
674
680
  let handler1 = Arc::new(MockGrpcHandler::new("service1", "response1"));
675
681
  let handler2 = Arc::new(MockGrpcHandler::new("service2", "response2"));
676
682
 
@@ -706,7 +712,7 @@ mod tests {
706
712
  #[tokio::test]
707
713
  async fn test_mock_grpc_handler_with_json() {
708
714
  let json_response = json!({"id": 1, "name": "Alice"});
709
- let handler = MockGrpcHandler::with_json("test.UserService", json_response.clone());
715
+ let handler = MockGrpcHandler::with_json("test.UserService", &json_response);
710
716
 
711
717
  let request = GrpcRequestData {
712
718
  service_name: "test.UserService".to_string(),
@@ -908,7 +914,7 @@ mod tests {
908
914
  };
909
915
 
910
916
  let expected = json!({"id": 1, "name": "Alice"});
911
- assert_grpc_response(response, expected);
917
+ assert_grpc_response(&response, &expected);
912
918
  }
913
919
 
914
920
  #[test]
@@ -945,25 +951,9 @@ mod tests {
945
951
 
946
952
  #[tokio::test]
947
953
  async fn test_send_unary_request_with_mock_handler() {
948
- let mut server = GrpcTestServer::new();
954
+ let server = GrpcTestServer::new();
949
955
  let _response_payload = json!({"result": "success"});
950
956
 
951
- // Create a custom handler that reports the correct service name
952
- struct TestHandler;
953
- impl GrpcHandler for TestHandler {
954
- fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
955
- Box::pin(async {
956
- Ok(GrpcResponseData {
957
- payload: serde_json::to_vec(&json!({"result": "success"})).unwrap().into(),
958
- metadata: MetadataMap::new(),
959
- })
960
- })
961
- }
962
- fn service_name(&self) -> &'static str {
963
- "test.TestService"
964
- }
965
- }
966
-
967
957
  server.register_service(Arc::new(TestHandler));
968
958
 
969
959
  let message = ProtobufMessageBuilder::new()
@@ -1016,7 +1006,6 @@ mod tests {
1016
1006
  #[test]
1017
1007
  fn test_grpc_test_client_creation() {
1018
1008
  let _client = create_grpc_test_client();
1019
- let _default_client = GrpcTestClient::default();
1020
1009
  let _new_client = GrpcTestClient::new();
1021
1010
  // Just ensure it can be created without panicking
1022
1011
  }
@@ -141,12 +141,12 @@ pub struct JsonHandler {
141
141
 
142
142
  impl JsonHandler {
143
143
  /// Create a new JSON handler with given status code and body
144
- pub fn new(status_code: StatusCode, body: serde_json::Value) -> Self {
144
+ pub const fn new(status_code: StatusCode, body: serde_json::Value) -> Self {
145
145
  Self { status_code, body }
146
146
  }
147
147
 
148
148
  /// Create a JSON handler with 200 OK status
149
- pub fn ok(body: serde_json::Value) -> Self {
149
+ pub const fn ok(body: serde_json::Value) -> Self {
150
150
  Self {
151
151
  status_code: StatusCode::OK,
152
152
  body,
@@ -154,7 +154,7 @@ impl JsonHandler {
154
154
  }
155
155
 
156
156
  /// Create a JSON handler with 201 Created status
157
- pub fn created(body: serde_json::Value) -> Self {
157
+ pub const fn created(body: serde_json::Value) -> Self {
158
158
  Self {
159
159
  status_code: StatusCode::CREATED,
160
160
  body,
@@ -162,7 +162,7 @@ impl JsonHandler {
162
162
  }
163
163
 
164
164
  /// Create a JSON handler with 400 Bad Request status
165
- pub fn bad_request(body: serde_json::Value) -> Self {
165
+ pub const fn bad_request(body: serde_json::Value) -> Self {
166
166
  Self {
167
167
  status_code: StatusCode::BAD_REQUEST,
168
168
  body,
@@ -170,7 +170,7 @@ impl JsonHandler {
170
170
  }
171
171
 
172
172
  /// Create a JSON handler with 500 Internal Server Error status
173
- pub fn server_error(body: serde_json::Value) -> Self {
173
+ pub const fn server_error(body: serde_json::Value) -> Self {
174
174
  Self {
175
175
  status_code: StatusCode::INTERNAL_SERVER_ERROR,
176
176
  body,
@@ -17,18 +17,17 @@
17
17
 
18
18
  #![allow(dead_code)]
19
19
 
20
+ pub mod grpc_helpers;
20
21
  pub mod handlers;
21
22
  pub mod test_builders;
22
- pub mod grpc_helpers;
23
23
 
24
+ #[allow(unused_imports)]
25
+ pub use grpc_helpers::{
26
+ EchoMockHandler, ErrorMockHandler, GrpcTestClient, GrpcTestServer, MockGrpcHandler, ProtobufMessageBuilder,
27
+ add_auth_metadata, add_metadata_header, assert_grpc_response, assert_grpc_status, create_grpc_test_client,
28
+ create_test_metadata, create_test_metadata_with_headers, send_unary_request,
29
+ };
24
30
  #[allow(unused_imports)]
25
31
  pub use handlers::{EchoHandler, ErrorHandler, JsonHandler, PanicHandler, SuccessHandler};
26
32
  #[allow(unused_imports)]
27
33
  pub use test_builders::{HandlerBuilder, RequestBuilder, assert_status, load_fixture, parse_json_body};
28
- #[allow(unused_imports)]
29
- pub use grpc_helpers::{
30
- GrpcTestServer, GrpcTestClient, create_grpc_test_client, send_unary_request,
31
- assert_grpc_response, assert_grpc_status, ProtobufMessageBuilder, create_test_metadata,
32
- add_auth_metadata, add_metadata_header, create_test_metadata_with_headers,
33
- MockGrpcHandler, ErrorMockHandler, EchoMockHandler,
34
- };
@@ -87,7 +87,7 @@ impl HandlerBuilder {
87
87
  /// Add a delay to the handler response for testing timeouts
88
88
  ///
89
89
  /// Useful for simulating slow handlers and testing timeout middleware.
90
- pub fn delay(mut self, duration: Duration) -> Self {
90
+ pub const fn delay(mut self, duration: Duration) -> Self {
91
91
  self.delay = Some(duration);
92
92
  self
93
93
  }
@@ -95,7 +95,7 @@ impl HandlerBuilder {
95
95
  /// Configure the handler to panic when called
96
96
  ///
97
97
  /// Useful for testing panic recovery and error handling middleware.
98
- pub fn panics(mut self) -> Self {
98
+ pub const fn panics(mut self) -> Self {
99
99
  self.should_panic = true;
100
100
  self
101
101
  }
@@ -119,7 +119,7 @@ impl Default for HandlerBuilder {
119
119
  }
120
120
  }
121
121
 
122
- /// Internal handler implementation constructed by HandlerBuilder
122
+ /// Internal handler implementation constructed by `HandlerBuilder`
123
123
  struct ConfiguredHandler {
124
124
  status: StatusCode,
125
125
  body: Value,
@@ -139,9 +139,7 @@ impl Handler for ConfiguredHandler {
139
139
  let should_panic = self.should_panic;
140
140
 
141
141
  Box::pin(async move {
142
- if should_panic {
143
- panic!("Handler configured to panic");
144
- }
142
+ assert!(!should_panic, "Handler configured to panic");
145
143
 
146
144
  if let Some(duration) = delay {
147
145
  sleep(duration).await;
@@ -160,7 +158,7 @@ impl Handler for ConfiguredHandler {
160
158
 
161
159
  /// Fluent builder for constructing test HTTP requests
162
160
  ///
163
- /// Provides a fluent API for building both hyper Request objects and RequestData
161
+ /// Provides a fluent API for building both hyper `Request` objects and `RequestData`
164
162
  /// structures needed for handler testing. Handles typical test scenarios without
165
163
  /// requiring manual construction of all components.
166
164
  ///
@@ -215,7 +213,7 @@ impl RequestBuilder {
215
213
  self
216
214
  }
217
215
 
218
- /// Add or replace headers from a HashMap
216
+ /// Add or replace headers from a `HashMap`
219
217
  ///
220
218
  /// Values are stored as-is; no normalization is performed.
221
219
  pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
@@ -229,7 +227,7 @@ impl RequestBuilder {
229
227
  self
230
228
  }
231
229
 
232
- /// Add or replace cookies from a HashMap
230
+ /// Add or replace cookies from a `HashMap`
233
231
  pub fn cookies(mut self, cookies: HashMap<String, String>) -> Self {
234
232
  self.cookies = cookies;
235
233
  self
@@ -249,9 +247,9 @@ impl RequestBuilder {
249
247
  self
250
248
  }
251
249
 
252
- /// Set query parameters as a HashMap of name to values
250
+ /// Set query parameters as a `HashMap` of name to values
253
251
  ///
254
- /// Values are stored as Vec<String> to support multi-valued parameters.
252
+ /// Values are stored as `Vec<String>` to support multi-valued parameters.
255
253
  pub fn query_params(mut self, params: HashMap<String, Vec<String>>) -> Self {
256
254
  self.query_params = params;
257
255
  self
@@ -259,16 +257,13 @@ impl RequestBuilder {
259
257
 
260
258
  /// Add a single query parameter
261
259
  pub fn query_param(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
262
- self.query_params
263
- .entry(name.into())
264
- .or_insert_with(Vec::new)
265
- .push(value.into());
260
+ self.query_params.entry(name.into()).or_default().push(value.into());
266
261
  self
267
262
  }
268
263
 
269
- /// Build the request into (Request<Body>, RequestData) tuple
264
+ /// Build the request into `(Request<Body>, RequestData)` tuple
270
265
  ///
271
- /// The Request can be passed directly to handler.call(). RequestData contains
266
+ /// The `Request` can be passed directly to `handler.call()`. `RequestData` contains
272
267
  /// all extracted request information (params, body, headers, etc.).
273
268
  pub fn build(self) -> (Request<Body>, RequestData) {
274
269
  let body = if self.body.is_null() {
@@ -327,11 +322,11 @@ fn build_query_json(raw_params: &HashMap<String, Vec<String>>) -> Value {
327
322
  Value::Object(map)
328
323
  }
329
324
 
330
- /// Load a JSON fixture from the testing_data directory
325
+ /// Load a JSON fixture from the `testing_data` directory
331
326
  ///
332
327
  /// # Arguments
333
328
  ///
334
- /// * `relative_path` - Path relative to project root, e.g., "testing_data/headers/01_user_agent_default.json"
329
+ /// * `relative_path` - Path relative to project root, e.g., `"testing_data/headers/01_user_agent_default.json"`
335
330
  ///
336
331
  /// # Example
337
332
  ///