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
@@ -17,20 +17,20 @@ mod common;
17
17
  /// Test successful unary RPC with JSON payload
18
18
  #[tokio::test]
19
19
  async fn test_unary_rpc_success_with_json_payload() {
20
- // Arrange: Create test server and handler
21
- let mut server = GrpcTestServer::new();
22
-
23
20
  // Create a custom handler with proper service name
24
21
  struct UserServiceHandler;
25
22
  impl spikard_http::grpc::GrpcHandler for UserServiceHandler {
26
- fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
27
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
23
+ fn call(
24
+ &self,
25
+ _request: spikard_http::grpc::GrpcRequestData,
26
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
28
27
  {
29
28
  let payload = serde_json::to_vec(&json!({
30
29
  "id": 123,
31
30
  "name": "Alice Johnson",
32
31
  "email": "alice@example.com"
33
- })).unwrap();
32
+ }))
33
+ .unwrap();
34
34
  Box::pin(async {
35
35
  Ok(spikard_http::grpc::GrpcResponseData {
36
36
  payload: Bytes::from(payload),
@@ -43,14 +43,14 @@ async fn test_unary_rpc_success_with_json_payload() {
43
43
  }
44
44
  }
45
45
 
46
+ // Arrange: Create test server and handler
47
+ let server = GrpcTestServer::new();
48
+
46
49
  server.register_service(Arc::new(UserServiceHandler));
47
50
 
48
51
  // Act: Create and send request
49
52
  let mut request_builder = ProtobufMessageBuilder::new();
50
- let request_payload = request_builder
51
- .add_int_field("user_id", 123)
52
- .build()
53
- .unwrap();
53
+ let request_payload = request_builder.add_int_field("user_id", 123).build().unwrap();
54
54
 
55
55
  let response = send_unary_request(
56
56
  &server,
@@ -63,23 +63,25 @@ async fn test_unary_rpc_success_with_json_payload() {
63
63
  .expect("Failed to send unary request");
64
64
 
65
65
  // Assert
66
- assert_grpc_response(response, json!({
67
- "id": 123,
68
- "name": "Alice Johnson",
69
- "email": "alice@example.com"
70
- }));
66
+ assert_grpc_response(
67
+ &response,
68
+ &json!({
69
+ "id": 123,
70
+ "name": "Alice Johnson",
71
+ "email": "alice@example.com"
72
+ }),
73
+ );
71
74
  }
72
75
 
73
76
  /// Test server routing with multiple services
74
77
  #[tokio::test]
75
78
  async fn test_server_routes_to_correct_service() {
76
- // Arrange: Register multiple services
77
- let mut server = GrpcTestServer::new();
78
-
79
79
  struct UserServiceHandler;
80
80
  impl spikard_http::grpc::GrpcHandler for UserServiceHandler {
81
- fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
82
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
81
+ fn call(
82
+ &self,
83
+ _request: spikard_http::grpc::GrpcRequestData,
84
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
83
85
  {
84
86
  Box::pin(async {
85
87
  Ok(spikard_http::grpc::GrpcResponseData {
@@ -95,8 +97,10 @@ async fn test_server_routes_to_correct_service() {
95
97
 
96
98
  struct OrderServiceHandler;
97
99
  impl spikard_http::grpc::GrpcHandler for OrderServiceHandler {
98
- fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
99
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
100
+ fn call(
101
+ &self,
102
+ _request: spikard_http::grpc::GrpcRequestData,
103
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
100
104
  {
101
105
  Box::pin(async {
102
106
  Ok(spikard_http::grpc::GrpcResponseData {
@@ -110,6 +114,9 @@ async fn test_server_routes_to_correct_service() {
110
114
  }
111
115
  }
112
116
 
117
+ // Arrange: Register multiple services
118
+ let server = GrpcTestServer::new();
119
+
113
120
  server.register_service(Arc::new(UserServiceHandler));
114
121
  server.register_service(Arc::new(OrderServiceHandler));
115
122
 
@@ -124,7 +131,7 @@ async fn test_server_routes_to_correct_service() {
124
131
  .await
125
132
  .expect("UserService request failed");
126
133
 
127
- assert_grpc_response(user_response, json!({"service": "UserService"}));
134
+ assert_grpc_response(&user_response, &json!({"service": "UserService"}));
128
135
 
129
136
  // Act & Assert: Route to OrderService
130
137
  let order_response = send_unary_request(
@@ -137,28 +144,28 @@ async fn test_server_routes_to_correct_service() {
137
144
  .await
138
145
  .expect("OrderService request failed");
139
146
 
140
- assert_grpc_response(order_response, json!({"service": "OrderService"}));
147
+ assert_grpc_response(&order_response, &json!({"service": "OrderService"}));
141
148
  }
142
149
 
143
150
  /// Test server correctly counts registered services
144
151
  #[tokio::test]
145
152
  async fn test_server_service_registration_count() {
146
- let mut server = GrpcTestServer::new();
153
+ let server = GrpcTestServer::new();
147
154
 
148
155
  assert_eq!(server.service_count(), 0);
149
156
 
150
157
  // Register first service
151
- let handler1 = Arc::new(MockGrpcHandler::with_json("service.One", json!({"id": 1})));
158
+ let handler1 = Arc::new(MockGrpcHandler::with_json("service.One", &json!({"id": 1})));
152
159
  server.register_service(handler1);
153
160
  assert_eq!(server.service_count(), 1);
154
161
 
155
162
  // Register second service
156
- let handler2 = Arc::new(MockGrpcHandler::with_json("service.Two", json!({"id": 2})));
163
+ let handler2 = Arc::new(MockGrpcHandler::with_json("service.Two", &json!({"id": 2})));
157
164
  server.register_service(handler2);
158
165
  assert_eq!(server.service_count(), 2);
159
166
 
160
167
  // Register third service
161
- let handler3 = Arc::new(MockGrpcHandler::with_json("service.Three", json!({"id": 3})));
168
+ let handler3 = Arc::new(MockGrpcHandler::with_json("service.Three", &json!({"id": 3})));
162
169
  server.register_service(handler3);
163
170
  assert_eq!(server.service_count(), 3);
164
171
  }
@@ -166,16 +173,17 @@ async fn test_server_service_registration_count() {
166
173
  /// Test unary RPC with complex nested JSON payload
167
174
  #[tokio::test]
168
175
  async fn test_unary_rpc_with_nested_json_payload() {
169
- let mut server = GrpcTestServer::new();
170
-
171
176
  struct ProductServiceHandler;
172
177
  impl spikard_http::grpc::GrpcHandler for ProductServiceHandler {
173
- fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
174
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
178
+ fn call(
179
+ &self,
180
+ _request: spikard_http::grpc::GrpcRequestData,
181
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
175
182
  {
176
183
  Box::pin(async {
177
184
  Ok(spikard_http::grpc::GrpcResponseData {
178
- payload: Bytes::from(r#"{
185
+ payload: Bytes::from(
186
+ r#"{
179
187
  "id": 42,
180
188
  "name": "Laptop",
181
189
  "price": 999.99,
@@ -184,7 +192,8 @@ async fn test_unary_rpc_with_nested_json_payload() {
184
192
  "warehouse": "US-WEST"
185
193
  },
186
194
  "tags": ["electronics", "computers", "portable"]
187
- }"#),
195
+ }"#,
196
+ ),
188
197
  metadata: tonic::metadata::MetadataMap::new(),
189
198
  })
190
199
  })
@@ -194,6 +203,8 @@ async fn test_unary_rpc_with_nested_json_payload() {
194
203
  }
195
204
  }
196
205
 
206
+ let server = GrpcTestServer::new();
207
+
197
208
  server.register_service(Arc::new(ProductServiceHandler));
198
209
 
199
210
  let response = send_unary_request(
@@ -219,12 +230,12 @@ async fn test_unary_rpc_with_nested_json_payload() {
219
230
  /// Test unary RPC with binary payload (raw bytes)
220
231
  #[tokio::test]
221
232
  async fn test_unary_rpc_with_binary_payload() {
222
- let mut server = GrpcTestServer::new();
223
-
224
233
  struct EchoServiceHandler;
225
234
  impl spikard_http::grpc::GrpcHandler for EchoServiceHandler {
226
- fn call(&self, request: spikard_http::grpc::GrpcRequestData)
227
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
235
+ fn call(
236
+ &self,
237
+ request: spikard_http::grpc::GrpcRequestData,
238
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
228
239
  {
229
240
  let payload = request.payload;
230
241
  Box::pin(async {
@@ -239,6 +250,8 @@ async fn test_unary_rpc_with_binary_payload() {
239
250
  }
240
251
  }
241
252
 
253
+ let server = GrpcTestServer::new();
254
+
242
255
  server.register_service(Arc::new(EchoServiceHandler));
243
256
 
244
257
  // Send binary payload
@@ -260,12 +273,12 @@ async fn test_unary_rpc_with_binary_payload() {
260
273
  /// Test request with custom metadata is preserved in response
261
274
  #[tokio::test]
262
275
  async fn test_request_metadata_handling() {
263
- let mut server = GrpcTestServer::new();
264
-
265
276
  struct MetadataAwareHandler;
266
277
  impl spikard_http::grpc::GrpcHandler for MetadataAwareHandler {
267
- fn call(&self, request: spikard_http::grpc::GrpcRequestData)
268
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
278
+ fn call(
279
+ &self,
280
+ request: spikard_http::grpc::GrpcRequestData,
281
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
269
282
  {
270
283
  // Check that metadata was received
271
284
  let has_custom_header = request.metadata.get("x-custom-header").is_some();
@@ -287,6 +300,8 @@ async fn test_request_metadata_handling() {
287
300
  }
288
301
  }
289
302
 
303
+ let server = GrpcTestServer::new();
304
+
290
305
  server.register_service(Arc::new(MetadataAwareHandler));
291
306
 
292
307
  let mut metadata = create_test_metadata();
@@ -302,13 +317,13 @@ async fn test_request_metadata_handling() {
302
317
  .await
303
318
  .expect("Failed to send request with metadata");
304
319
 
305
- assert_grpc_response(response, json!({"status": "metadata_received"}));
320
+ assert_grpc_response(&response, &json!({"status": "metadata_received"}));
306
321
  }
307
322
 
308
- /// Test error response with NOT_FOUND status
323
+ /// Test error response with `NOT_FOUND` status
309
324
  #[tokio::test]
310
325
  async fn test_error_response_not_found() {
311
- let mut server = GrpcTestServer::new();
326
+ let server = GrpcTestServer::new();
312
327
 
313
328
  let handler = Arc::new(ErrorMockHandler::new(
314
329
  "api.NotFoundService",
@@ -329,10 +344,10 @@ async fn test_error_response_not_found() {
329
344
  assert!(result.is_err());
330
345
  }
331
346
 
332
- /// Test error response with INVALID_ARGUMENT status
347
+ /// Test error response with `INVALID_ARGUMENT` status
333
348
  #[tokio::test]
334
349
  async fn test_error_response_invalid_argument() {
335
- let mut server = GrpcTestServer::new();
350
+ let server = GrpcTestServer::new();
336
351
 
337
352
  let handler = Arc::new(ErrorMockHandler::new(
338
353
  "api.ValidationService",
@@ -356,7 +371,7 @@ async fn test_error_response_invalid_argument() {
356
371
  /// Test error response with INTERNAL status
357
372
  #[tokio::test]
358
373
  async fn test_error_response_internal_server_error() {
359
- let mut server = GrpcTestServer::new();
374
+ let server = GrpcTestServer::new();
360
375
 
361
376
  let handler = Arc::new(ErrorMockHandler::new(
362
377
  "api.DatabaseService",
@@ -401,12 +416,12 @@ async fn test_server_no_services_registered() {
401
416
  /// Test unary RPC with empty request payload
402
417
  #[tokio::test]
403
418
  async fn test_unary_rpc_empty_request_payload() {
404
- let mut server = GrpcTestServer::new();
405
-
406
419
  struct EmptyRequestHandler;
407
420
  impl spikard_http::grpc::GrpcHandler for EmptyRequestHandler {
408
- fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
409
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
421
+ fn call(
422
+ &self,
423
+ _request: spikard_http::grpc::GrpcRequestData,
424
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
410
425
  {
411
426
  Box::pin(async {
412
427
  Ok(spikard_http::grpc::GrpcResponseData {
@@ -420,6 +435,8 @@ async fn test_unary_rpc_empty_request_payload() {
420
435
  }
421
436
  }
422
437
 
438
+ let server = GrpcTestServer::new();
439
+
423
440
  server.register_service(Arc::new(EmptyRequestHandler));
424
441
 
425
442
  let response = send_unary_request(
@@ -432,18 +449,18 @@ async fn test_unary_rpc_empty_request_payload() {
432
449
  .await
433
450
  .expect("Failed to send empty request");
434
451
 
435
- assert_grpc_response(response, json!({"status": "ok"}));
452
+ assert_grpc_response(&response, &json!({"status": "ok"}));
436
453
  }
437
454
 
438
455
  /// Test echo handler preserves exact request payload
439
456
  #[tokio::test]
440
457
  async fn test_echo_handler_payload_preservation() {
441
- let mut server = GrpcTestServer::new();
442
-
443
458
  struct TestEchoHandler;
444
459
  impl spikard_http::grpc::GrpcHandler for TestEchoHandler {
445
- fn call(&self, request: spikard_http::grpc::GrpcRequestData)
446
- -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
460
+ fn call(
461
+ &self,
462
+ request: spikard_http::grpc::GrpcRequestData,
463
+ ) -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
447
464
  {
448
465
  let payload = request.payload;
449
466
  Box::pin(async move {
@@ -458,6 +475,8 @@ async fn test_echo_handler_payload_preservation() {
458
475
  }
459
476
  }
460
477
 
478
+ let server = GrpcTestServer::new();
479
+
461
480
  server.register_service(Arc::new(TestEchoHandler));
462
481
 
463
482
  let request_payload = Bytes::from(r#"{"echo": "test message"}"#);
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-rb"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -101,9 +101,8 @@ impl RubyGrpcResponse {
101
101
  return Err(Error::new(magnus::exception::arg_error(), "wrong number of arguments"));
102
102
  };
103
103
 
104
- let payload_str = RString::try_convert(payload_value).map_err(|_| {
105
- Error::new(magnus::exception::arg_error(), "payload must be a String (binary)")
106
- })?;
104
+ let payload_str = RString::try_convert(payload_value)
105
+ .map_err(|_| Error::new(magnus::exception::arg_error(), "payload must be a String (binary)"))?;
107
106
 
108
107
  let payload_bytes = unsafe { payload_str.as_slice() }.to_vec();
109
108
 
@@ -203,32 +202,26 @@ impl RubyGrpcHandler {
203
202
  }
204
203
 
205
204
  fn handle_request_inner(&self, request: GrpcRequestData) -> GrpcHandlerResult {
206
- let ruby = Ruby::get().map_err(|_| {
207
- tonic::Status::internal("Ruby VM unavailable while invoking gRPC handler")
208
- })?;
205
+ let ruby =
206
+ Ruby::get().map_err(|_| tonic::Status::internal("Ruby VM unavailable while invoking gRPC handler"))?;
209
207
 
210
208
  // Convert request to Ruby object
211
209
  let ruby_request = RubyGrpcRequest::from_grpc_request(request);
212
- let request_value = ruby
213
- .obj_wrap(ruby_request)
214
- .as_value();
210
+ let request_value = ruby.obj_wrap(ruby_request).as_value();
215
211
 
216
212
  // Call Ruby handler
217
213
  let handler_value = self.inner.handler.get_inner_with(&ruby);
218
214
  let response_value = handler_value
219
215
  .funcall::<_, _, Value>("handle_request", (request_value,))
220
- .map_err(|err| {
221
- tonic::Status::internal(format!("Ruby gRPC handler failed: {}", err))
222
- })?;
216
+ .map_err(|err| tonic::Status::internal(format!("Ruby gRPC handler failed: {}", err)))?;
223
217
 
224
218
  // Convert Ruby response to GrpcResponseData
225
- let ruby_response = <&RubyGrpcResponse>::try_convert(response_value)
226
- .map_err(|err| {
227
- tonic::Status::internal(format!(
228
- "Handler must return Spikard::Grpc::Response, got error: {}",
229
- err
230
- ))
231
- })?;
219
+ let ruby_response = <&RubyGrpcResponse>::try_convert(response_value).map_err(|err| {
220
+ tonic::Status::internal(format!(
221
+ "Handler must return Spikard::Grpc::Response, got error: {}",
222
+ err
223
+ ))
224
+ })?;
232
225
 
233
226
  ruby_response
234
227
  .clone()
@@ -299,12 +292,24 @@ mod tests {
299
292
  use spikard_bindings_shared::grpc_metadata::extract_metadata_to_hashmap;
300
293
 
301
294
  let mut metadata = MetadataMap::new();
302
- metadata.insert("content-type", "application/grpc".parse().unwrap());
303
- metadata.insert("authorization", "Bearer token123".parse().unwrap());
295
+ metadata.insert(
296
+ "content-type",
297
+ "application/grpc".parse().expect("Valid metadata value"),
298
+ );
299
+ metadata.insert(
300
+ "authorization",
301
+ "Bearer token123".parse().expect("Valid metadata value"),
302
+ );
304
303
 
305
304
  let extracted = extract_metadata_to_hashmap(&metadata, false);
306
- assert_eq!(extracted.get("content-type").unwrap(), "application/grpc");
307
- assert_eq!(extracted.get("authorization").unwrap(), "Bearer token123");
305
+ assert_eq!(
306
+ extracted.get("content-type").expect("content-type header"),
307
+ "application/grpc"
308
+ );
309
+ assert_eq!(
310
+ extracted.get("authorization").expect("authorization header"),
311
+ "Bearer token123"
312
+ );
308
313
  }
309
314
 
310
315
  #[test]
@@ -316,7 +321,7 @@ mod tests {
316
321
 
317
322
  let grpc_response = response.into_grpc_response();
318
323
  assert!(grpc_response.is_ok());
319
- let grpc_response = grpc_response.unwrap();
324
+ let grpc_response = grpc_response.expect("Valid grpc response");
320
325
  assert_eq!(grpc_response.payload, Bytes::from("test response"));
321
326
  }
322
327
 
@@ -332,7 +337,7 @@ mod tests {
332
337
 
333
338
  let grpc_response = response.into_grpc_response();
334
339
  assert!(grpc_response.is_ok());
335
- let grpc_response = grpc_response.unwrap();
340
+ let grpc_response = grpc_response.expect("Valid grpc response");
336
341
  assert!(!grpc_response.metadata.is_empty());
337
342
  }
338
343
 
@@ -880,8 +880,7 @@ impl RubyHandler {
880
880
 
881
881
  fn handle(&self, request_data: RequestData) -> HandlerResult {
882
882
  with_gvl(|| {
883
- let result =
884
- std::panic::catch_unwind(AssertUnwindSafe(|| self.handle_inner(request_data)));
883
+ let result = std::panic::catch_unwind(AssertUnwindSafe(|| self.handle_inner(request_data)));
885
884
  match result {
886
885
  Ok(res) => res,
887
886
  Err(_) => Err((
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-rb-macros"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  publish = false
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spikard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Na'aman Hirschfeld