spikard 0.3.6 → 0.5.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -6
  3. data/ext/spikard_rb/Cargo.toml +2 -2
  4. data/lib/spikard/app.rb +33 -14
  5. data/lib/spikard/testing.rb +47 -12
  6. data/lib/spikard/version.rb +1 -1
  7. data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
  8. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
  9. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
  10. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
  11. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
  12. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
  13. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
  14. data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
  15. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
  16. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
  17. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
  18. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
  19. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
  20. data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
  21. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
  22. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
  23. data/vendor/crates/spikard-core/Cargo.toml +4 -4
  24. data/vendor/crates/spikard-core/src/debug.rs +64 -0
  25. data/vendor/crates/spikard-core/src/di/container.rs +3 -27
  26. data/vendor/crates/spikard-core/src/di/factory.rs +1 -5
  27. data/vendor/crates/spikard-core/src/di/graph.rs +8 -47
  28. data/vendor/crates/spikard-core/src/di/mod.rs +1 -1
  29. data/vendor/crates/spikard-core/src/di/resolved.rs +1 -7
  30. data/vendor/crates/spikard-core/src/di/value.rs +2 -4
  31. data/vendor/crates/spikard-core/src/errors.rs +30 -0
  32. data/vendor/crates/spikard-core/src/http.rs +262 -0
  33. data/vendor/crates/spikard-core/src/lib.rs +1 -1
  34. data/vendor/crates/spikard-core/src/lifecycle.rs +764 -0
  35. data/vendor/crates/spikard-core/src/metadata.rs +389 -0
  36. data/vendor/crates/spikard-core/src/parameters.rs +1962 -159
  37. data/vendor/crates/spikard-core/src/problem.rs +34 -0
  38. data/vendor/crates/spikard-core/src/request_data.rs +966 -1
  39. data/vendor/crates/spikard-core/src/router.rs +263 -2
  40. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +688 -0
  41. data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +26 -268
  42. data/vendor/crates/spikard-http/Cargo.toml +12 -16
  43. data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
  44. data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
  45. data/vendor/crates/spikard-http/src/auth.rs +65 -16
  46. data/vendor/crates/spikard-http/src/background.rs +1614 -3
  47. data/vendor/crates/spikard-http/src/cors.rs +515 -0
  48. data/vendor/crates/spikard-http/src/debug.rs +65 -0
  49. data/vendor/crates/spikard-http/src/di_handler.rs +1322 -77
  50. data/vendor/crates/spikard-http/src/handler_response.rs +711 -0
  51. data/vendor/crates/spikard-http/src/handler_trait.rs +607 -5
  52. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +6 -0
  53. data/vendor/crates/spikard-http/src/lib.rs +33 -28
  54. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +81 -0
  55. data/vendor/crates/spikard-http/src/lifecycle.rs +765 -0
  56. data/vendor/crates/spikard-http/src/middleware/mod.rs +372 -117
  57. data/vendor/crates/spikard-http/src/middleware/multipart.rs +836 -10
  58. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +409 -43
  59. data/vendor/crates/spikard-http/src/middleware/validation.rs +513 -65
  60. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +345 -0
  61. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1055 -0
  62. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +473 -3
  63. data/vendor/crates/spikard-http/src/query_parser.rs +455 -31
  64. data/vendor/crates/spikard-http/src/response.rs +321 -0
  65. data/vendor/crates/spikard-http/src/server/handler.rs +1572 -9
  66. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +136 -0
  67. data/vendor/crates/spikard-http/src/server/mod.rs +875 -178
  68. data/vendor/crates/spikard-http/src/server/request_extraction.rs +674 -23
  69. data/vendor/crates/spikard-http/src/server/routing_factory.rs +599 -0
  70. data/vendor/crates/spikard-http/src/sse.rs +983 -21
  71. data/vendor/crates/spikard-http/src/testing/form.rs +38 -0
  72. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -2
  73. data/vendor/crates/spikard-http/src/testing.rs +7 -7
  74. data/vendor/crates/spikard-http/src/websocket.rs +1055 -4
  75. data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
  76. data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
  77. data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
  78. data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
  79. data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
  80. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
  81. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
  82. data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
  83. data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
  84. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
  85. data/vendor/crates/spikard-rb/Cargo.toml +10 -4
  86. data/vendor/crates/spikard-rb/build.rs +196 -5
  87. data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
  88. data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +100 -109
  89. data/vendor/crates/spikard-rb/src/conversion.rs +121 -20
  90. data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
  91. data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +12 -46
  92. data/vendor/crates/spikard-rb/src/handler.rs +100 -107
  93. data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
  94. data/vendor/crates/spikard-rb/src/lib.rs +467 -1428
  95. data/vendor/crates/spikard-rb/src/lifecycle.rs +1 -0
  96. data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
  97. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +447 -0
  98. data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
  99. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
  100. data/vendor/crates/spikard-rb/src/server.rs +47 -22
  101. data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +187 -40
  102. data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
  103. data/vendor/crates/spikard-rb/src/testing/websocket.rs +635 -0
  104. data/vendor/crates/spikard-rb/src/websocket.rs +178 -37
  105. metadata +46 -13
  106. data/vendor/crates/spikard-http/src/parameters.rs +0 -1
  107. data/vendor/crates/spikard-http/src/problem.rs +0 -1
  108. data/vendor/crates/spikard-http/src/router.rs +0 -1
  109. data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
  110. data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
  111. data/vendor/crates/spikard-http/src/validation.rs +0 -1
  112. data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
  113. /data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +0 -0
@@ -396,4 +396,325 @@ mod tests {
396
396
  let cookie = response.headers.get("set-cookie").unwrap();
397
397
  assert!(cookie.contains("Max-Age=-1"));
398
398
  }
399
+
400
+ #[test]
401
+ fn response_various_status_codes() {
402
+ let status_codes = vec![
403
+ (200, "OK"),
404
+ (201, "Created"),
405
+ (204, "No Content"),
406
+ (301, "Moved Permanently"),
407
+ (302, "Found"),
408
+ (304, "Not Modified"),
409
+ (400, "Bad Request"),
410
+ (401, "Unauthorized"),
411
+ (403, "Forbidden"),
412
+ (404, "Not Found"),
413
+ (500, "Internal Server Error"),
414
+ (502, "Bad Gateway"),
415
+ (503, "Service Unavailable"),
416
+ ];
417
+
418
+ for (code, _name) in status_codes {
419
+ let response = Response::with_status(None, code);
420
+ assert_eq!(response.status_code, code);
421
+ assert!(response.headers.is_empty());
422
+ }
423
+ }
424
+
425
+ #[test]
426
+ fn response_with_large_json_body() {
427
+ let mut items = vec![];
428
+ for i in 0..1000 {
429
+ items.push(json!({"id": i, "name": format!("item_{}", i)}));
430
+ }
431
+ let large_array = serde_json::Value::Array(items);
432
+ let response = Response::new(Some(large_array.clone()));
433
+ assert_eq!(response.status_code, 200);
434
+ assert_eq!(response.content, Some(large_array));
435
+ }
436
+
437
+ #[test]
438
+ fn response_with_deeply_nested_json() {
439
+ let nested = json!({
440
+ "level1": {
441
+ "level2": {
442
+ "level3": {
443
+ "level4": {
444
+ "level5": {
445
+ "data": "deeply nested value"
446
+ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+ });
452
+ let response = Response::new(Some(nested.clone()));
453
+ assert_eq!(response.content, Some(nested));
454
+ }
455
+
456
+ #[test]
457
+ fn response_with_empty_json_object() {
458
+ let empty_obj = json!({});
459
+ let response = Response::new(Some(empty_obj.clone()));
460
+ assert_eq!(response.content, Some(empty_obj));
461
+ assert_ne!(response.content, None);
462
+ }
463
+
464
+ #[test]
465
+ fn response_with_empty_json_array() {
466
+ let empty_array = json!([]);
467
+ let response = Response::new(Some(empty_array.clone()));
468
+ assert_eq!(response.content, Some(empty_array));
469
+ assert_ne!(response.content, None);
470
+ }
471
+
472
+ #[test]
473
+ fn response_with_null_vs_none() {
474
+ let null_value = json!(null);
475
+ let response_with_null = Response::new(Some(null_value.clone()));
476
+ let response_with_none = Response::new(None);
477
+
478
+ assert_eq!(response_with_null.content, Some(null_value));
479
+ assert_eq!(response_with_none.content, None);
480
+ assert_ne!(response_with_null.content, response_with_none.content);
481
+ }
482
+
483
+ #[test]
484
+ fn response_with_json_primitives() {
485
+ let test_cases = vec![
486
+ json!(true),
487
+ json!(false),
488
+ json!(0),
489
+ json!(-1),
490
+ json!(42),
491
+ json!(3.14),
492
+ json!("string"),
493
+ json!(""),
494
+ ];
495
+
496
+ for test_value in test_cases {
497
+ let response = Response::new(Some(test_value.clone()));
498
+ assert_eq!(response.content, Some(test_value));
499
+ }
500
+ }
501
+
502
+ #[test]
503
+ fn response_header_case_sensitivity() {
504
+ let mut response = Response::new(None);
505
+ response.set_header("Content-Type".to_string(), "application/json".to_string());
506
+ response.set_header("content-type".to_string(), "text/plain".to_string());
507
+
508
+ assert_eq!(response.headers.len(), 2);
509
+ assert_eq!(
510
+ response.headers.get("Content-Type"),
511
+ Some(&"application/json".to_string())
512
+ );
513
+ assert_eq!(response.headers.get("content-type"), Some(&"text/plain".to_string()));
514
+ }
515
+
516
+ #[test]
517
+ fn response_header_with_empty_value() {
518
+ let mut response = Response::new(None);
519
+ response.set_header("X-Empty".to_string(), "".to_string());
520
+ assert_eq!(response.headers.get("X-Empty"), Some(&"".to_string()));
521
+ }
522
+
523
+ #[test]
524
+ fn response_header_with_special_chars() {
525
+ let mut response = Response::new(None);
526
+ response.set_header("X-Special".to_string(), "value; charset=utf-8".to_string());
527
+ assert_eq!(
528
+ response.headers.get("X-Special"),
529
+ Some(&"value; charset=utf-8".to_string())
530
+ );
531
+ }
532
+
533
+ #[test]
534
+ fn response_multiple_different_cookies() {
535
+ let mut response = Response::new(None);
536
+ response.set_cookie(
537
+ "session".to_string(),
538
+ "abc123".to_string(),
539
+ None,
540
+ None,
541
+ None,
542
+ false,
543
+ false,
544
+ None,
545
+ );
546
+ let cookie_count = response.headers.iter().filter(|(k, _)| *k == "set-cookie").count();
547
+ assert_eq!(cookie_count, 1);
548
+ }
549
+
550
+ #[test]
551
+ fn response_cookie_empty_value() {
552
+ let mut response = Response::new(None);
553
+ response.set_cookie(
554
+ "empty".to_string(),
555
+ "".to_string(),
556
+ None,
557
+ None,
558
+ None,
559
+ false,
560
+ false,
561
+ None,
562
+ );
563
+ let cookie = response.headers.get("set-cookie").unwrap();
564
+ assert_eq!(cookie, "empty=");
565
+ }
566
+
567
+ #[test]
568
+ fn response_cookie_with_equals_in_value() {
569
+ let mut response = Response::new(None);
570
+ response.set_cookie(
571
+ "data".to_string(),
572
+ "key=value&other=123".to_string(),
573
+ None,
574
+ None,
575
+ None,
576
+ false,
577
+ false,
578
+ None,
579
+ );
580
+ let cookie = response.headers.get("set-cookie").unwrap();
581
+ assert!(cookie.contains("key=value&other=123"));
582
+ }
583
+
584
+ #[test]
585
+ fn response_cookie_attribute_order() {
586
+ let mut response = Response::new(None);
587
+ response.set_cookie(
588
+ "test".to_string(),
589
+ "value".to_string(),
590
+ Some(3600),
591
+ Some("example.com".to_string()),
592
+ Some("/".to_string()),
593
+ true,
594
+ true,
595
+ Some("Strict".to_string()),
596
+ );
597
+ let cookie = response.headers.get("set-cookie").unwrap();
598
+
599
+ let parts: Vec<&str> = cookie.split("; ").collect();
600
+ assert_eq!(parts.len(), 7);
601
+ assert!(parts[0].starts_with("test="));
602
+ assert!(parts[1].starts_with("Max-Age="));
603
+ assert!(parts[2].starts_with("Domain="));
604
+ assert!(parts[3].starts_with("Path="));
605
+ assert_eq!(parts[4], "Secure");
606
+ assert_eq!(parts[5], "HttpOnly");
607
+ assert!(parts[6].starts_with("SameSite="));
608
+ }
609
+
610
+ #[test]
611
+ fn response_cookie_with_very_long_value() {
612
+ let mut response = Response::new(None);
613
+ let long_value = "x".repeat(4096);
614
+ response.set_cookie(
615
+ "long".to_string(),
616
+ long_value.clone(),
617
+ None,
618
+ None,
619
+ None,
620
+ false,
621
+ false,
622
+ None,
623
+ );
624
+ let cookie = response.headers.get("set-cookie").unwrap();
625
+ assert!(cookie.contains(&format!("long={}", long_value)));
626
+ }
627
+
628
+ #[test]
629
+ fn response_cookie_max_age_large_value() {
630
+ let mut response = Response::new(None);
631
+ let max_age_value = 86400 * 365;
632
+ response.set_cookie(
633
+ "session".to_string(),
634
+ "token".to_string(),
635
+ Some(max_age_value),
636
+ None,
637
+ None,
638
+ false,
639
+ false,
640
+ None,
641
+ );
642
+ let cookie = response.headers.get("set-cookie").unwrap();
643
+ assert!(cookie.contains(&format!("Max-Age={}", max_age_value)));
644
+ }
645
+
646
+ #[test]
647
+ fn response_status_code_informational() {
648
+ let response = Response::with_status(None, 100);
649
+ assert_eq!(response.status_code, 100);
650
+ }
651
+
652
+ #[test]
653
+ fn response_status_code_redirect_with_location() {
654
+ let mut response = Response::with_status(None, 301);
655
+ response.set_header("Location".to_string(), "https://example.com/new".to_string());
656
+ assert_eq!(response.status_code, 301);
657
+ assert_eq!(
658
+ response.headers.get("Location"),
659
+ Some(&"https://example.com/new".to_string())
660
+ );
661
+ }
662
+
663
+ #[test]
664
+ fn response_with_error_status_and_content() {
665
+ let error_content = json!({
666
+ "error": "Unauthorized",
667
+ "code": 401,
668
+ "message": "Invalid credentials"
669
+ });
670
+ let response = Response::with_status(Some(error_content.clone()), 401);
671
+ assert_eq!(response.status_code, 401);
672
+ assert_eq!(response.content, Some(error_content));
673
+ }
674
+
675
+ #[test]
676
+ fn response_clone_preserves_state() {
677
+ let mut original = Response::with_status(Some(json!({"key": "value"})), 202);
678
+ original.set_header("X-Custom".to_string(), "header-value".to_string());
679
+ original.set_cookie(
680
+ "session".to_string(),
681
+ "token".to_string(),
682
+ Some(3600),
683
+ None,
684
+ None,
685
+ true,
686
+ false,
687
+ None,
688
+ );
689
+
690
+ let cloned = original.clone();
691
+
692
+ assert_eq!(cloned.status_code, 202);
693
+ assert_eq!(cloned.content, original.content);
694
+ assert_eq!(cloned.headers, original.headers);
695
+ }
696
+
697
+ #[test]
698
+ fn response_with_numeric_status_boundaries() {
699
+ let boundary_codes = vec![1, 99, 100, 199, 200, 299, 300, 399, 400, 499, 500, 599, 600, 999, 65535];
700
+ for code in boundary_codes {
701
+ let response = Response::with_status(None, code);
702
+ assert_eq!(response.status_code, code);
703
+ }
704
+ }
705
+
706
+ #[test]
707
+ fn response_header_unicode_value() {
708
+ let mut response = Response::new(None);
709
+ response.set_header("X-Unicode".to_string(), "こんにちは".to_string());
710
+ assert_eq!(response.headers.get("X-Unicode"), Some(&"こんにちは".to_string()));
711
+ }
712
+
713
+ #[test]
714
+ fn response_debug_trait() {
715
+ let response = Response::with_status(Some(json!({"test": "data"})), 200);
716
+ let debug_str = format!("{:?}", response);
717
+ assert!(debug_str.contains("Response"));
718
+ assert!(debug_str.contains("200"));
719
+ }
399
720
  }