spikard 0.2.0 → 0.2.5
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/spikard_rb/Cargo.toml +3 -2
- data/lib/spikard/version.rb +1 -1
- data/vendor/bundle/ruby/3.3.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
- data/vendor/crates/spikard-core/Cargo.toml +40 -0
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/crates/spikard-core/src/debug.rs +63 -0
- data/vendor/crates/spikard-core/src/di/container.rs +726 -0
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/crates/spikard-core/src/di/error.rs +118 -0
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/crates/spikard-core/src/di/value.rs +283 -0
- data/vendor/crates/spikard-core/src/http.rs +153 -0
- data/vendor/crates/spikard-core/src/lib.rs +28 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/crates/spikard-core/src/parameters.rs +719 -0
- data/vendor/crates/spikard-core/src/problem.rs +310 -0
- data/vendor/crates/spikard-core/src/request_data.rs +189 -0
- data/vendor/crates/spikard-core/src/router.rs +249 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
- data/vendor/crates/spikard-core/src/validation.rs +699 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +249 -0
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/crates/spikard-http/src/cors.rs +490 -0
- data/vendor/crates/spikard-http/src/debug.rs +63 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +423 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +190 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +228 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +529 -0
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +86 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +147 -0
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +190 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +308 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +195 -0
- data/vendor/crates/spikard-http/src/parameters.rs +1 -0
- data/vendor/crates/spikard-http/src/problem.rs +1 -0
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
- data/vendor/crates/spikard-http/src/response.rs +399 -0
- data/vendor/crates/spikard-http/src/router.rs +1 -0
- data/vendor/crates/spikard-http/src/schema_registry.rs +1 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +80 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +805 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +119 -0
- data/vendor/crates/spikard-http/src/sse.rs +447 -0
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/crates/spikard-http/src/testing.rs +377 -0
- data/vendor/crates/spikard-http/src/type_hints.rs +1 -0
- data/vendor/crates/spikard-http/src/validation.rs +1 -0
- data/vendor/crates/spikard-http/src/websocket.rs +324 -0
- data/vendor/crates/spikard-rb/Cargo.toml +42 -0
- data/vendor/crates/spikard-rb/build.rs +8 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config.rs +294 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +392 -0
- data/vendor/crates/spikard-rb/src/di.rs +409 -0
- data/vendor/crates/spikard-rb/src/handler.rs +534 -0
- data/vendor/crates/spikard-rb/src/lib.rs +2020 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +267 -0
- data/vendor/crates/spikard-rb/src/server.rs +283 -0
- data/vendor/crates/spikard-rb/src/sse.rs +231 -0
- data/vendor/crates/spikard-rb/src/test_client.rs +404 -0
- data/vendor/crates/spikard-rb/src/test_sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/test_websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- metadata +81 -2
|
@@ -0,0 +1,428 @@
|
|
|
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
|
+
}
|