1use std::{future::Future, pin::Pin, sync::Arc};
8
9type RequestHookFutureSend<'a, Req, Resp> =
10 Pin<Box<dyn Future<Output = Result<HookResult<Req, Resp>, String>> + Send + 'a>>;
11type ResponseHookFutureSend<'a, Resp> =
12 Pin<Box<dyn Future<Output = Result<HookResult<Resp, Resp>, String>> + Send + 'a>>;
13
14type RequestHookFutureLocal<'a, Req, Resp> = Pin<Box<dyn Future<Output = Result<HookResult<Req, Resp>, String>> + 'a>>;
15type ResponseHookFutureLocal<'a, Resp> = Pin<Box<dyn Future<Output = Result<HookResult<Resp, Resp>, String>> + 'a>>;
16
17#[derive(Debug)]
19pub enum HookResult<T, U> {
20 Continue(T),
22 ShortCircuit(U),
24}
25
26pub trait NativeLifecycleHook<Req, Resp>: Send + Sync {
28 fn name(&self) -> &str;
30
31 fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureSend<'a, Req, Resp>;
33
34 fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureSend<'a, Resp>;
36}
37
38pub trait LocalLifecycleHook<Req, Resp> {
40 fn name(&self) -> &str;
42
43 fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp>;
45
46 fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp>;
48}
49
50#[cfg(target_arch = "wasm32")]
51pub use LocalLifecycleHook as LifecycleHook;
52#[cfg(not(target_arch = "wasm32"))]
53pub use NativeLifecycleHook as LifecycleHook;
54
55#[cfg(not(target_arch = "wasm32"))]
57type CoreHook<Req, Resp> = dyn NativeLifecycleHook<Req, Resp>;
58#[cfg(target_arch = "wasm32")]
59type CoreHook<Req, Resp> = dyn LocalLifecycleHook<Req, Resp>;
60
61pub type TargetLifecycleHooks<Req, Resp> = LifecycleHooks<Req, Resp>;
63
64#[derive(Clone)]
66pub struct LifecycleHooks<Req, Resp> {
67 on_request: Vec<Arc<CoreHook<Req, Resp>>>,
68 pre_validation: Vec<Arc<CoreHook<Req, Resp>>>,
69 pre_handler: Vec<Arc<CoreHook<Req, Resp>>>,
70 on_response: Vec<Arc<CoreHook<Req, Resp>>>,
71 on_error: Vec<Arc<CoreHook<Req, Resp>>>,
72}
73
74impl<Req, Resp> Default for LifecycleHooks<Req, Resp> {
75 fn default() -> Self {
76 Self {
77 on_request: Vec::new(),
78 pre_validation: Vec::new(),
79 pre_handler: Vec::new(),
80 on_response: Vec::new(),
81 on_error: Vec::new(),
82 }
83 }
84}
85
86impl<Req, Resp> std::fmt::Debug for LifecycleHooks<Req, Resp> {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("LifecycleHooks")
89 .field("on_request_count", &self.on_request.len())
90 .field("pre_validation_count", &self.pre_validation.len())
91 .field("pre_handler_count", &self.pre_handler.len())
92 .field("on_response_count", &self.on_response.len())
93 .field("on_error_count", &self.on_error.len())
94 .finish()
95 }
96}
97
98impl<Req, Resp> LifecycleHooks<Req, Resp> {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn builder() -> LifecycleHooksBuilder<Req, Resp> {
106 LifecycleHooksBuilder::new()
107 }
108
109 #[inline(always)]
111 pub fn is_empty(&self) -> bool {
112 self.on_request.is_empty()
113 && self.pre_validation.is_empty()
114 && self.pre_handler.is_empty()
115 && self.on_response.is_empty()
116 && self.on_error.is_empty()
117 }
118
119 pub fn add_on_request(&mut self, hook: Arc<CoreHook<Req, Resp>>) {
120 self.on_request.push(hook);
121 }
122
123 pub fn add_pre_validation(&mut self, hook: Arc<CoreHook<Req, Resp>>) {
124 self.pre_validation.push(hook);
125 }
126
127 pub fn add_pre_handler(&mut self, hook: Arc<CoreHook<Req, Resp>>) {
128 self.pre_handler.push(hook);
129 }
130
131 pub fn add_on_response(&mut self, hook: Arc<CoreHook<Req, Resp>>) {
132 self.on_response.push(hook);
133 }
134
135 pub fn add_on_error(&mut self, hook: Arc<CoreHook<Req, Resp>>) {
136 self.on_error.push(hook);
137 }
138
139 pub async fn execute_on_request(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
140 if self.on_request.is_empty() {
141 return Ok(HookResult::Continue(req));
142 }
143
144 for hook in &self.on_request {
145 match hook.execute_request(req).await? {
146 HookResult::Continue(r) => req = r,
147 HookResult::ShortCircuit(response) => return Ok(HookResult::ShortCircuit(response)),
148 }
149 }
150
151 Ok(HookResult::Continue(req))
152 }
153
154 pub async fn execute_pre_validation(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
155 if self.pre_validation.is_empty() {
156 return Ok(HookResult::Continue(req));
157 }
158
159 for hook in &self.pre_validation {
160 match hook.execute_request(req).await? {
161 HookResult::Continue(r) => req = r,
162 HookResult::ShortCircuit(response) => return Ok(HookResult::ShortCircuit(response)),
163 }
164 }
165
166 Ok(HookResult::Continue(req))
167 }
168
169 pub async fn execute_pre_handler(&self, mut req: Req) -> Result<HookResult<Req, Resp>, String> {
170 if self.pre_handler.is_empty() {
171 return Ok(HookResult::Continue(req));
172 }
173
174 for hook in &self.pre_handler {
175 match hook.execute_request(req).await? {
176 HookResult::Continue(r) => req = r,
177 HookResult::ShortCircuit(response) => return Ok(HookResult::ShortCircuit(response)),
178 }
179 }
180
181 Ok(HookResult::Continue(req))
182 }
183
184 pub async fn execute_on_response(&self, mut resp: Resp) -> Result<Resp, String> {
185 if self.on_response.is_empty() {
186 return Ok(resp);
187 }
188
189 for hook in &self.on_response {
190 match hook.execute_response(resp).await? {
191 HookResult::Continue(r) => resp = r,
192 HookResult::ShortCircuit(r) => resp = r,
193 }
194 }
195
196 Ok(resp)
197 }
198
199 pub async fn execute_on_error(&self, mut resp: Resp) -> Result<Resp, String> {
200 if self.on_error.is_empty() {
201 return Ok(resp);
202 }
203
204 for hook in &self.on_error {
205 match hook.execute_response(resp).await? {
206 HookResult::Continue(r) => resp = r,
207 HookResult::ShortCircuit(r) => resp = r,
208 }
209 }
210
211 Ok(resp)
212 }
213}
214
215struct RequestHookFn<F, Req, Resp> {
217 name: String,
218 func: F,
219 _marker: std::marker::PhantomData<fn(Req, Resp)>,
220}
221
222struct ResponseHookFn<F, Req, Resp> {
223 name: String,
224 func: F,
225 _marker: std::marker::PhantomData<fn(Req, Resp)>,
226}
227
228#[cfg(not(target_arch = "wasm32"))]
229impl<F, Fut, Req, Resp> NativeLifecycleHook<Req, Resp> for RequestHookFn<F, Req, Resp>
230where
231 F: Fn(Req) -> Fut + Send + Sync,
232 Fut: Future<Output = Result<HookResult<Req, Resp>, String>> + Send + 'static,
233 Req: Send + 'static,
234 Resp: Send + 'static,
235{
236 fn name(&self) -> &str {
237 &self.name
238 }
239
240 fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
241 Box::pin((self.func)(req))
242 }
243
244 fn execute_response<'a>(&'a self, _resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
245 Box::pin(async move { Err("Request hook called with response - this is a bug".to_string()) })
246 }
247}
248
249#[cfg(target_arch = "wasm32")]
250impl<F, Fut, Req, Resp> LocalLifecycleHook<Req, Resp> for RequestHookFn<F, Req, Resp>
251where
252 F: Fn(Req) -> Fut + Send + Sync,
253 Fut: Future<Output = Result<HookResult<Req, Resp>, String>> + 'static,
254 Req: 'static,
255 Resp: 'static,
256{
257 fn name(&self) -> &str {
258 &self.name
259 }
260
261 fn execute_request<'a>(&'a self, req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
262 Box::pin((self.func)(req))
263 }
264
265 fn execute_response<'a>(&'a self, _resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
266 Box::pin(async move { Err("Request hook called with response - this is a bug".to_string()) })
267 }
268}
269
270#[cfg(not(target_arch = "wasm32"))]
271impl<F, Fut, Req, Resp> NativeLifecycleHook<Req, Resp> for ResponseHookFn<F, Req, Resp>
272where
273 F: Fn(Resp) -> Fut + Send + Sync,
274 Fut: Future<Output = Result<HookResult<Resp, Resp>, String>> + Send + 'static,
275 Req: Send + 'static,
276 Resp: Send + 'static,
277{
278 fn name(&self) -> &str {
279 &self.name
280 }
281
282 fn execute_request<'a>(&'a self, _req: Req) -> RequestHookFutureSend<'a, Req, Resp> {
283 Box::pin(async move { Err("Response hook called with request - this is a bug".to_string()) })
284 }
285
286 fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureSend<'a, Resp> {
287 Box::pin((self.func)(resp))
288 }
289}
290
291#[cfg(target_arch = "wasm32")]
292impl<F, Fut, Req, Resp> LocalLifecycleHook<Req, Resp> for ResponseHookFn<F, Req, Resp>
293where
294 F: Fn(Resp) -> Fut + Send + Sync,
295 Fut: Future<Output = Result<HookResult<Resp, Resp>, String>> + 'static,
296 Req: 'static,
297 Resp: 'static,
298{
299 fn name(&self) -> &str {
300 &self.name
301 }
302
303 fn execute_request<'a>(&'a self, _req: Req) -> RequestHookFutureLocal<'a, Req, Resp> {
304 Box::pin(async move { Err("Response hook called with request - this is a bug".to_string()) })
305 }
306
307 fn execute_response<'a>(&'a self, resp: Resp) -> ResponseHookFutureLocal<'a, Resp> {
308 Box::pin((self.func)(resp))
309 }
310}
311
312pub struct LifecycleHooksBuilder<Req, Resp> {
314 hooks: LifecycleHooks<Req, Resp>,
315}
316
317impl<Req, Resp> LifecycleHooksBuilder<Req, Resp> {
318 pub fn new() -> Self {
319 Self {
320 hooks: LifecycleHooks::default(),
321 }
322 }
323
324 pub fn on_request(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
325 self.hooks.add_on_request(hook);
326 self
327 }
328
329 pub fn pre_validation(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
330 self.hooks.add_pre_validation(hook);
331 self
332 }
333
334 pub fn pre_handler(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
335 self.hooks.add_pre_handler(hook);
336 self
337 }
338
339 pub fn on_response(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
340 self.hooks.add_on_response(hook);
341 self
342 }
343
344 pub fn on_error(mut self, hook: Arc<CoreHook<Req, Resp>>) -> Self {
345 self.hooks.add_on_error(hook);
346 self
347 }
348
349 pub fn build(self) -> LifecycleHooks<Req, Resp> {
350 self.hooks
351 }
352}
353
354impl<Req, Resp> Default for LifecycleHooksBuilder<Req, Resp> {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360#[cfg(not(target_arch = "wasm32"))]
362pub fn request_hook<Req, Resp, F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Req, Resp>>
363where
364 F: Fn(Req) -> Fut + Send + Sync + 'static,
365 Fut: Future<Output = Result<HookResult<Req, Resp>, String>> + Send + 'static,
366 Req: Send + 'static,
367 Resp: Send + 'static,
368{
369 Arc::new(RequestHookFn {
370 name: name.into(),
371 func,
372 _marker: std::marker::PhantomData,
373 })
374}
375
376#[cfg(target_arch = "wasm32")]
378pub fn request_hook<Req, Resp, F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Req, Resp>>
379where
380 F: Fn(Req) -> Fut + Send + Sync + 'static,
381 Fut: Future<Output = Result<HookResult<Req, Resp>, String>> + 'static,
382 Req: 'static,
383 Resp: 'static,
384{
385 Arc::new(RequestHookFn {
386 name: name.into(),
387 func,
388 _marker: std::marker::PhantomData,
389 })
390}
391
392#[cfg(not(target_arch = "wasm32"))]
394pub fn response_hook<Req, Resp, F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Req, Resp>>
395where
396 F: Fn(Resp) -> Fut + Send + Sync + 'static,
397 Fut: Future<Output = Result<HookResult<Resp, Resp>, String>> + Send + 'static,
398 Req: Send + 'static,
399 Resp: Send + 'static,
400{
401 Arc::new(ResponseHookFn {
402 name: name.into(),
403 func,
404 _marker: std::marker::PhantomData,
405 })
406}
407
408#[cfg(target_arch = "wasm32")]
410pub fn response_hook<Req, Resp, F, Fut>(name: impl Into<String>, func: F) -> Arc<dyn LifecycleHook<Req, Resp>>
411where
412 F: Fn(Resp) -> Fut + Send + Sync + 'static,
413 Fut: Future<Output = Result<HookResult<Resp, Resp>, String>> + 'static,
414 Req: 'static,
415 Resp: 'static,
416{
417 Arc::new(ResponseHookFn {
418 name: name.into(),
419 func,
420 _marker: std::marker::PhantomData,
421 })
422}
423
424#[cfg(test)]
425mod 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}