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
@@ -420,3 +420,767 @@ where
420
420
  _marker: std::marker::PhantomData,
421
421
  })
422
422
  }
423
+
424
+ #[cfg(test)]
425
+ mod tests {
426
+ use super::*;
427
+ use tokio_test::block_on;
428
+
429
+ #[test]
430
+ fn test_hook_result_continue_variant() {
431
+ let result: HookResult<i32, String> = HookResult::Continue(42);
432
+ assert!(matches!(result, HookResult::Continue(42)));
433
+ }
434
+
435
+ #[test]
436
+ fn test_hook_result_short_circuit_variant() {
437
+ let result: HookResult<i32, String> = HookResult::ShortCircuit("response".to_string());
438
+ assert!(matches!(result, HookResult::ShortCircuit(ref s) if s == "response"));
439
+ }
440
+
441
+ #[test]
442
+ fn test_hook_result_debug_format() {
443
+ let continue_result: HookResult<i32, String> = HookResult::Continue(100);
444
+ let debug_str = format!("{:?}", continue_result);
445
+ assert!(debug_str.contains("Continue"));
446
+
447
+ let short_circuit_result: HookResult<i32, String> = HookResult::ShortCircuit("err".to_string());
448
+ let debug_str = format!("{:?}", short_circuit_result);
449
+ assert!(debug_str.contains("ShortCircuit"));
450
+ }
451
+
452
+ #[test]
453
+ fn test_lifecycle_hooks_default() {
454
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
455
+ assert!(hooks.is_empty());
456
+ }
457
+
458
+ #[test]
459
+ fn test_lifecycle_hooks_new() {
460
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
461
+ assert!(hooks.is_empty());
462
+ }
463
+
464
+ #[test]
465
+ fn test_lifecycle_hooks_is_empty_true() {
466
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
467
+ assert!(hooks.is_empty());
468
+ }
469
+
470
+ #[test]
471
+ fn test_lifecycle_hooks_debug_format_empty() {
472
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
473
+ let debug_str = format!("{:?}", hooks);
474
+ assert!(debug_str.contains("LifecycleHooks"));
475
+ assert!(debug_str.contains("on_request_count"));
476
+ assert!(debug_str.contains("0"));
477
+ }
478
+
479
+ #[test]
480
+ fn test_lifecycle_hooks_clone() {
481
+ let hooks1: LifecycleHooks<String, String> = LifecycleHooks::default();
482
+ let hooks2 = hooks1.clone();
483
+ assert!(hooks2.is_empty());
484
+ }
485
+
486
+ #[test]
487
+ fn test_lifecycle_hooks_builder_new() {
488
+ let builder: LifecycleHooksBuilder<String, String> = LifecycleHooksBuilder::new();
489
+ let hooks = builder.build();
490
+ assert!(hooks.is_empty());
491
+ }
492
+
493
+ #[test]
494
+ fn test_lifecycle_hooks_builder_default() {
495
+ let builder: LifecycleHooksBuilder<String, String> = LifecycleHooksBuilder::default();
496
+ let hooks = builder.build();
497
+ assert!(hooks.is_empty());
498
+ }
499
+
500
+ #[test]
501
+ fn test_lifecycle_hooks_builder_method() {
502
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::builder().build();
503
+ assert!(hooks.is_empty());
504
+ }
505
+
506
+ #[test]
507
+ fn test_add_on_request_hook() {
508
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
509
+
510
+ #[cfg(not(target_arch = "wasm32"))]
511
+ let hook = Arc::new(TestRequestHook);
512
+ #[cfg(target_arch = "wasm32")]
513
+ let hook = Arc::new(TestRequestHookLocal);
514
+
515
+ hooks.add_on_request(hook);
516
+ assert!(!hooks.is_empty());
517
+ }
518
+
519
+ #[test]
520
+ fn request_hook_errors_if_called_with_response() {
521
+ #[cfg(not(target_arch = "wasm32"))]
522
+ {
523
+ let hook = request_hook::<String, String, _, _>("req", |req| async move { Ok(HookResult::Continue(req)) });
524
+ let err = block_on(async { hook.execute_response("resp".to_string()).await }).unwrap_err();
525
+ assert!(err.contains("Request hook called with response"));
526
+ }
527
+ }
528
+
529
+ #[test]
530
+ fn response_hook_errors_if_called_with_request() {
531
+ #[cfg(not(target_arch = "wasm32"))]
532
+ {
533
+ let hook =
534
+ response_hook::<String, String, _, _>("resp", |resp| async move { Ok(HookResult::Continue(resp)) });
535
+ let err = block_on(async { hook.execute_request("req".to_string()).await }).unwrap_err();
536
+ assert!(err.contains("Response hook called with request"));
537
+ }
538
+ }
539
+
540
+ #[cfg(not(target_arch = "wasm32"))]
541
+ struct TestRequestHook;
542
+
543
+ #[cfg(not(target_arch = "wasm32"))]
544
+ impl NativeLifecycleHook<String, String> for TestRequestHook {
545
+ fn name(&self) -> &str {
546
+ "test_request_hook"
547
+ }
548
+
549
+ fn execute_request<'a>(&'a self, req: String) -> RequestHookFutureSend<'a, String, String> {
550
+ Box::pin(async move { Ok(HookResult::Continue(req + "_modified")) })
551
+ }
552
+
553
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
554
+ Box::pin(async { Err("not implemented".to_string()) })
555
+ }
556
+ }
557
+
558
+ #[cfg(target_arch = "wasm32")]
559
+ struct TestRequestHookLocal;
560
+
561
+ #[cfg(target_arch = "wasm32")]
562
+ impl LocalLifecycleHook<String, String> for TestRequestHookLocal {
563
+ fn name(&self) -> &str {
564
+ "test_request_hook"
565
+ }
566
+
567
+ fn execute_request<'a>(&'a self, req: String) -> RequestHookFutureLocal<'a, String, String> {
568
+ Box::pin(async move { Ok(HookResult::Continue(req + "_modified")) })
569
+ }
570
+
571
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
572
+ Box::pin(async { Err("not implemented".to_string()) })
573
+ }
574
+ }
575
+
576
+ #[test]
577
+ fn test_add_pre_validation_hook() {
578
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
579
+
580
+ #[cfg(not(target_arch = "wasm32"))]
581
+ let hook = Arc::new(TestRequestHook);
582
+ #[cfg(target_arch = "wasm32")]
583
+ let hook = Arc::new(TestRequestHookLocal);
584
+
585
+ hooks.add_pre_validation(hook);
586
+ assert!(!hooks.is_empty());
587
+ }
588
+
589
+ #[test]
590
+ fn test_add_pre_handler_hook() {
591
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
592
+
593
+ #[cfg(not(target_arch = "wasm32"))]
594
+ let hook = Arc::new(TestRequestHook);
595
+ #[cfg(target_arch = "wasm32")]
596
+ let hook = Arc::new(TestRequestHookLocal);
597
+
598
+ hooks.add_pre_handler(hook);
599
+ assert!(!hooks.is_empty());
600
+ }
601
+
602
+ #[cfg(not(target_arch = "wasm32"))]
603
+ struct TestResponseHook;
604
+
605
+ #[cfg(not(target_arch = "wasm32"))]
606
+ impl NativeLifecycleHook<String, String> for TestResponseHook {
607
+ fn name(&self) -> &str {
608
+ "test_response_hook"
609
+ }
610
+
611
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
612
+ Box::pin(async { Err("not implemented".to_string()) })
613
+ }
614
+
615
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
616
+ Box::pin(async move { Ok(HookResult::Continue(resp + "_processed")) })
617
+ }
618
+ }
619
+
620
+ #[cfg(target_arch = "wasm32")]
621
+ struct TestResponseHookLocal;
622
+
623
+ #[cfg(target_arch = "wasm32")]
624
+ impl LocalLifecycleHook<String, String> for TestResponseHookLocal {
625
+ fn name(&self) -> &str {
626
+ "test_response_hook"
627
+ }
628
+
629
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
630
+ Box::pin(async { Err("not implemented".to_string()) })
631
+ }
632
+
633
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
634
+ Box::pin(async move { Ok(HookResult::Continue(resp + "_processed")) })
635
+ }
636
+ }
637
+
638
+ #[test]
639
+ fn test_add_on_response_hook() {
640
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
641
+
642
+ #[cfg(not(target_arch = "wasm32"))]
643
+ let hook = Arc::new(TestResponseHook);
644
+ #[cfg(target_arch = "wasm32")]
645
+ let hook = Arc::new(TestResponseHookLocal);
646
+
647
+ hooks.add_on_response(hook);
648
+ assert!(!hooks.is_empty());
649
+ }
650
+
651
+ #[test]
652
+ fn test_add_on_error_hook() {
653
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
654
+
655
+ #[cfg(not(target_arch = "wasm32"))]
656
+ let hook = Arc::new(TestResponseHook);
657
+ #[cfg(target_arch = "wasm32")]
658
+ let hook = Arc::new(TestResponseHookLocal);
659
+
660
+ hooks.add_on_error(hook);
661
+ assert!(!hooks.is_empty());
662
+ }
663
+
664
+ #[test]
665
+ fn test_execute_on_request_no_hooks() {
666
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
667
+ assert!(hooks.is_empty());
668
+ }
669
+
670
+ #[test]
671
+ fn test_execute_pre_validation_no_hooks() {
672
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
673
+ assert!(hooks.is_empty());
674
+ }
675
+
676
+ #[test]
677
+ fn test_execute_pre_handler_no_hooks() {
678
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
679
+ assert!(hooks.is_empty());
680
+ }
681
+
682
+ #[test]
683
+ fn test_execute_on_response_no_hooks() {
684
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
685
+ assert!(hooks.is_empty());
686
+ }
687
+
688
+ #[test]
689
+ fn test_execute_on_error_no_hooks() {
690
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::default();
691
+ assert!(hooks.is_empty());
692
+ }
693
+
694
+ #[cfg(not(target_arch = "wasm32"))]
695
+ struct TestShortCircuitHook;
696
+
697
+ #[cfg(not(target_arch = "wasm32"))]
698
+ impl NativeLifecycleHook<String, String> for TestShortCircuitHook {
699
+ fn name(&self) -> &str {
700
+ "short_circuit"
701
+ }
702
+
703
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
704
+ Box::pin(async { Ok(HookResult::ShortCircuit("short_circuit_response".to_string())) })
705
+ }
706
+
707
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
708
+ Box::pin(async { Err("not implemented".to_string()) })
709
+ }
710
+ }
711
+
712
+ #[cfg(target_arch = "wasm32")]
713
+ struct TestShortCircuitHookLocal;
714
+
715
+ #[cfg(target_arch = "wasm32")]
716
+ impl LocalLifecycleHook<String, String> for TestShortCircuitHookLocal {
717
+ fn name(&self) -> &str {
718
+ "short_circuit"
719
+ }
720
+
721
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
722
+ Box::pin(async { Ok(HookResult::ShortCircuit("short_circuit_response".to_string())) })
723
+ }
724
+
725
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
726
+ Box::pin(async { Err("not implemented".to_string()) })
727
+ }
728
+ }
729
+
730
+ #[test]
731
+ fn test_on_request_short_circuit() {
732
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
733
+
734
+ #[cfg(not(target_arch = "wasm32"))]
735
+ let hook = Arc::new(TestShortCircuitHook);
736
+ #[cfg(target_arch = "wasm32")]
737
+ let hook = Arc::new(TestShortCircuitHookLocal);
738
+
739
+ hooks.add_on_request(hook);
740
+ assert!(!hooks.is_empty());
741
+ }
742
+
743
+ #[test]
744
+ fn test_pre_validation_short_circuit() {
745
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
746
+
747
+ #[cfg(not(target_arch = "wasm32"))]
748
+ let hook = Arc::new(TestShortCircuitHook);
749
+ #[cfg(target_arch = "wasm32")]
750
+ let hook = Arc::new(TestShortCircuitHookLocal);
751
+
752
+ hooks.add_pre_validation(hook);
753
+ assert!(!hooks.is_empty());
754
+ }
755
+
756
+ #[test]
757
+ fn test_pre_handler_short_circuit() {
758
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
759
+
760
+ #[cfg(not(target_arch = "wasm32"))]
761
+ let hook = Arc::new(TestShortCircuitHook);
762
+ #[cfg(target_arch = "wasm32")]
763
+ let hook = Arc::new(TestShortCircuitHookLocal);
764
+
765
+ hooks.add_pre_handler(hook);
766
+ assert!(!hooks.is_empty());
767
+ }
768
+
769
+ #[cfg(not(target_arch = "wasm32"))]
770
+ struct TestResponseShortCircuitHook;
771
+
772
+ #[cfg(not(target_arch = "wasm32"))]
773
+ impl NativeLifecycleHook<String, String> for TestResponseShortCircuitHook {
774
+ fn name(&self) -> &str {
775
+ "response_short_circuit"
776
+ }
777
+
778
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
779
+ Box::pin(async { Err("not implemented".to_string()) })
780
+ }
781
+
782
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
783
+ Box::pin(async move { Ok(HookResult::ShortCircuit("short_circuit_".to_string() + &resp)) })
784
+ }
785
+ }
786
+
787
+ #[cfg(target_arch = "wasm32")]
788
+ struct TestResponseShortCircuitHookLocal;
789
+
790
+ #[cfg(target_arch = "wasm32")]
791
+ impl LocalLifecycleHook<String, String> for TestResponseShortCircuitHookLocal {
792
+ fn name(&self) -> &str {
793
+ "response_short_circuit"
794
+ }
795
+
796
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
797
+ Box::pin(async { Err("not implemented".to_string()) })
798
+ }
799
+
800
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
801
+ Box::pin(async move { Ok(HookResult::ShortCircuit("short_circuit_".to_string() + &resp)) })
802
+ }
803
+ }
804
+
805
+ #[test]
806
+ fn test_on_response_short_circuit() {
807
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
808
+
809
+ #[cfg(not(target_arch = "wasm32"))]
810
+ let hook = Arc::new(TestResponseShortCircuitHook);
811
+ #[cfg(target_arch = "wasm32")]
812
+ let hook = Arc::new(TestResponseShortCircuitHookLocal);
813
+
814
+ hooks.add_on_response(hook);
815
+ assert!(!hooks.is_empty());
816
+ }
817
+
818
+ #[test]
819
+ fn test_on_error_short_circuit() {
820
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
821
+
822
+ #[cfg(not(target_arch = "wasm32"))]
823
+ let hook = Arc::new(TestResponseShortCircuitHook);
824
+ #[cfg(target_arch = "wasm32")]
825
+ let hook = Arc::new(TestResponseShortCircuitHookLocal);
826
+
827
+ hooks.add_on_error(hook);
828
+ assert!(!hooks.is_empty());
829
+ }
830
+
831
+ #[test]
832
+ fn test_multiple_on_request_hooks_in_sequence() {
833
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
834
+
835
+ #[cfg(not(target_arch = "wasm32"))]
836
+ {
837
+ hooks.add_on_request(Arc::new(TestAppendHook("_first")));
838
+ hooks.add_on_request(Arc::new(TestAppendHook("_second")));
839
+ }
840
+ #[cfg(target_arch = "wasm32")]
841
+ {
842
+ hooks.add_on_request(Arc::new(TestAppendHookLocal("_first")));
843
+ hooks.add_on_request(Arc::new(TestAppendHookLocal("_second")));
844
+ }
845
+
846
+ assert_eq!(hooks.on_request.len(), 2);
847
+ }
848
+
849
+ #[cfg(not(target_arch = "wasm32"))]
850
+ struct TestAppendHook(&'static str);
851
+
852
+ #[cfg(not(target_arch = "wasm32"))]
853
+ impl NativeLifecycleHook<String, String> for TestAppendHook {
854
+ fn name(&self) -> &str {
855
+ "append"
856
+ }
857
+
858
+ fn execute_request<'a>(&'a self, req: String) -> RequestHookFutureSend<'a, String, String> {
859
+ let suffix = self.0;
860
+ Box::pin(async move { Ok(HookResult::Continue(req + suffix)) })
861
+ }
862
+
863
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
864
+ Box::pin(async { Err("not implemented".to_string()) })
865
+ }
866
+ }
867
+
868
+ #[cfg(target_arch = "wasm32")]
869
+ struct TestAppendHookLocal(&'static str);
870
+
871
+ #[cfg(target_arch = "wasm32")]
872
+ impl LocalLifecycleHook<String, String> for TestAppendHookLocal {
873
+ fn name(&self) -> &str {
874
+ "append"
875
+ }
876
+
877
+ fn execute_request<'a>(&'a self, req: String) -> RequestHookFutureLocal<'a, String, String> {
878
+ let suffix = self.0;
879
+ Box::pin(async move { Ok(HookResult::Continue(req + suffix)) })
880
+ }
881
+
882
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
883
+ Box::pin(async { Err("not implemented".to_string()) })
884
+ }
885
+ }
886
+
887
+ #[test]
888
+ fn test_multiple_response_hooks_in_sequence() {
889
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
890
+
891
+ #[cfg(not(target_arch = "wasm32"))]
892
+ {
893
+ hooks.add_on_response(Arc::new(TestAppendResponseHook("_first")));
894
+ hooks.add_on_response(Arc::new(TestAppendResponseHook("_second")));
895
+ }
896
+ #[cfg(target_arch = "wasm32")]
897
+ {
898
+ hooks.add_on_response(Arc::new(TestAppendResponseHookLocal("_first")));
899
+ hooks.add_on_response(Arc::new(TestAppendResponseHookLocal("_second")));
900
+ }
901
+
902
+ assert_eq!(hooks.on_response.len(), 2);
903
+ }
904
+
905
+ #[cfg(not(target_arch = "wasm32"))]
906
+ struct TestAppendResponseHook(&'static str);
907
+
908
+ #[cfg(not(target_arch = "wasm32"))]
909
+ impl NativeLifecycleHook<String, String> for TestAppendResponseHook {
910
+ fn name(&self) -> &str {
911
+ "append_response"
912
+ }
913
+
914
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
915
+ Box::pin(async { Err("not implemented".to_string()) })
916
+ }
917
+
918
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureSend<'a, String> {
919
+ let suffix = self.0;
920
+ Box::pin(async move { Ok(HookResult::Continue(resp + suffix)) })
921
+ }
922
+ }
923
+
924
+ #[cfg(target_arch = "wasm32")]
925
+ struct TestAppendResponseHookLocal(&'static str);
926
+
927
+ #[cfg(target_arch = "wasm32")]
928
+ impl LocalLifecycleHook<String, String> for TestAppendResponseHookLocal {
929
+ fn name(&self) -> &str {
930
+ "append_response"
931
+ }
932
+
933
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
934
+ Box::pin(async { Err("not implemented".to_string()) })
935
+ }
936
+
937
+ fn execute_response<'a>(&'a self, resp: String) -> ResponseHookFutureLocal<'a, String> {
938
+ let suffix = self.0;
939
+ Box::pin(async move { Ok(HookResult::Continue(resp + suffix)) })
940
+ }
941
+ }
942
+
943
+ #[test]
944
+ fn test_builder_chain_multiple_hooks() {
945
+ #[cfg(not(target_arch = "wasm32"))]
946
+ let hooks = LifecycleHooks::builder()
947
+ .on_request(Arc::new(TestRequestHook))
948
+ .pre_validation(Arc::new(TestRequestHook))
949
+ .pre_handler(Arc::new(TestRequestHook))
950
+ .on_response(Arc::new(TestResponseHook))
951
+ .on_error(Arc::new(TestResponseHook))
952
+ .build();
953
+
954
+ #[cfg(target_arch = "wasm32")]
955
+ let hooks = LifecycleHooks::builder()
956
+ .on_request(Arc::new(TestRequestHookLocal))
957
+ .pre_validation(Arc::new(TestRequestHookLocal))
958
+ .pre_handler(Arc::new(TestRequestHookLocal))
959
+ .on_response(Arc::new(TestResponseHookLocal))
960
+ .on_error(Arc::new(TestResponseHookLocal))
961
+ .build();
962
+
963
+ assert!(!hooks.is_empty());
964
+ }
965
+
966
+ #[cfg(not(target_arch = "wasm32"))]
967
+ struct TestErrorHook;
968
+
969
+ #[cfg(not(target_arch = "wasm32"))]
970
+ impl NativeLifecycleHook<String, String> for TestErrorHook {
971
+ fn name(&self) -> &str {
972
+ "error_hook"
973
+ }
974
+
975
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureSend<'a, String, String> {
976
+ Box::pin(async { Err("hook_error".to_string()) })
977
+ }
978
+
979
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureSend<'a, String> {
980
+ Box::pin(async { Err("hook_error".to_string()) })
981
+ }
982
+ }
983
+
984
+ #[cfg(target_arch = "wasm32")]
985
+ struct TestErrorHookLocal;
986
+
987
+ #[cfg(target_arch = "wasm32")]
988
+ impl LocalLifecycleHook<String, String> for TestErrorHookLocal {
989
+ fn name(&self) -> &str {
990
+ "error_hook"
991
+ }
992
+
993
+ fn execute_request<'a>(&'a self, _req: String) -> RequestHookFutureLocal<'a, String, String> {
994
+ Box::pin(async { Err("hook_error".to_string()) })
995
+ }
996
+
997
+ fn execute_response<'a>(&'a self, _resp: String) -> ResponseHookFutureLocal<'a, String> {
998
+ Box::pin(async { Err("hook_error".to_string()) })
999
+ }
1000
+ }
1001
+
1002
+ #[test]
1003
+ fn test_on_request_hook_error_propagates() {
1004
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1005
+
1006
+ #[cfg(not(target_arch = "wasm32"))]
1007
+ let hook = Arc::new(TestErrorHook);
1008
+ #[cfg(target_arch = "wasm32")]
1009
+ let hook = Arc::new(TestErrorHookLocal);
1010
+
1011
+ hooks.add_on_request(hook);
1012
+ assert!(!hooks.is_empty());
1013
+ }
1014
+
1015
+ #[test]
1016
+ fn test_pre_validation_hook_error_propagates() {
1017
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1018
+
1019
+ #[cfg(not(target_arch = "wasm32"))]
1020
+ let hook = Arc::new(TestErrorHook);
1021
+ #[cfg(target_arch = "wasm32")]
1022
+ let hook = Arc::new(TestErrorHookLocal);
1023
+
1024
+ hooks.add_pre_validation(hook);
1025
+ assert!(!hooks.is_empty());
1026
+ }
1027
+
1028
+ #[test]
1029
+ fn test_pre_handler_hook_error_propagates() {
1030
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1031
+
1032
+ #[cfg(not(target_arch = "wasm32"))]
1033
+ let hook = Arc::new(TestErrorHook);
1034
+ #[cfg(target_arch = "wasm32")]
1035
+ let hook = Arc::new(TestErrorHookLocal);
1036
+
1037
+ hooks.add_pre_handler(hook);
1038
+ assert!(!hooks.is_empty());
1039
+ }
1040
+
1041
+ #[test]
1042
+ fn test_on_response_hook_error_propagates() {
1043
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1044
+
1045
+ #[cfg(not(target_arch = "wasm32"))]
1046
+ let hook = Arc::new(TestErrorHook);
1047
+ #[cfg(target_arch = "wasm32")]
1048
+ let hook = Arc::new(TestErrorHookLocal);
1049
+
1050
+ hooks.add_on_response(hook);
1051
+ assert!(!hooks.is_empty());
1052
+ }
1053
+
1054
+ #[test]
1055
+ fn test_on_error_hook_error_propagates() {
1056
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1057
+
1058
+ #[cfg(not(target_arch = "wasm32"))]
1059
+ let hook = Arc::new(TestErrorHook);
1060
+ #[cfg(target_arch = "wasm32")]
1061
+ let hook = Arc::new(TestErrorHookLocal);
1062
+
1063
+ hooks.add_on_error(hook);
1064
+ assert!(!hooks.is_empty());
1065
+ }
1066
+
1067
+ #[test]
1068
+ fn test_debug_format_with_hooks() {
1069
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1070
+
1071
+ #[cfg(not(target_arch = "wasm32"))]
1072
+ hooks.add_on_request(Arc::new(TestRequestHook));
1073
+ #[cfg(target_arch = "wasm32")]
1074
+ hooks.add_on_request(Arc::new(TestRequestHookLocal));
1075
+
1076
+ let debug_str = format!("{:?}", hooks);
1077
+ assert!(debug_str.contains("on_request_count"));
1078
+ assert!(debug_str.contains("1"));
1079
+ }
1080
+
1081
+ #[cfg(not(target_arch = "wasm32"))]
1082
+ #[test]
1083
+ fn test_request_hook_called_with_response_returns_error() {
1084
+ let hook = TestRequestHook;
1085
+ assert_eq!(hook.name(), "test_request_hook");
1086
+ }
1087
+
1088
+ #[cfg(not(target_arch = "wasm32"))]
1089
+ #[test]
1090
+ fn test_response_hook_called_with_request_returns_error() {
1091
+ let hook = TestResponseHook;
1092
+ assert_eq!(hook.name(), "test_response_hook");
1093
+ }
1094
+
1095
+ #[cfg(not(target_arch = "wasm32"))]
1096
+ #[test]
1097
+ fn test_request_hook_name() {
1098
+ let hook = TestRequestHook;
1099
+ assert_eq!(hook.name(), "test_request_hook");
1100
+ }
1101
+
1102
+ #[cfg(not(target_arch = "wasm32"))]
1103
+ #[test]
1104
+ fn test_response_hook_name() {
1105
+ let hook = TestResponseHook;
1106
+ assert_eq!(hook.name(), "test_response_hook");
1107
+ }
1108
+
1109
+ #[test]
1110
+ fn test_first_hook_short_circuits_subsequent_hooks_not_executed() {
1111
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1112
+
1113
+ #[cfg(not(target_arch = "wasm32"))]
1114
+ {
1115
+ hooks.add_on_request(Arc::new(TestShortCircuitHook));
1116
+ hooks.add_on_request(Arc::new(TestRequestHook));
1117
+ }
1118
+ #[cfg(target_arch = "wasm32")]
1119
+ {
1120
+ hooks.add_on_request(Arc::new(TestShortCircuitHookLocal));
1121
+ hooks.add_on_request(Arc::new(TestRequestHookLocal));
1122
+ }
1123
+
1124
+ assert_eq!(hooks.on_request.len(), 2);
1125
+ }
1126
+
1127
+ #[test]
1128
+ fn test_hook_count_accessors() {
1129
+ let hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1130
+ assert_eq!(hooks.on_request.len(), 0);
1131
+ assert_eq!(hooks.pre_validation.len(), 0);
1132
+ assert_eq!(hooks.pre_handler.len(), 0);
1133
+ assert_eq!(hooks.on_response.len(), 0);
1134
+ assert_eq!(hooks.on_error.len(), 0);
1135
+ }
1136
+
1137
+ #[test]
1138
+ fn test_lifecycle_hooks_clone_with_hooks() {
1139
+ let mut hooks1: LifecycleHooks<String, String> = LifecycleHooks::new();
1140
+
1141
+ #[cfg(not(target_arch = "wasm32"))]
1142
+ hooks1.add_on_request(Arc::new(TestRequestHook));
1143
+ #[cfg(target_arch = "wasm32")]
1144
+ hooks1.add_on_request(Arc::new(TestRequestHookLocal));
1145
+
1146
+ let hooks2 = hooks1.clone();
1147
+ assert_eq!(hooks1.on_request.len(), hooks2.on_request.len());
1148
+ assert!(!hooks2.is_empty());
1149
+ }
1150
+
1151
+ #[test]
1152
+ fn test_builder_as_default() {
1153
+ let builder = LifecycleHooksBuilder::<String, String>::default();
1154
+ let hooks = builder.build();
1155
+ assert!(hooks.is_empty());
1156
+ }
1157
+
1158
+ #[test]
1159
+ fn test_is_empty_comprehensive() {
1160
+ let mut hooks: LifecycleHooks<String, String> = LifecycleHooks::new();
1161
+ assert!(hooks.is_empty());
1162
+
1163
+ #[cfg(not(target_arch = "wasm32"))]
1164
+ hooks.add_on_request(Arc::new(TestRequestHook));
1165
+ #[cfg(target_arch = "wasm32")]
1166
+ hooks.add_on_request(Arc::new(TestRequestHookLocal));
1167
+
1168
+ assert!(!hooks.is_empty());
1169
+ }
1170
+
1171
+ #[test]
1172
+ fn test_hook_result_enum_value() {
1173
+ let val1: HookResult<String, String> = HookResult::Continue(String::from("test"));
1174
+ let val2: HookResult<String, String> = HookResult::ShortCircuit(String::from("response"));
1175
+
1176
+ match val1 {
1177
+ HookResult::Continue(s) => assert_eq!(s, "test"),
1178
+ HookResult::ShortCircuit(_) => panic!("Wrong variant"),
1179
+ }
1180
+
1181
+ match val2 {
1182
+ HookResult::Continue(_) => panic!("Wrong variant"),
1183
+ HookResult::ShortCircuit(s) => assert_eq!(s, "response"),
1184
+ }
1185
+ }
1186
+ }