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