Skip to main content

connpool/factories/
async_factory.rs

1//! `AsyncConnectionFactory`: the async seam users implement to pool anything (feature `tokio`).
2//!
3//! Unlike the sibling languages there is no cancellation token: Rust async cancellation is
4//! "drop the future". The pool enforces `create_timeout` by wrapping `create` in a timer, and
5//! shields disposal by running `close` to completion on a detached task.
6
7use std::error::Error;
8use std::future::Future;
9use std::pin::Pin;
10
11use crate::core::errors::FactoryError;
12
13/// A boxed, `Send` future — the stored-closure form used by [`AsyncFnFactory`].
14pub type BoxFuture<'a, O> = Pin<Box<dyn Future<Output = O> + Send + 'a>>;
15
16/// Implement this to pool anything asynchronously. `create` must return a distinct object each
17/// call (tracked by value identity).
18pub trait AsyncConnectionFactory<T>: Send + Sync {
19    /// Opens a new connection.
20    fn create(&self) -> impl Future<Output = Result<T, FactoryError>> + Send;
21
22    /// A cheap liveness check. Defaults to `true`.
23    fn validate(&self, _conn: &T) -> impl Future<Output = bool> + Send {
24        async { true }
25    }
26
27    /// Scrubs session state on release. Defaults to a no-op.
28    fn reset(&self, _conn: &T) -> impl Future<Output = Result<(), FactoryError>> + Send {
29        async { Ok(()) }
30    }
31
32    /// Disposes of a connection. Defaults to a no-op.
33    fn close(&self, _conn: &T) -> impl Future<Output = Result<(), FactoryError>> + Send {
34        async { Ok(()) }
35    }
36
37    /// Whether `err` proves the connection is broken. Defaults to `false`.
38    fn is_fatal_error(&self, _err: &(dyn Error + 'static)) -> bool {
39        false
40    }
41}
42
43type CreateFn<T> = Box<dyn Fn() -> BoxFuture<'static, Result<T, FactoryError>> + Send + Sync>;
44type ValidateFn<T> = Box<dyn for<'a> Fn(&'a T) -> BoxFuture<'a, bool> + Send + Sync>;
45type ResetFn<T> =
46    Box<dyn for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync>;
47type CloseFn<T> =
48    Box<dyn for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync>;
49type IsFatalFn = Box<dyn Fn(&(dyn Error + 'static)) -> bool + Send + Sync>;
50
51/// An [`AsyncConnectionFactory`] built from closures returning [`BoxFuture`]s.
52pub struct AsyncFnFactory<T> {
53    create_fn: CreateFn<T>,
54    validate_fn: Option<ValidateFn<T>>,
55    reset_fn: Option<ResetFn<T>>,
56    close_fn: Option<CloseFn<T>>,
57    is_fatal_fn: Option<IsFatalFn>,
58}
59
60impl<T> AsyncFnFactory<T> {
61    /// Builds a factory from a `create` closure returning a boxed future.
62    pub fn new<F>(create: F) -> Self
63    where
64        F: Fn() -> BoxFuture<'static, Result<T, FactoryError>> + Send + Sync + 'static,
65    {
66        Self {
67            create_fn: Box::new(create),
68            validate_fn: None,
69            reset_fn: None,
70            close_fn: None,
71            is_fatal_fn: None,
72        }
73    }
74
75    /// Sets the liveness check.
76    pub fn with_validate<F>(mut self, f: F) -> Self
77    where
78        F: for<'a> Fn(&'a T) -> BoxFuture<'a, bool> + Send + Sync + 'static,
79    {
80        self.validate_fn = Some(Box::new(f));
81        self
82    }
83
84    /// Sets the release-time reset.
85    pub fn with_reset<F>(mut self, f: F) -> Self
86    where
87        F: for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync + 'static,
88    {
89        self.reset_fn = Some(Box::new(f));
90        self
91    }
92
93    /// Sets the disposal hook.
94    pub fn with_close<F>(mut self, f: F) -> Self
95    where
96        F: for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync + 'static,
97    {
98        self.close_fn = Some(Box::new(f));
99        self
100    }
101
102    /// Sets the fatal-error classifier.
103    pub fn with_is_fatal_error<F>(mut self, f: F) -> Self
104    where
105        F: Fn(&(dyn Error + 'static)) -> bool + Send + Sync + 'static,
106    {
107        self.is_fatal_fn = Some(Box::new(f));
108        self
109    }
110}
111
112impl<T> AsyncConnectionFactory<T> for AsyncFnFactory<T>
113where
114    T: Send + Sync,
115{
116    fn create(&self) -> impl Future<Output = Result<T, FactoryError>> + Send {
117        (self.create_fn)()
118    }
119
120    async fn validate(&self, conn: &T) -> bool {
121        match &self.validate_fn {
122            Some(f) => f(conn).await,
123            None => true,
124        }
125    }
126
127    async fn reset(&self, conn: &T) -> Result<(), FactoryError> {
128        match &self.reset_fn {
129            Some(f) => f(conn).await,
130            None => Ok(()),
131        }
132    }
133
134    async fn close(&self, conn: &T) -> Result<(), FactoryError> {
135        match &self.close_fn {
136            Some(f) => f(conn).await,
137            None => Ok(()),
138        }
139    }
140
141    fn is_fatal_error(&self, err: &(dyn Error + 'static)) -> bool {
142        match &self.is_fatal_fn {
143            Some(f) => f(err),
144            None => false,
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[tokio::test]
154    async fn defaults_apply_when_unset() {
155        let f = AsyncFnFactory::<u32>::new(|| Box::pin(async { Ok::<_, FactoryError>(7u32) }));
156        assert_eq!(f.create().await.unwrap(), 7u32);
157        assert!(f.validate(&7).await);
158        assert!(f.reset(&7).await.is_ok());
159        assert!(f.close(&7).await.is_ok());
160        let err: FactoryError = "x".into();
161        assert!(!f.is_fatal_error(&*err));
162    }
163
164    #[tokio::test]
165    async fn custom_hooks_are_used() {
166        let f = AsyncFnFactory::<u32>::new(|| Box::pin(async { Ok::<_, FactoryError>(1u32) }))
167            .with_validate(|c: &u32| {
168                let v = *c == 1;
169                Box::pin(async move { v }) as _
170            })
171            .with_reset(|_| Box::pin(async { Err::<(), FactoryError>("reset boom".into()) }) as _)
172            .with_close(|_| Box::pin(async { Err::<(), FactoryError>("close boom".into()) }) as _)
173            .with_is_fatal_error(|e| e.to_string() == "fatal");
174        assert!(f.validate(&1).await);
175        assert_eq!(f.reset(&1).await.unwrap_err().to_string(), "reset boom");
176        assert_eq!(f.close(&1).await.unwrap_err().to_string(), "close boom");
177        let fatal: FactoryError = "fatal".into();
178        assert!(f.is_fatal_error(&*fatal));
179    }
180
181    #[tokio::test]
182    async fn trait_defaults_apply_to_a_minimal_impl() {
183        struct Minimal;
184        impl AsyncConnectionFactory<u32> for Minimal {
185            async fn create(&self) -> Result<u32, FactoryError> {
186                Ok(1)
187            }
188        }
189        let m = Minimal;
190        assert_eq!(m.create().await.unwrap(), 1);
191        assert!(m.validate(&1).await);
192        assert!(m.reset(&1).await.is_ok());
193        assert!(m.close(&1).await.is_ok());
194        let e: FactoryError = "x".into();
195        assert!(!m.is_fatal_error(&*e));
196    }
197}