spikard 0.6.2 → 0.7.1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -508
  3. data/ext/spikard_rb/Cargo.lock +3287 -0
  4. data/ext/spikard_rb/Cargo.toml +1 -1
  5. data/ext/spikard_rb/extconf.rb +3 -3
  6. data/lib/spikard/app.rb +72 -49
  7. data/lib/spikard/background.rb +38 -7
  8. data/lib/spikard/testing.rb +42 -4
  9. data/lib/spikard/version.rb +1 -1
  10. data/sig/spikard.rbs +4 -0
  11. data/vendor/crates/spikard-bindings-shared/Cargo.toml +1 -1
  12. data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +191 -0
  13. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  14. data/vendor/crates/spikard-core/src/http.rs +1 -0
  15. data/vendor/crates/spikard-core/src/lifecycle.rs +63 -0
  16. data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +136 -0
  17. data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +37 -0
  18. data/vendor/crates/spikard-core/tests/error_mapper.rs +761 -0
  19. data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +106 -0
  20. data/vendor/crates/spikard-core/tests/parameters_full.rs +701 -0
  21. data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +301 -0
  22. data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +67 -0
  23. data/vendor/crates/spikard-core/tests/validation_coverage.rs +250 -0
  24. data/vendor/crates/spikard-core/tests/validation_error_paths.rs +45 -0
  25. data/vendor/crates/spikard-http/Cargo.toml +1 -1
  26. data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +502 -0
  27. data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +648 -0
  28. data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +58 -0
  29. data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +1207 -0
  30. data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2262 -0
  31. data/vendor/crates/spikard-http/src/testing/test_client.rs +155 -2
  32. data/vendor/crates/spikard-http/src/testing.rs +171 -0
  33. data/vendor/crates/spikard-http/src/websocket.rs +79 -6
  34. data/vendor/crates/spikard-http/tests/auth_integration.rs +647 -0
  35. data/vendor/crates/spikard-http/tests/common/test_builders.rs +633 -0
  36. data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +162 -0
  37. data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +389 -0
  38. data/vendor/crates/spikard-http/tests/request_extraction_full.rs +513 -0
  39. data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +244 -0
  40. data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +200 -0
  41. data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +82 -0
  42. data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +464 -0
  43. data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +286 -0
  44. data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +118 -0
  45. data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +99 -0
  46. data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +206 -0
  47. data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +281 -0
  48. data/vendor/crates/spikard-http/tests/server_router_behavior.rs +121 -0
  49. data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +584 -0
  50. data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +130 -0
  51. data/vendor/crates/spikard-http/tests/test_client_requests.rs +167 -0
  52. data/vendor/crates/spikard-http/tests/testing_helpers.rs +87 -0
  53. data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +156 -0
  54. data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +82 -0
  55. data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +440 -0
  56. data/vendor/crates/spikard-http/tests/websocket_integration.rs +152 -0
  57. data/vendor/crates/spikard-rb/Cargo.toml +1 -1
  58. data/vendor/crates/spikard-rb/src/gvl.rs +80 -0
  59. data/vendor/crates/spikard-rb/src/handler.rs +12 -9
  60. data/vendor/crates/spikard-rb/src/lib.rs +137 -124
  61. data/vendor/crates/spikard-rb/src/request.rs +342 -0
  62. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +1 -8
  63. data/vendor/crates/spikard-rb/src/server.rs +1 -8
  64. data/vendor/crates/spikard-rb/src/testing/client.rs +168 -9
  65. data/vendor/crates/spikard-rb/src/websocket.rs +119 -30
  66. data/vendor/crates/spikard-rb-macros/Cargo.toml +14 -0
  67. data/vendor/crates/spikard-rb-macros/src/lib.rs +52 -0
  68. metadata +44 -1
@@ -0,0 +1,136 @@
1
+ use brotli::Decompressor;
2
+ use flate2::read::GzDecoder;
3
+ use spikard_core::{CompressionConfig, RawResponse, StaticAsset};
4
+ use std::collections::HashMap;
5
+ use std::io::Read;
6
+
7
+ fn decode_gzip(payload: &[u8]) -> Vec<u8> {
8
+ let mut decoder = GzDecoder::new(payload);
9
+ let mut buf = Vec::new();
10
+ decoder.read_to_end(&mut buf).expect("gzip decode failed");
11
+ buf
12
+ }
13
+
14
+ fn decode_brotli(payload: &[u8]) -> Vec<u8> {
15
+ let mut decoder = Decompressor::new(payload, 4096);
16
+ let mut buf = Vec::new();
17
+ decoder.read_to_end(&mut buf).expect("brotli decode failed");
18
+ buf
19
+ }
20
+
21
+ #[test]
22
+ fn raw_response_does_not_compress_empty_or_partial_content() {
23
+ let mut headers = HashMap::new();
24
+ headers.insert("content-type".to_string(), "application/json".to_string());
25
+
26
+ let mut empty = RawResponse::new(200, headers.clone(), Vec::new());
27
+ let cfg = CompressionConfig {
28
+ min_size: 1,
29
+ ..Default::default()
30
+ };
31
+ empty.apply_compression(&HashMap::new(), &cfg);
32
+ assert!(!empty.headers.contains_key("content-encoding"));
33
+ assert!(empty.body.is_empty());
34
+
35
+ let mut partial = RawResponse::new(206, headers, b"{\"ok\":true}".to_vec());
36
+ partial.apply_compression(&HashMap::new(), &cfg);
37
+ assert!(!partial.headers.contains_key("content-encoding"));
38
+ }
39
+
40
+ #[test]
41
+ fn raw_response_respects_existing_content_encoding_and_min_size() {
42
+ let mut headers = HashMap::new();
43
+ headers.insert("content-encoding".to_string(), "gzip".to_string());
44
+
45
+ let mut response = RawResponse::new(200, headers, b"x".repeat(4096));
46
+ let cfg = CompressionConfig::default();
47
+ response.apply_compression(&HashMap::new(), &cfg);
48
+ assert_eq!(
49
+ response.headers.get("content-encoding").map(String::as_str),
50
+ Some("gzip")
51
+ );
52
+
53
+ let mut small = RawResponse::new(200, HashMap::new(), b"x".repeat(10));
54
+ let cfg = CompressionConfig {
55
+ min_size: 1024,
56
+ ..Default::default()
57
+ };
58
+ small.apply_compression(&HashMap::new(), &cfg);
59
+ assert!(!small.headers.contains_key("content-encoding"));
60
+ }
61
+
62
+ #[test]
63
+ fn raw_response_prefers_brotli_when_accepted() {
64
+ let original = b"hello world ".repeat(256);
65
+ let mut response = RawResponse::new(200, HashMap::new(), original.clone());
66
+
67
+ let mut request_headers = HashMap::new();
68
+ request_headers.insert("Accept-Encoding".to_string(), "br, gzip".to_string());
69
+
70
+ let cfg = CompressionConfig {
71
+ min_size: 1,
72
+ quality: 6,
73
+ gzip: true,
74
+ brotli: true,
75
+ };
76
+
77
+ response.apply_compression(&request_headers, &cfg);
78
+
79
+ assert_eq!(response.headers.get("content-encoding").map(String::as_str), Some("br"));
80
+ assert_eq!(
81
+ response.headers.get("vary").map(String::as_str),
82
+ Some("Accept-Encoding")
83
+ );
84
+ assert_eq!(decode_brotli(&response.body), original);
85
+ }
86
+
87
+ #[test]
88
+ fn raw_response_falls_back_to_gzip() {
89
+ let original = b"hello world ".repeat(256);
90
+ let mut response = RawResponse::new(200, HashMap::new(), original.clone());
91
+
92
+ let mut request_headers = HashMap::new();
93
+ request_headers.insert("Accept-Encoding".to_string(), "gzip".to_string());
94
+
95
+ let cfg = CompressionConfig {
96
+ min_size: 1,
97
+ quality: 6,
98
+ gzip: true,
99
+ brotli: true,
100
+ };
101
+
102
+ response.apply_compression(&request_headers, &cfg);
103
+
104
+ assert_eq!(
105
+ response.headers.get("content-encoding").map(String::as_str),
106
+ Some("gzip")
107
+ );
108
+ assert_eq!(decode_gzip(&response.body), original);
109
+ }
110
+
111
+ #[test]
112
+ fn static_asset_serves_get_and_head_only() {
113
+ let mut headers = HashMap::new();
114
+ headers.insert("content-type".to_string(), "text/plain".to_string());
115
+
116
+ let asset = StaticAsset {
117
+ route: "/static/hello.txt".to_string(),
118
+ headers,
119
+ body: b"hello".to_vec(),
120
+ };
121
+
122
+ let get = asset.serve("GET", "/static/hello.txt").expect("expected GET response");
123
+ assert_eq!(get.status, 200);
124
+ assert_eq!(get.body, b"hello");
125
+ assert_eq!(get.headers.get("content-length").map(String::as_str), Some("5"));
126
+
127
+ let head = asset
128
+ .serve("HEAD", "/static/hello.txt")
129
+ .expect("expected HEAD response");
130
+ assert_eq!(head.status, 200);
131
+ assert!(head.body.is_empty());
132
+ assert_eq!(head.headers.get("content-length").map(String::as_str), Some("5"));
133
+
134
+ assert!(asset.serve("POST", "/static/hello.txt").is_none());
135
+ assert!(asset.serve("GET", "/static/other.txt").is_none());
136
+ }
@@ -0,0 +1,37 @@
1
+ #![cfg(feature = "di")]
2
+
3
+ use http::Request;
4
+ use spikard_core::RequestData;
5
+ use spikard_core::di::{Dependency, DependencyError, ResolvedDependencies};
6
+ use std::any::Any;
7
+ use std::sync::Arc;
8
+
9
+ struct DummyDependency;
10
+
11
+ impl Dependency for DummyDependency {
12
+ fn resolve(
13
+ &self,
14
+ _request: &Request<()>,
15
+ _request_data: &RequestData,
16
+ _resolved: &ResolvedDependencies,
17
+ ) -> std::pin::Pin<
18
+ Box<dyn std::future::Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>,
19
+ > {
20
+ Box::pin(async move { Ok(Arc::new(()) as Arc<dyn Any + Send + Sync>) })
21
+ }
22
+
23
+ fn key(&self) -> &str {
24
+ "dummy"
25
+ }
26
+
27
+ fn depends_on(&self) -> Vec<String> {
28
+ Vec::new()
29
+ }
30
+ }
31
+
32
+ #[test]
33
+ fn dependency_defaults_are_false() {
34
+ let dep = DummyDependency;
35
+ assert!(!dep.cacheable());
36
+ assert!(!dep.singleton());
37
+ }