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}