1use std::collections::HashMap;
38use std::future::Future;
39use std::pin::Pin;
40use std::sync::Arc;
41
42use crate::error::{ClientError, ClientResult};
43
44#[derive(Debug)]
51pub struct ClientRequest {
52 pub method: String,
54
55 pub params: serde_json::Value,
57
58 pub extra_headers: HashMap<String, String>,
62}
63
64impl ClientRequest {
65 #[must_use]
67 pub fn new(method: impl Into<String>, params: serde_json::Value) -> Self {
68 Self {
69 method: method.into(),
70 params,
71 extra_headers: HashMap::new(),
72 }
73 }
74}
75
76#[derive(Debug)]
80pub struct ClientResponse {
81 pub method: String,
83
84 pub result: serde_json::Value,
86
87 pub status_code: u16,
95}
96
97pub trait CallInterceptor: Send + Sync + 'static {
111 fn before<'a>(
115 &'a self,
116 req: &'a mut ClientRequest,
117 ) -> impl Future<Output = ClientResult<()>> + Send + 'a;
118
119 fn after<'a>(
123 &'a self,
124 resp: &'a ClientResponse,
125 ) -> impl Future<Output = ClientResult<()>> + Send + 'a;
126
127 fn on_error<'a>(
143 &'a self,
144 req: &'a ClientRequest,
145 err: &'a ClientError,
146 ) -> impl Future<Output = ()> + Send + 'a {
147 let _ = (req, err);
148 async {}
149 }
150}
151
152pub(crate) trait CallInterceptorBoxed: Send + Sync + 'static {
158 fn before_boxed<'a>(
159 &'a self,
160 req: &'a mut ClientRequest,
161 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>>;
162
163 fn after_boxed<'a>(
164 &'a self,
165 resp: &'a ClientResponse,
166 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>>;
167
168 fn on_error_boxed<'a>(
169 &'a self,
170 req: &'a ClientRequest,
171 err: &'a ClientError,
172 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
173}
174
175impl<T: CallInterceptor> CallInterceptorBoxed for T {
176 fn before_boxed<'a>(
177 &'a self,
178 req: &'a mut ClientRequest,
179 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>> {
180 Box::pin(self.before(req))
181 }
182
183 fn after_boxed<'a>(
184 &'a self,
185 resp: &'a ClientResponse,
186 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>> {
187 Box::pin(self.after(resp))
188 }
189
190 fn on_error_boxed<'a>(
191 &'a self,
192 req: &'a ClientRequest,
193 err: &'a ClientError,
194 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
195 Box::pin(self.on_error(req, err))
196 }
197}
198
199impl CallInterceptorBoxed for Box<dyn CallInterceptorBoxed> {
200 fn before_boxed<'a>(
201 &'a self,
202 req: &'a mut ClientRequest,
203 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>> {
204 (**self).before_boxed(req)
205 }
206
207 fn after_boxed<'a>(
208 &'a self,
209 resp: &'a ClientResponse,
210 ) -> Pin<Box<dyn Future<Output = ClientResult<()>> + Send + 'a>> {
211 (**self).after_boxed(resp)
212 }
213
214 fn on_error_boxed<'a>(
215 &'a self,
216 req: &'a ClientRequest,
217 err: &'a ClientError,
218 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
219 (**self).on_error_boxed(req, err)
220 }
221}
222
223#[derive(Default)]
230pub struct InterceptorChain {
231 interceptors: Vec<Arc<dyn CallInterceptorBoxed>>,
232}
233
234impl InterceptorChain {
235 #[must_use]
237 pub fn new() -> Self {
238 Self::default()
239 }
240
241 pub fn push<I: CallInterceptor>(&mut self, interceptor: I) {
243 self.interceptors.push(Arc::new(interceptor));
244 }
245
246 #[must_use]
248 pub fn is_empty(&self) -> bool {
249 self.interceptors.is_empty()
250 }
251
252 pub async fn run_before(&self, req: &mut ClientRequest) -> ClientResult<()> {
258 for interceptor in &self.interceptors {
259 interceptor.before_boxed(req).await?;
260 }
261 Ok(())
262 }
263
264 pub async fn run_after(&self, resp: &ClientResponse) -> ClientResult<()> {
270 for interceptor in self.interceptors.iter().rev() {
271 interceptor.after_boxed(resp).await?;
272 }
273 Ok(())
274 }
275
276 pub async fn run_on_error(&self, req: &ClientRequest, err: &ClientError) {
278 for interceptor in self.interceptors.iter().rev() {
279 interceptor.on_error_boxed(req, err).await;
280 }
281 }
282}
283
284impl std::fmt::Debug for InterceptorChain {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 f.debug_struct("InterceptorChain")
287 .field("count", &self.interceptors.len())
288 .finish()
289 }
290}
291
292#[cfg(test)]
295mod tests {
296 use super::*;
297 use std::sync::atomic::{AtomicUsize, Ordering};
298
299 struct CountingInterceptor(Arc<AtomicUsize>);
300
301 impl CallInterceptor for CountingInterceptor {
302 #[allow(clippy::manual_async_fn)]
303 fn before<'a>(
304 &'a self,
305 _req: &'a mut ClientRequest,
306 ) -> impl std::future::Future<Output = ClientResult<()>> + Send + 'a {
307 async move {
308 self.0.fetch_add(1, Ordering::SeqCst);
309 Ok(())
310 }
311 }
312 #[allow(clippy::manual_async_fn)]
313 fn after<'a>(
314 &'a self,
315 _resp: &'a ClientResponse,
316 ) -> impl std::future::Future<Output = ClientResult<()>> + Send + 'a {
317 async move {
318 self.0.fetch_add(10, Ordering::SeqCst);
319 Ok(())
320 }
321 }
322 }
323
324 #[test]
325 fn chain_is_empty_when_new() {
326 let chain = InterceptorChain::new();
327 assert!(chain.is_empty(), "new chain should be empty");
328 }
329
330 #[test]
331 fn chain_is_not_empty_after_push() {
332 let counter = Arc::new(AtomicUsize::new(0));
333 let mut chain = InterceptorChain::new();
334 chain.push(CountingInterceptor(Arc::clone(&counter)));
335 assert!(
336 !chain.is_empty(),
337 "chain with one interceptor should not be empty"
338 );
339 }
340
341 #[tokio::test]
342 async fn chain_runs_before_in_order() {
343 let counter = Arc::new(AtomicUsize::new(0));
344 let mut chain = InterceptorChain::new();
345 chain.push(CountingInterceptor(Arc::clone(&counter)));
346 chain.push(CountingInterceptor(Arc::clone(&counter)));
347
348 let mut req = ClientRequest::new("message/send", serde_json::Value::Null);
349 chain.run_before(&mut req).await.unwrap();
350 assert_eq!(counter.load(Ordering::SeqCst), 2);
351 }
352
353 #[tokio::test]
354 async fn chain_runs_after_in_reverse_order() {
355 let counter = Arc::new(AtomicUsize::new(0));
356 let mut chain = InterceptorChain::new();
357 chain.push(CountingInterceptor(Arc::clone(&counter)));
358
359 let resp = ClientResponse {
360 method: "message/send".into(),
361 result: serde_json::Value::Null,
362 status_code: 200,
363 };
364 chain.run_after(&resp).await.unwrap();
365 assert_eq!(counter.load(Ordering::SeqCst), 10);
366 }
367
368 #[tokio::test]
371 async fn boxed_interceptor_delegates_before_and_after() {
372 let counter = Arc::new(AtomicUsize::new(0));
373 let interceptor = CountingInterceptor(Arc::clone(&counter));
374 let boxed: Box<dyn CallInterceptorBoxed> = Box::new(interceptor);
376
377 let mut req = ClientRequest::new("test", serde_json::Value::Null);
378 boxed.before_boxed(&mut req).await.unwrap();
379 assert_eq!(
380 counter.load(Ordering::SeqCst),
381 1,
382 "before_boxed should delegate"
383 );
384
385 let resp = ClientResponse {
386 method: "test".into(),
387 result: serde_json::Value::Null,
388 status_code: 200,
389 };
390 boxed.after_boxed(&resp).await.unwrap();
391 assert_eq!(
392 counter.load(Ordering::SeqCst),
393 11,
394 "after_boxed should delegate"
395 );
396
397 let double_boxed: Box<dyn CallInterceptorBoxed> = Box::new(boxed);
399 double_boxed.before_boxed(&mut req).await.unwrap();
400 assert_eq!(
401 counter.load(Ordering::SeqCst),
402 12,
403 "double-boxed before should delegate"
404 );
405 double_boxed.after_boxed(&resp).await.unwrap();
406 assert_eq!(
407 counter.load(Ordering::SeqCst),
408 22,
409 "double-boxed after should delegate"
410 );
411 }
412
413 struct ErrorRecorder(u8, Arc<std::sync::Mutex<Vec<u8>>>);
415
416 impl CallInterceptor for ErrorRecorder {
417 #[allow(clippy::manual_async_fn)]
418 fn before<'a>(
419 &'a self,
420 _req: &'a mut ClientRequest,
421 ) -> impl std::future::Future<Output = ClientResult<()>> + Send + 'a {
422 async move { Ok(()) }
423 }
424 #[allow(clippy::manual_async_fn)]
425 fn after<'a>(
426 &'a self,
427 _resp: &'a ClientResponse,
428 ) -> impl std::future::Future<Output = ClientResult<()>> + Send + 'a {
429 async move { Ok(()) }
430 }
431 #[allow(clippy::manual_async_fn)]
432 fn on_error<'a>(
433 &'a self,
434 _req: &'a ClientRequest,
435 _err: &'a ClientError,
436 ) -> impl std::future::Future<Output = ()> + Send + 'a {
437 async move { self.1.lock().expect("log").push(self.0) }
438 }
439 }
440
441 #[tokio::test]
442 async fn chain_runs_on_error_in_reverse_order_and_default_is_a_no_op() {
443 let log = Arc::new(std::sync::Mutex::new(Vec::new()));
444 let counter = Arc::new(AtomicUsize::new(0));
445 let mut chain = InterceptorChain::new();
446 chain.push(ErrorRecorder(1, Arc::clone(&log)));
447 chain.push(CountingInterceptor(Arc::clone(&counter)));
448 chain.push(ErrorRecorder(2, Arc::clone(&log)));
449
450 let req = ClientRequest::new("GetTask", serde_json::Value::Null);
451 chain
452 .run_on_error(&req, &ClientError::Timeout("t".into()))
453 .await;
454 assert_eq!(
455 *log.lock().expect("log"),
456 [2, 1],
457 "reverse order, like after"
458 );
459 assert_eq!(
460 counter.load(Ordering::SeqCst),
461 0,
462 "the default does nothing"
463 );
464 }
465
466 #[tokio::test]
467 async fn boxed_interceptor_delegates_on_error() {
468 let log = Arc::new(std::sync::Mutex::new(Vec::new()));
469 let boxed: Box<dyn CallInterceptorBoxed> = Box::new(ErrorRecorder(7, Arc::clone(&log)));
470 let double_boxed: Box<dyn CallInterceptorBoxed> = Box::new(boxed);
471 let req = ClientRequest::new("GetTask", serde_json::Value::Null);
472 double_boxed
473 .on_error_boxed(&req, &ClientError::Timeout("t".into()))
474 .await;
475 assert_eq!(*log.lock().expect("log"), [7]);
476 }
477}