spikard 0.2.5 → 0.3.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/ext/spikard_rb/Cargo.toml +3 -3
  4. data/lib/spikard/app.rb +61 -49
  5. data/lib/spikard/converters.rb +3 -75
  6. data/lib/spikard/handler_wrapper.rb +6 -9
  7. data/lib/spikard/provide.rb +14 -28
  8. data/lib/spikard/response.rb +75 -11
  9. data/lib/spikard/streaming_response.rb +24 -1
  10. data/lib/spikard/testing.rb +1 -1
  11. data/lib/spikard/version.rb +1 -1
  12. data/sig/spikard.rbs +14 -3
  13. data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
  14. metadata +3 -80
  15. data/vendor/crates/spikard-core/Cargo.toml +0 -40
  16. data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
  17. data/vendor/crates/spikard-core/src/bindings/response.rs +0 -133
  18. data/vendor/crates/spikard-core/src/debug.rs +0 -63
  19. data/vendor/crates/spikard-core/src/di/container.rs +0 -726
  20. data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
  21. data/vendor/crates/spikard-core/src/di/error.rs +0 -118
  22. data/vendor/crates/spikard-core/src/di/factory.rs +0 -538
  23. data/vendor/crates/spikard-core/src/di/graph.rs +0 -545
  24. data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
  25. data/vendor/crates/spikard-core/src/di/resolved.rs +0 -411
  26. data/vendor/crates/spikard-core/src/di/value.rs +0 -283
  27. data/vendor/crates/spikard-core/src/http.rs +0 -153
  28. data/vendor/crates/spikard-core/src/lib.rs +0 -28
  29. data/vendor/crates/spikard-core/src/lifecycle.rs +0 -422
  30. data/vendor/crates/spikard-core/src/parameters.rs +0 -719
  31. data/vendor/crates/spikard-core/src/problem.rs +0 -310
  32. data/vendor/crates/spikard-core/src/request_data.rs +0 -189
  33. data/vendor/crates/spikard-core/src/router.rs +0 -249
  34. data/vendor/crates/spikard-core/src/schema_registry.rs +0 -183
  35. data/vendor/crates/spikard-core/src/type_hints.rs +0 -304
  36. data/vendor/crates/spikard-core/src/validation.rs +0 -699
  37. data/vendor/crates/spikard-http/Cargo.toml +0 -58
  38. data/vendor/crates/spikard-http/src/auth.rs +0 -247
  39. data/vendor/crates/spikard-http/src/background.rs +0 -249
  40. data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
  41. data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
  42. data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
  43. data/vendor/crates/spikard-http/src/cors.rs +0 -490
  44. data/vendor/crates/spikard-http/src/debug.rs +0 -63
  45. data/vendor/crates/spikard-http/src/di_handler.rs +0 -423
  46. data/vendor/crates/spikard-http/src/handler_response.rs +0 -190
  47. data/vendor/crates/spikard-http/src/handler_trait.rs +0 -228
  48. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -284
  49. data/vendor/crates/spikard-http/src/lib.rs +0 -529
  50. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -149
  51. data/vendor/crates/spikard-http/src/lifecycle.rs +0 -428
  52. data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -285
  53. data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -86
  54. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -147
  55. data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -287
  56. data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
  57. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -190
  58. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -308
  59. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -195
  60. data/vendor/crates/spikard-http/src/parameters.rs +0 -1
  61. data/vendor/crates/spikard-http/src/problem.rs +0 -1
  62. data/vendor/crates/spikard-http/src/query_parser.rs +0 -369
  63. data/vendor/crates/spikard-http/src/response.rs +0 -399
  64. data/vendor/crates/spikard-http/src/router.rs +0 -1
  65. data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
  66. data/vendor/crates/spikard-http/src/server/handler.rs +0 -80
  67. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -98
  68. data/vendor/crates/spikard-http/src/server/mod.rs +0 -805
  69. data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -119
  70. data/vendor/crates/spikard-http/src/sse.rs +0 -447
  71. data/vendor/crates/spikard-http/src/testing/form.rs +0 -14
  72. data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -60
  73. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -285
  74. data/vendor/crates/spikard-http/src/testing.rs +0 -377
  75. data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
  76. data/vendor/crates/spikard-http/src/validation.rs +0 -1
  77. data/vendor/crates/spikard-http/src/websocket.rs +0 -324
  78. data/vendor/crates/spikard-rb/Cargo.toml +0 -42
  79. data/vendor/crates/spikard-rb/build.rs +0 -8
  80. data/vendor/crates/spikard-rb/src/background.rs +0 -63
  81. data/vendor/crates/spikard-rb/src/config.rs +0 -294
  82. data/vendor/crates/spikard-rb/src/conversion.rs +0 -392
  83. data/vendor/crates/spikard-rb/src/di.rs +0 -409
  84. data/vendor/crates/spikard-rb/src/handler.rs +0 -534
  85. data/vendor/crates/spikard-rb/src/lib.rs +0 -2020
  86. data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -267
  87. data/vendor/crates/spikard-rb/src/server.rs +0 -283
  88. data/vendor/crates/spikard-rb/src/sse.rs +0 -231
  89. data/vendor/crates/spikard-rb/src/test_client.rs +0 -404
  90. data/vendor/crates/spikard-rb/src/test_sse.rs +0 -143
  91. data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
  92. data/vendor/crates/spikard-rb/src/websocket.rs +0 -233
@@ -1,428 +0,0 @@
1
- use axum::{
2
- body::Body,
3
- http::{Request, Response},
4
- };
5
- use std::sync::Arc;
6
-
7
- pub mod adapter;
8
-
9
- pub use spikard_core::lifecycle::{HookResult, LifecycleHook};
10
-
11
- pub type LifecycleHooks = spikard_core::lifecycle::LifecycleHooks<Request<Body>, Response<Body>>;
12
- pub type LifecycleHooksBuilder = spikard_core::lifecycle::LifecycleHooksBuilder<Request<Body>, Response<Body>>;
13
-
14
- /// Create a request hook for the current target.
15
- #[cfg(not(target_arch = "wasm32"))]
16
- pub fn request_hook<F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>
17
- where
18
- F: Fn(Request<Body>) -> Fut + Send + Sync + 'static,
19
- Fut: std::future::Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'static,
20
- {
21
- spikard_core::lifecycle::request_hook::<Request<Body>, Response<Body>, _, _>(name, func)
22
- }
23
-
24
- /// Create a request hook for wasm targets (no Send on futures).
25
- #[cfg(target_arch = "wasm32")]
26
- pub fn request_hook<F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>
27
- where
28
- F: Fn(Request<Body>) -> Fut + Send + Sync + 'static,
29
- Fut: std::future::Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + 'static,
30
- {
31
- spikard_core::lifecycle::request_hook::<Request<Body>, Response<Body>, _, _>(name, func)
32
- }
33
-
34
- /// Create a response hook for the current target.
35
- #[cfg(not(target_arch = "wasm32"))]
36
- pub fn response_hook<F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>
37
- where
38
- F: Fn(Response<Body>) -> Fut + Send + Sync + 'static,
39
- Fut: std::future::Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'static,
40
- {
41
- spikard_core::lifecycle::response_hook::<Request<Body>, Response<Body>, _, _>(name, func)
42
- }
43
-
44
- /// Create a response hook for wasm targets (no Send on futures).
45
- #[cfg(target_arch = "wasm32")]
46
- pub fn response_hook<F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Request<Body>, Response<Body>>>
47
- where
48
- F: Fn(Response<Body>) -> Fut + Send + Sync + 'static,
49
- Fut: std::future::Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + 'static,
50
- {
51
- spikard_core::lifecycle::response_hook::<Request<Body>, Response<Body>, _, _>(name, func)
52
- }
53
-
54
- #[cfg(test)]
55
- mod tests {
56
- use super::*;
57
- use axum::body::Body;
58
- use axum::http::{Request, Response, StatusCode};
59
- use std::future::Future;
60
- use std::pin::Pin;
61
-
62
- /// Test hook that always continues
63
- struct ContinueHook {
64
- name: String,
65
- }
66
-
67
- impl LifecycleHook<Request<Body>, Response<Body>> for ContinueHook {
68
- fn name(&self) -> &str {
69
- &self.name
70
- }
71
-
72
- fn execute_request<'a>(
73
- &'a self,
74
- req: Request<Body>,
75
- ) -> Pin<Box<dyn Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'a>>
76
- {
77
- Box::pin(async move { Ok(HookResult::Continue(req)) })
78
- }
79
-
80
- fn execute_response<'a>(
81
- &'a self,
82
- resp: Response<Body>,
83
- ) -> Pin<Box<dyn Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'a>>
84
- {
85
- Box::pin(async move { Ok(HookResult::Continue(resp)) })
86
- }
87
- }
88
-
89
- /// Test hook that short-circuits with a 401 response
90
- struct ShortCircuitHook {
91
- name: String,
92
- }
93
-
94
- impl LifecycleHook<Request<Body>, Response<Body>> for ShortCircuitHook {
95
- fn name(&self) -> &str {
96
- &self.name
97
- }
98
-
99
- fn execute_request<'a>(
100
- &'a self,
101
- _req: Request<Body>,
102
- ) -> Pin<Box<dyn Future<Output = Result<HookResult<Request<Body>, Response<Body>>, String>> + Send + 'a>>
103
- {
104
- Box::pin(async move {
105
- let response = Response::builder()
106
- .status(StatusCode::UNAUTHORIZED)
107
- .body(Body::from("Unauthorized"))
108
- .unwrap();
109
- Ok(HookResult::ShortCircuit(response))
110
- })
111
- }
112
-
113
- fn execute_response<'a>(
114
- &'a self,
115
- _resp: Response<Body>,
116
- ) -> Pin<Box<dyn Future<Output = Result<HookResult<Response<Body>, Response<Body>>, String>> + Send + 'a>>
117
- {
118
- Box::pin(async move {
119
- let response = Response::builder()
120
- .status(StatusCode::UNAUTHORIZED)
121
- .body(Body::from("Unauthorized"))
122
- .unwrap();
123
- Ok(HookResult::ShortCircuit(response))
124
- })
125
- }
126
- }
127
-
128
- #[tokio::test]
129
- async fn test_empty_hooks_fast_path() {
130
- let hooks = LifecycleHooks::new();
131
- assert!(hooks.is_empty());
132
-
133
- let req = Request::builder().body(Body::empty()).unwrap();
134
- let result = hooks.execute_on_request(req).await.unwrap();
135
- assert!(matches!(result, HookResult::Continue(_)));
136
- }
137
-
138
- #[tokio::test]
139
- async fn test_on_request_continue() {
140
- let mut hooks = LifecycleHooks::new();
141
- hooks.add_on_request(Arc::new(ContinueHook {
142
- name: "test".to_string(),
143
- }));
144
-
145
- let req = Request::builder().body(Body::empty()).unwrap();
146
- let result = hooks.execute_on_request(req).await.unwrap();
147
- assert!(matches!(result, HookResult::Continue(_)));
148
- }
149
-
150
- #[tokio::test]
151
- async fn test_on_request_short_circuit() {
152
- let mut hooks = LifecycleHooks::new();
153
- hooks.add_on_request(Arc::new(ShortCircuitHook {
154
- name: "auth_check".to_string(),
155
- }));
156
-
157
- let req = Request::builder().body(Body::empty()).unwrap();
158
- let result = hooks.execute_on_request(req).await.unwrap();
159
-
160
- match result {
161
- HookResult::ShortCircuit(resp) => {
162
- assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
163
- }
164
- HookResult::Continue(_) => panic!("Expected ShortCircuit, got Continue"),
165
- }
166
- }
167
-
168
- #[tokio::test]
169
- async fn test_multiple_hooks_in_order() {
170
- let mut hooks = LifecycleHooks::new();
171
-
172
- hooks.add_on_request(Arc::new(ContinueHook {
173
- name: "first".to_string(),
174
- }));
175
- hooks.add_on_request(Arc::new(ContinueHook {
176
- name: "second".to_string(),
177
- }));
178
-
179
- let req = Request::builder().body(Body::empty()).unwrap();
180
- let result = hooks.execute_on_request(req).await.unwrap();
181
- assert!(matches!(result, HookResult::Continue(_)));
182
- }
183
-
184
- #[tokio::test]
185
- async fn test_short_circuit_stops_execution() {
186
- let mut hooks = LifecycleHooks::new();
187
-
188
- hooks.add_on_request(Arc::new(ShortCircuitHook {
189
- name: "short_circuit".to_string(),
190
- }));
191
- hooks.add_on_request(Arc::new(ContinueHook {
192
- name: "never_executed".to_string(),
193
- }));
194
-
195
- let req = Request::builder().body(Body::empty()).unwrap();
196
- let result = hooks.execute_on_request(req).await.unwrap();
197
-
198
- match result {
199
- HookResult::ShortCircuit(_) => {}
200
- HookResult::Continue(_) => panic!("Expected ShortCircuit, got Continue"),
201
- }
202
- }
203
-
204
- #[tokio::test]
205
- async fn test_on_response_hooks() {
206
- let mut hooks = LifecycleHooks::new();
207
- hooks.add_on_response(Arc::new(ContinueHook {
208
- name: "response_hook".to_string(),
209
- }));
210
-
211
- let resp = Response::builder().status(StatusCode::OK).body(Body::empty()).unwrap();
212
-
213
- let result = hooks.execute_on_response(resp).await.unwrap();
214
- assert_eq!(result.status(), StatusCode::OK);
215
- }
216
-
217
- #[tokio::test]
218
- async fn test_request_hook_builder() {
219
- let hook = request_hook("test", |req| async move { Ok(HookResult::Continue(req)) });
220
-
221
- let req = Request::builder().body(Body::empty()).unwrap();
222
- let result = hook.execute_request(req).await.unwrap();
223
-
224
- assert!(matches!(result, HookResult::Continue(_)));
225
- }
226
-
227
- #[tokio::test]
228
- async fn test_request_hook_with_modification() {
229
- let hook = request_hook("add_header", |mut req| async move {
230
- req.headers_mut()
231
- .insert("X-Custom-Header", axum::http::HeaderValue::from_static("test-value"));
232
- Ok(HookResult::Continue(req))
233
- });
234
-
235
- let req = Request::builder().body(Body::empty()).unwrap();
236
- let result = hook.execute_request(req).await.unwrap();
237
-
238
- match result {
239
- HookResult::Continue(req) => {
240
- assert_eq!(req.headers().get("X-Custom-Header").unwrap(), "test-value");
241
- }
242
- HookResult::ShortCircuit(_) => panic!("Expected Continue"),
243
- }
244
- }
245
-
246
- #[tokio::test]
247
- async fn test_request_hook_short_circuit() {
248
- let hook = request_hook("auth", |_req| async move {
249
- let response = Response::builder()
250
- .status(StatusCode::UNAUTHORIZED)
251
- .body(Body::from("Unauthorized"))
252
- .unwrap();
253
- Ok(HookResult::ShortCircuit(response))
254
- });
255
-
256
- let req = Request::builder().body(Body::empty()).unwrap();
257
- let result = hook.execute_request(req).await.unwrap();
258
-
259
- match result {
260
- HookResult::ShortCircuit(resp) => {
261
- assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
262
- }
263
- HookResult::Continue(_) => panic!("Expected ShortCircuit"),
264
- }
265
- }
266
-
267
- #[tokio::test]
268
- async fn test_response_hook_builder() {
269
- let hook = response_hook("security", |mut resp| async move {
270
- resp.headers_mut()
271
- .insert("X-Frame-Options", axum::http::HeaderValue::from_static("DENY"));
272
- Ok(HookResult::Continue(resp))
273
- });
274
-
275
- let resp = Response::builder().status(StatusCode::OK).body(Body::empty()).unwrap();
276
-
277
- let result = hook.execute_response(resp).await.unwrap();
278
-
279
- match result {
280
- HookResult::Continue(resp) => {
281
- assert_eq!(resp.headers().get("X-Frame-Options").unwrap(), "DENY");
282
- assert_eq!(resp.status(), StatusCode::OK);
283
- }
284
- HookResult::ShortCircuit(_) => panic!("Expected Continue"),
285
- }
286
- }
287
-
288
- #[tokio::test]
289
- async fn test_builder_pattern() {
290
- let hooks = LifecycleHooks::builder()
291
- .on_request(request_hook(
292
- "logger",
293
- |req| async move { Ok(HookResult::Continue(req)) },
294
- ))
295
- .pre_handler(request_hook("auth", |req| async move { Ok(HookResult::Continue(req)) }))
296
- .on_response(response_hook("security", |resp| async move {
297
- Ok(HookResult::Continue(resp))
298
- }))
299
- .build();
300
-
301
- assert!(!hooks.is_empty());
302
-
303
- let req = Request::builder().body(Body::empty()).unwrap();
304
- let result = hooks.execute_on_request(req).await.unwrap();
305
- assert!(matches!(result, HookResult::Continue(_)));
306
- }
307
-
308
- #[tokio::test]
309
- async fn test_builder_with_multiple_hooks() {
310
- let hooks = LifecycleHooks::builder()
311
- .on_request(request_hook("first", |mut req| async move {
312
- req.headers_mut()
313
- .insert("X-First", axum::http::HeaderValue::from_static("1"));
314
- Ok(HookResult::Continue(req))
315
- }))
316
- .on_request(request_hook("second", |mut req| async move {
317
- req.headers_mut()
318
- .insert("X-Second", axum::http::HeaderValue::from_static("2"));
319
- Ok(HookResult::Continue(req))
320
- }))
321
- .build();
322
-
323
- let req = Request::builder().body(Body::empty()).unwrap();
324
- let result = hooks.execute_on_request(req).await.unwrap();
325
-
326
- match result {
327
- HookResult::Continue(req) => {
328
- assert_eq!(req.headers().get("X-First").unwrap(), "1");
329
- assert_eq!(req.headers().get("X-Second").unwrap(), "2");
330
- }
331
- HookResult::ShortCircuit(_) => panic!("Expected Continue"),
332
- }
333
- }
334
-
335
- #[tokio::test]
336
- async fn test_builder_short_circuit_stops_chain() {
337
- let hooks = LifecycleHooks::builder()
338
- .on_request(request_hook(
339
- "first",
340
- |req| async move { Ok(HookResult::Continue(req)) },
341
- ))
342
- .on_request(request_hook("short_circuit", |_req| async move {
343
- let response = Response::builder()
344
- .status(StatusCode::FORBIDDEN)
345
- .body(Body::from("Blocked"))
346
- .unwrap();
347
- Ok(HookResult::ShortCircuit(response))
348
- }))
349
- .on_request(request_hook("never_called", |mut req| async move {
350
- req.headers_mut()
351
- .insert("X-Should-Not-Exist", axum::http::HeaderValue::from_static("value"));
352
- Ok(HookResult::Continue(req))
353
- }))
354
- .build();
355
-
356
- let req = Request::builder().body(Body::empty()).unwrap();
357
- let result = hooks.execute_on_request(req).await.unwrap();
358
-
359
- match result {
360
- HookResult::ShortCircuit(resp) => {
361
- assert_eq!(resp.status(), StatusCode::FORBIDDEN);
362
- }
363
- HookResult::Continue(_) => panic!("Expected ShortCircuit"),
364
- }
365
- }
366
-
367
- #[tokio::test]
368
- async fn test_all_hook_types() {
369
- let hooks = LifecycleHooks::builder()
370
- .on_request(request_hook("on_request", |req| async move {
371
- Ok(HookResult::Continue(req))
372
- }))
373
- .pre_validation(request_hook("pre_validation", |req| async move {
374
- Ok(HookResult::Continue(req))
375
- }))
376
- .pre_handler(request_hook("pre_handler", |req| async move {
377
- Ok(HookResult::Continue(req))
378
- }))
379
- .on_response(response_hook("on_response", |resp| async move {
380
- Ok(HookResult::Continue(resp))
381
- }))
382
- .on_error(response_hook("on_error", |resp| async move {
383
- Ok(HookResult::Continue(resp))
384
- }))
385
- .build();
386
-
387
- assert!(!hooks.is_empty());
388
-
389
- let req = Request::builder().body(Body::empty()).unwrap();
390
- assert!(matches!(
391
- hooks.execute_on_request(req).await.unwrap(),
392
- HookResult::Continue(_)
393
- ));
394
-
395
- let req = Request::builder().body(Body::empty()).unwrap();
396
- assert!(matches!(
397
- hooks.execute_pre_validation(req).await.unwrap(),
398
- HookResult::Continue(_)
399
- ));
400
-
401
- let req = Request::builder().body(Body::empty()).unwrap();
402
- assert!(matches!(
403
- hooks.execute_pre_handler(req).await.unwrap(),
404
- HookResult::Continue(_)
405
- ));
406
-
407
- let resp = Response::builder().status(StatusCode::OK).body(Body::empty()).unwrap();
408
- let result = hooks.execute_on_response(resp).await.unwrap();
409
- assert_eq!(result.status(), StatusCode::OK);
410
-
411
- let resp = Response::builder()
412
- .status(StatusCode::INTERNAL_SERVER_ERROR)
413
- .body(Body::empty())
414
- .unwrap();
415
- let result = hooks.execute_on_error(resp).await.unwrap();
416
- assert_eq!(result.status(), StatusCode::INTERNAL_SERVER_ERROR);
417
- }
418
-
419
- #[tokio::test]
420
- async fn test_empty_builder() {
421
- let hooks = LifecycleHooks::builder().build();
422
- assert!(hooks.is_empty());
423
-
424
- let req = Request::builder().body(Body::empty()).unwrap();
425
- let result = hooks.execute_on_request(req).await.unwrap();
426
- assert!(matches!(result, HookResult::Continue(_)));
427
- }
428
- }