spikard 0.7.4 → 0.8.0

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.lock +583 -201
  3. data/ext/spikard_rb/Cargo.toml +1 -1
  4. data/lib/spikard/grpc.rb +182 -0
  5. data/lib/spikard/version.rb +1 -1
  6. data/lib/spikard.rb +1 -1
  7. data/vendor/crates/spikard-bindings-shared/Cargo.toml +2 -1
  8. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +197 -0
  9. data/vendor/crates/spikard-bindings-shared/src/lib.rs +2 -0
  10. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  11. data/vendor/crates/spikard-http/Cargo.toml +5 -1
  12. data/vendor/crates/spikard-http/src/grpc/handler.rs +260 -0
  13. data/vendor/crates/spikard-http/src/grpc/mod.rs +342 -0
  14. data/vendor/crates/spikard-http/src/grpc/service.rs +392 -0
  15. data/vendor/crates/spikard-http/src/grpc/streaming.rs +237 -0
  16. data/vendor/crates/spikard-http/src/lib.rs +14 -0
  17. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +288 -0
  18. data/vendor/crates/spikard-http/src/server/mod.rs +1 -0
  19. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +1023 -0
  20. data/vendor/crates/spikard-http/tests/common/mod.rs +8 -0
  21. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +653 -0
  22. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +332 -0
  23. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +518 -0
  24. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +476 -0
  25. data/vendor/crates/spikard-rb/Cargo.toml +2 -1
  26. data/vendor/crates/spikard-rb/src/config/server_config.rs +1 -0
  27. data/vendor/crates/spikard-rb/src/grpc/handler.rs +352 -0
  28. data/vendor/crates/spikard-rb/src/grpc/mod.rs +9 -0
  29. data/vendor/crates/spikard-rb/src/lib.rs +4 -0
  30. data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
  31. metadata +15 -1
@@ -19,8 +19,16 @@
19
19
 
20
20
  pub mod handlers;
21
21
  pub mod test_builders;
22
+ pub mod grpc_helpers;
22
23
 
23
24
  #[allow(unused_imports)]
24
25
  pub use handlers::{EchoHandler, ErrorHandler, JsonHandler, PanicHandler, SuccessHandler};
25
26
  #[allow(unused_imports)]
26
27
  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
+ };
@@ -0,0 +1,653 @@
1
+ //! Comprehensive gRPC error handling tests
2
+ //!
3
+ //! Tests all 17 standard gRPC status codes including:
4
+ //! - OK (0)
5
+ //! - CANCELLED (1)
6
+ //! - UNKNOWN (2)
7
+ //! - INVALID_ARGUMENT (3)
8
+ //! - DEADLINE_EXCEEDED (4)
9
+ //! - NOT_FOUND (5)
10
+ //! - ALREADY_EXISTS (6)
11
+ //! - PERMISSION_DENIED (7)
12
+ //! - RESOURCE_EXHAUSTED (8)
13
+ //! - FAILED_PRECONDITION (9)
14
+ //! - ABORTED (10)
15
+ //! - OUT_OF_RANGE (11)
16
+ //! - UNIMPLEMENTED (12)
17
+ //! - INTERNAL (13)
18
+ //! - UNAVAILABLE (14)
19
+ //! - DATA_LOSS (15)
20
+ //! - UNAUTHENTICATED (16)
21
+
22
+ use crate::common::grpc_helpers::*;
23
+ use bytes::Bytes;
24
+ use std::sync::Arc;
25
+ use tonic::Code;
26
+
27
+ mod common;
28
+
29
+ /// Test OK status (success case)
30
+ #[tokio::test]
31
+ async fn test_status_ok_success() {
32
+ let mut server = GrpcTestServer::new();
33
+
34
+ struct SuccessHandler;
35
+ impl spikard_http::grpc::GrpcHandler for SuccessHandler {
36
+ fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
37
+ -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
38
+ {
39
+ Box::pin(async {
40
+ Ok(spikard_http::grpc::GrpcResponseData {
41
+ payload: Bytes::from(r#"{"result": "success"}"#),
42
+ metadata: tonic::metadata::MetadataMap::new(),
43
+ })
44
+ })
45
+ }
46
+ fn service_name(&self) -> &'static str {
47
+ "test.SuccessService"
48
+ }
49
+ }
50
+
51
+ server.register_service(Arc::new(SuccessHandler));
52
+
53
+ let response = send_unary_request(
54
+ &server,
55
+ "test.SuccessService",
56
+ "DoWork",
57
+ Bytes::from("{}"),
58
+ create_test_metadata(),
59
+ )
60
+ .await;
61
+
62
+ assert!(response.is_ok());
63
+ let resp = response.unwrap();
64
+ assert_grpc_response(resp, serde_json::json!({"result": "success"}));
65
+ }
66
+
67
+ /// Test CANCELLED status code
68
+ #[tokio::test]
69
+ async fn test_status_cancelled() {
70
+ let mut server = GrpcTestServer::new();
71
+
72
+ let handler = Arc::new(ErrorMockHandler::new(
73
+ "test.CancelledService",
74
+ Code::Cancelled,
75
+ "Request was cancelled by the client",
76
+ ));
77
+ server.register_service(handler);
78
+
79
+ let result = send_unary_request(
80
+ &server,
81
+ "test.CancelledService",
82
+ "CancelledOperation",
83
+ Bytes::from("{}"),
84
+ create_test_metadata(),
85
+ )
86
+ .await;
87
+
88
+ // Verify error occurred
89
+ assert!(result.is_err());
90
+ }
91
+
92
+ /// Test UNKNOWN status code
93
+ #[tokio::test]
94
+ async fn test_status_unknown() {
95
+ let mut server = GrpcTestServer::new();
96
+
97
+ let handler = Arc::new(ErrorMockHandler::new(
98
+ "test.UnknownService",
99
+ Code::Unknown,
100
+ "Unknown error occurred",
101
+ ));
102
+ server.register_service(handler);
103
+
104
+ let result = send_unary_request(
105
+ &server,
106
+ "test.UnknownService",
107
+ "UnknownMethod",
108
+ Bytes::from("{}"),
109
+ create_test_metadata(),
110
+ )
111
+ .await;
112
+
113
+ assert!(result.is_err());
114
+ }
115
+
116
+ /// Test INVALID_ARGUMENT status code
117
+ #[tokio::test]
118
+ async fn test_status_invalid_argument() {
119
+ let mut server = GrpcTestServer::new();
120
+
121
+ let handler = Arc::new(ErrorMockHandler::new(
122
+ "test.ValidationService",
123
+ Code::InvalidArgument,
124
+ "Field 'email' must be a valid email address",
125
+ ));
126
+ server.register_service(handler);
127
+
128
+ let result = send_unary_request(
129
+ &server,
130
+ "test.ValidationService",
131
+ "ValidateInput",
132
+ Bytes::from("{}"),
133
+ create_test_metadata(),
134
+ )
135
+ .await;
136
+
137
+ assert!(result.is_err());
138
+ // Error verified
139
+ }
140
+
141
+ /// Test DEADLINE_EXCEEDED status code
142
+ #[tokio::test]
143
+ async fn test_status_deadline_exceeded() {
144
+ let mut server = GrpcTestServer::new();
145
+
146
+ let handler = Arc::new(ErrorMockHandler::new(
147
+ "test.TimeoutService",
148
+ Code::DeadlineExceeded,
149
+ "Request took too long to process (timeout)",
150
+ ));
151
+ server.register_service(handler);
152
+
153
+ let result = send_unary_request(
154
+ &server,
155
+ "test.TimeoutService",
156
+ "SlowOperation",
157
+ Bytes::from("{}"),
158
+ create_test_metadata(),
159
+ )
160
+ .await;
161
+
162
+ assert!(result.is_err());
163
+ // Error verified
164
+ }
165
+
166
+ /// Test NOT_FOUND status code
167
+ #[tokio::test]
168
+ async fn test_status_not_found() {
169
+ let mut server = GrpcTestServer::new();
170
+
171
+ let handler = Arc::new(ErrorMockHandler::new(
172
+ "test.NotFoundService",
173
+ Code::NotFound,
174
+ "User with ID 12345 does not exist",
175
+ ));
176
+ server.register_service(handler);
177
+
178
+ let result = send_unary_request(
179
+ &server,
180
+ "test.NotFoundService",
181
+ "GetUser",
182
+ Bytes::from("{}"),
183
+ create_test_metadata(),
184
+ )
185
+ .await;
186
+
187
+ assert!(result.is_err());
188
+ // Error verified
189
+ }
190
+
191
+ /// Test ALREADY_EXISTS status code
192
+ #[tokio::test]
193
+ async fn test_status_already_exists() {
194
+ let mut server = GrpcTestServer::new();
195
+
196
+ let handler = Arc::new(ErrorMockHandler::new(
197
+ "test.CreateService",
198
+ Code::AlreadyExists,
199
+ "User with email 'john@example.com' already exists",
200
+ ));
201
+ server.register_service(handler);
202
+
203
+ let result = send_unary_request(
204
+ &server,
205
+ "test.CreateService",
206
+ "CreateUser",
207
+ Bytes::from("{}"),
208
+ create_test_metadata(),
209
+ )
210
+ .await;
211
+
212
+ assert!(result.is_err());
213
+ // Error verified
214
+ }
215
+
216
+ /// Test PERMISSION_DENIED status code
217
+ #[tokio::test]
218
+ async fn test_status_permission_denied() {
219
+ let mut server = GrpcTestServer::new();
220
+
221
+ let handler = Arc::new(ErrorMockHandler::new(
222
+ "test.AuthService",
223
+ Code::PermissionDenied,
224
+ "User does not have permission to delete resources",
225
+ ));
226
+ server.register_service(handler);
227
+
228
+ let result = send_unary_request(
229
+ &server,
230
+ "test.AuthService",
231
+ "DeleteResource",
232
+ Bytes::from("{}"),
233
+ create_test_metadata(),
234
+ )
235
+ .await;
236
+
237
+ assert!(result.is_err());
238
+ // Error verified
239
+ }
240
+
241
+ /// Test RESOURCE_EXHAUSTED status code
242
+ #[tokio::test]
243
+ async fn test_status_resource_exhausted() {
244
+ let mut server = GrpcTestServer::new();
245
+
246
+ let handler = Arc::new(ErrorMockHandler::new(
247
+ "test.QuotaService",
248
+ Code::ResourceExhausted,
249
+ "API rate limit exceeded. Maximum 100 requests per minute.",
250
+ ));
251
+ server.register_service(handler);
252
+
253
+ let result = send_unary_request(
254
+ &server,
255
+ "test.QuotaService",
256
+ "MakeRequest",
257
+ Bytes::from("{}"),
258
+ create_test_metadata(),
259
+ )
260
+ .await;
261
+
262
+ assert!(result.is_err());
263
+ // Error verified
264
+ }
265
+
266
+ /// Test FAILED_PRECONDITION status code
267
+ #[tokio::test]
268
+ async fn test_status_failed_precondition() {
269
+ let mut server = GrpcTestServer::new();
270
+
271
+ let handler = Arc::new(ErrorMockHandler::new(
272
+ "test.OrderService",
273
+ Code::FailedPrecondition,
274
+ "Cannot process order: inventory for product is out of stock",
275
+ ));
276
+ server.register_service(handler);
277
+
278
+ let result = send_unary_request(
279
+ &server,
280
+ "test.OrderService",
281
+ "ProcessOrder",
282
+ Bytes::from("{}"),
283
+ create_test_metadata(),
284
+ )
285
+ .await;
286
+
287
+ assert!(result.is_err());
288
+ // Error verified
289
+ }
290
+
291
+ /// Test ABORTED status code
292
+ #[tokio::test]
293
+ async fn test_status_aborted() {
294
+ let mut server = GrpcTestServer::new();
295
+
296
+ let handler = Arc::new(ErrorMockHandler::new(
297
+ "test.TransactionService",
298
+ Code::Aborted,
299
+ "Transaction was aborted due to concurrency conflict",
300
+ ));
301
+ server.register_service(handler);
302
+
303
+ let result = send_unary_request(
304
+ &server,
305
+ "test.TransactionService",
306
+ "CommitTransaction",
307
+ Bytes::from("{}"),
308
+ create_test_metadata(),
309
+ )
310
+ .await;
311
+
312
+ assert!(result.is_err());
313
+ // Error verified
314
+ }
315
+
316
+ /// Test OUT_OF_RANGE status code
317
+ #[tokio::test]
318
+ async fn test_status_out_of_range() {
319
+ let mut server = GrpcTestServer::new();
320
+
321
+ let handler = Arc::new(ErrorMockHandler::new(
322
+ "test.PageService",
323
+ Code::OutOfRange,
324
+ "Requested page 999 is out of range. Only 10 pages available.",
325
+ ));
326
+ server.register_service(handler);
327
+
328
+ let result = send_unary_request(
329
+ &server,
330
+ "test.PageService",
331
+ "GetPage",
332
+ Bytes::from("{}"),
333
+ create_test_metadata(),
334
+ )
335
+ .await;
336
+
337
+ assert!(result.is_err());
338
+ // Error verified
339
+ }
340
+
341
+ /// Test UNIMPLEMENTED status code
342
+ #[tokio::test]
343
+ async fn test_status_unimplemented() {
344
+ let mut server = GrpcTestServer::new();
345
+
346
+ let handler = Arc::new(ErrorMockHandler::new(
347
+ "test.ExperimentalService",
348
+ Code::Unimplemented,
349
+ "Method 'AdvancedFeature' is not yet implemented",
350
+ ));
351
+ server.register_service(handler);
352
+
353
+ let result = send_unary_request(
354
+ &server,
355
+ "test.ExperimentalService",
356
+ "AdvancedFeature",
357
+ Bytes::from("{}"),
358
+ create_test_metadata(),
359
+ )
360
+ .await;
361
+
362
+ assert!(result.is_err());
363
+ // Error verified
364
+ }
365
+
366
+ /// Test INTERNAL status code
367
+ #[tokio::test]
368
+ async fn test_status_internal_error() {
369
+ let mut server = GrpcTestServer::new();
370
+
371
+ let handler = Arc::new(ErrorMockHandler::new(
372
+ "test.DatabaseService",
373
+ Code::Internal,
374
+ "Internal server error: Database connection pool exhausted",
375
+ ));
376
+ server.register_service(handler);
377
+
378
+ let result = send_unary_request(
379
+ &server,
380
+ "test.DatabaseService",
381
+ "QueryData",
382
+ Bytes::from("{}"),
383
+ create_test_metadata(),
384
+ )
385
+ .await;
386
+
387
+ assert!(result.is_err());
388
+ // Error verified
389
+ }
390
+
391
+ /// Test UNAVAILABLE status code
392
+ #[tokio::test]
393
+ async fn test_status_unavailable() {
394
+ let mut server = GrpcTestServer::new();
395
+
396
+ let handler = Arc::new(ErrorMockHandler::new(
397
+ "test.ExternalService",
398
+ Code::Unavailable,
399
+ "Service is temporarily unavailable. Please retry later.",
400
+ ));
401
+ server.register_service(handler);
402
+
403
+ let result = send_unary_request(
404
+ &server,
405
+ "test.ExternalService",
406
+ "CallExternal",
407
+ Bytes::from("{}"),
408
+ create_test_metadata(),
409
+ )
410
+ .await;
411
+
412
+ assert!(result.is_err());
413
+ // Error verified
414
+ }
415
+
416
+ /// Test DATA_LOSS status code
417
+ #[tokio::test]
418
+ async fn test_status_data_loss() {
419
+ let mut server = GrpcTestServer::new();
420
+
421
+ let handler = Arc::new(ErrorMockHandler::new(
422
+ "test.FileService",
423
+ Code::DataLoss,
424
+ "Data loss detected: corrupted database records",
425
+ ));
426
+ server.register_service(handler);
427
+
428
+ let result = send_unary_request(
429
+ &server,
430
+ "test.FileService",
431
+ "RecoverData",
432
+ Bytes::from("{}"),
433
+ create_test_metadata(),
434
+ )
435
+ .await;
436
+
437
+ assert!(result.is_err());
438
+ // Error verified
439
+ }
440
+
441
+ /// Test UNAUTHENTICATED status code
442
+ #[tokio::test]
443
+ async fn test_status_unauthenticated() {
444
+ let mut server = GrpcTestServer::new();
445
+
446
+ let handler = Arc::new(ErrorMockHandler::new(
447
+ "test.SecureService",
448
+ Code::Unauthenticated,
449
+ "Authentication required. Please provide valid credentials.",
450
+ ));
451
+ server.register_service(handler);
452
+
453
+ let result = send_unary_request(
454
+ &server,
455
+ "test.SecureService",
456
+ "SecureOperation",
457
+ Bytes::from("{}"),
458
+ create_test_metadata(),
459
+ )
460
+ .await;
461
+
462
+ assert!(result.is_err());
463
+ // Error verified
464
+ }
465
+
466
+ /// Test error message propagation
467
+ #[tokio::test]
468
+ async fn test_error_message_propagation() {
469
+ let mut server = GrpcTestServer::new();
470
+
471
+ let detailed_message = "Validation failed: Password must be at least 12 characters. \
472
+ Current length: 8. Special characters required.";
473
+ let handler = Arc::new(ErrorMockHandler::new(
474
+ "test.DetailedErrorService",
475
+ Code::InvalidArgument,
476
+ detailed_message,
477
+ ));
478
+ server.register_service(handler);
479
+
480
+ let result = send_unary_request(
481
+ &server,
482
+ "test.DetailedErrorService",
483
+ "Validate",
484
+ Bytes::from("{}"),
485
+ create_test_metadata(),
486
+ )
487
+ .await;
488
+
489
+ assert!(result.is_err());
490
+ }
491
+
492
+ /// Test multiple error responses in sequence
493
+ #[tokio::test]
494
+ async fn test_multiple_error_responses() {
495
+ let mut server = GrpcTestServer::new();
496
+
497
+ // Register service that returns INVALID_ARGUMENT
498
+ let handler1 = Arc::new(ErrorMockHandler::new(
499
+ "test.Service1",
500
+ Code::InvalidArgument,
501
+ "Invalid input",
502
+ ));
503
+ server.register_service(handler1);
504
+
505
+ // Register service that returns NOT_FOUND
506
+ let handler2 = Arc::new(ErrorMockHandler::new(
507
+ "test.Service2",
508
+ Code::NotFound,
509
+ "Resource not found",
510
+ ));
511
+ server.register_service(handler2);
512
+
513
+ // Register service that returns PERMISSION_DENIED
514
+ let handler3 = Arc::new(ErrorMockHandler::new(
515
+ "test.Service3",
516
+ Code::PermissionDenied,
517
+ "Access denied",
518
+ ));
519
+ server.register_service(handler3);
520
+
521
+ // Test first error
522
+ let result1 = send_unary_request(
523
+ &server,
524
+ "test.Service1",
525
+ "Method",
526
+ Bytes::from("{}"),
527
+ create_test_metadata(),
528
+ )
529
+ .await;
530
+ assert!(result1.is_err());
531
+
532
+ // Test second error
533
+ let result2 = send_unary_request(
534
+ &server,
535
+ "test.Service2",
536
+ "Method",
537
+ Bytes::from("{}"),
538
+ create_test_metadata(),
539
+ )
540
+ .await;
541
+ assert!(result2.is_err());
542
+
543
+ // Test third error
544
+ let result3 = send_unary_request(
545
+ &server,
546
+ "test.Service3",
547
+ "Method",
548
+ Bytes::from("{}"),
549
+ create_test_metadata(),
550
+ )
551
+ .await;
552
+ assert!(result3.is_err());
553
+ }
554
+
555
+ /// Test error with empty message
556
+ #[tokio::test]
557
+ async fn test_error_with_empty_message() {
558
+ let mut server = GrpcTestServer::new();
559
+
560
+ let handler = Arc::new(ErrorMockHandler::new(
561
+ "test.EmptyErrorService",
562
+ Code::Internal,
563
+ "",
564
+ ));
565
+ server.register_service(handler);
566
+
567
+ let result = send_unary_request(
568
+ &server,
569
+ "test.EmptyErrorService",
570
+ "Method",
571
+ Bytes::from("{}"),
572
+ create_test_metadata(),
573
+ )
574
+ .await;
575
+
576
+ assert!(result.is_err());
577
+ }
578
+
579
+ /// Test error with very long message
580
+ #[tokio::test]
581
+ async fn test_error_with_long_message() {
582
+ let mut server = GrpcTestServer::new();
583
+
584
+ let long_message = "Error occurred during processing: ".to_string()
585
+ + &"detailed reason ".repeat(50)
586
+ + "Please contact support.";
587
+
588
+ let handler = Arc::new(ErrorMockHandler::new(
589
+ "test.LongErrorService",
590
+ Code::Internal,
591
+ &long_message,
592
+ ));
593
+ server.register_service(handler);
594
+
595
+ let result = send_unary_request(
596
+ &server,
597
+ "test.LongErrorService",
598
+ "Method",
599
+ Bytes::from("{}"),
600
+ create_test_metadata(),
601
+ )
602
+ .await;
603
+
604
+ assert!(result.is_err());
605
+ }
606
+
607
+ /// Test handler conversion of custom errors to gRPC status
608
+ #[tokio::test]
609
+ async fn test_handler_error_to_status_conversion() {
610
+ let mut server = GrpcTestServer::new();
611
+
612
+ struct ValidationHandler;
613
+ impl spikard_http::grpc::GrpcHandler for ValidationHandler {
614
+ fn call(&self, request: spikard_http::grpc::GrpcRequestData)
615
+ -> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
616
+ {
617
+ // Simulate validation logic
618
+ if request.payload.is_empty() {
619
+ Box::pin(async {
620
+ Err(tonic::Status::new(
621
+ Code::InvalidArgument,
622
+ "Request body cannot be empty"
623
+ ))
624
+ })
625
+ } else {
626
+ Box::pin(async {
627
+ Ok(spikard_http::grpc::GrpcResponseData {
628
+ payload: Bytes::from(r#"{"status": "valid"}"#),
629
+ metadata: tonic::metadata::MetadataMap::new(),
630
+ })
631
+ })
632
+ }
633
+ }
634
+ fn service_name(&self) -> &'static str {
635
+ "test.ValidationService"
636
+ }
637
+ }
638
+
639
+ server.register_service(Arc::new(ValidationHandler));
640
+
641
+ // Test with empty payload (should error)
642
+ let error_result = send_unary_request(
643
+ &server,
644
+ "test.ValidationService",
645
+ "Validate",
646
+ Bytes::new(),
647
+ create_test_metadata(),
648
+ )
649
+ .await;
650
+
651
+ assert!(error_result.is_err());
652
+ assert!(error_result.unwrap_err().to_string().contains("empty"));
653
+ }