fedimint_api_client/api/global_api/
with_request_hook.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3
4use fedimint_connectors::ServerResult;
5use fedimint_core::core::ModuleInstanceId;
6use fedimint_core::module::ApiRequestErased;
7use fedimint_core::task::{MaybeSend, MaybeSync};
8use fedimint_core::{PeerId, apply, async_trait_maybe_send, maybe_add_send_sync};
9use futures::stream::BoxStream;
10use serde_json::Value;
11
12use super::super::{DynModuleApi, IRawFederationApi};
13
14/// "Api Request Hook"
15///
16/// An "api request hook" is a function that gets a raw federation api and
17/// can either pass it unmodified (no hook) or wrap it in whatever custom
18/// logic and return as a new raw federation api, possibly forwarding the call
19/// to the original one.
20///
21/// This is meant to allow downstream users to add custom logic for debugging,
22/// testing (e.g. simulating network being down), collecting stats, notifying
23/// about slow calls, errors, etc.
24pub type ApiRequestHook =
25    Arc<maybe_add_send_sync!(dyn Fn(DynIRawFederationApi) -> DynIRawFederationApi + 'static)>;
26
27pub type DynIRawFederationApi = Box<maybe_add_send_sync!(dyn IRawFederationApi + 'static)>;
28
29/// Convenience extension trait used for wrapping [`IRawFederationApi`] in
30/// a [`RawFederationApiWithRequestHook`]
31pub trait RawFederationApiWithRequestHookExt
32where
33    Self: Sized,
34{
35    fn with_request_hook(self, hook: &ApiRequestHook) -> RawFederationApiWithRequestHook;
36}
37
38impl<T> RawFederationApiWithRequestHookExt for T
39where
40    T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
41{
42    fn with_request_hook(self, hook: &ApiRequestHook) -> RawFederationApiWithRequestHook {
43        RawFederationApiWithRequestHook::new(self, hook)
44    }
45}
46
47/// [`IRawFederationApi`] wrapping some `T: IRawFederationApi` in a user hook
48///
49/// Use [`RawFederationApiWithRequestHookExt::with_request_hook`] to
50/// create.
51#[derive(Debug)]
52pub struct RawFederationApiWithRequestHook {
53    pub(crate) inner: DynIRawFederationApi,
54}
55
56impl RawFederationApiWithRequestHook {
57    pub fn new<T>(inner: T, hook: &ApiRequestHook) -> RawFederationApiWithRequestHook
58    where
59        T: IRawFederationApi + MaybeSend + MaybeSync + 'static,
60    {
61        RawFederationApiWithRequestHook {
62            inner: hook(Box::new(inner)),
63        }
64    }
65}
66
67#[apply(async_trait_maybe_send!)]
68impl IRawFederationApi for RawFederationApiWithRequestHook {
69    fn all_peers(&self) -> &BTreeSet<PeerId> {
70        self.inner.all_peers()
71    }
72
73    fn self_peer(&self) -> Option<PeerId> {
74        self.inner.self_peer()
75    }
76
77    fn with_module(&self, id: ModuleInstanceId) -> DynModuleApi {
78        self.inner.with_module(id)
79    }
80
81    async fn request_raw(
82        &self,
83        peer_id: PeerId,
84        method: &str,
85        params: &ApiRequestErased,
86    ) -> ServerResult<Value> {
87        self.inner.request_raw(peer_id, method, params).await
88    }
89
90    fn connection_status_stream(&self) -> BoxStream<'static, BTreeMap<PeerId, bool>> {
91        self.inner.connection_status_stream()
92    }
93
94    async fn wait_for_initialized_connections(&self) {
95        self.inner.wait_for_initialized_connections().await;
96    }
97}