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> = Box<dyn for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync>;
46type CloseFn<T> = Box<dyn for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync>;
47type IsFatalFn = Box<dyn Fn(&(dyn Error + 'static)) -> bool + Send + Sync>;
48
49/// An [`AsyncConnectionFactory`] built from closures returning [`BoxFuture`]s.
50pub struct AsyncFnFactory<T> {
51    create_fn: CreateFn<T>,
52    validate_fn: Option<ValidateFn<T>>,
53    reset_fn: Option<ResetFn<T>>,
54    close_fn: Option<CloseFn<T>>,
55    is_fatal_fn: Option<IsFatalFn>,
56}
57
58impl<T> AsyncFnFactory<T> {
59    /// Builds a factory from a `create` closure returning a boxed future.
60    pub fn new<F>(create: F) -> Self
61    where
62        F: Fn() -> BoxFuture<'static, Result<T, FactoryError>> + Send + Sync + 'static,
63    {
64        Self {
65            create_fn: Box::new(create),
66            validate_fn: None,
67            reset_fn: None,
68            close_fn: None,
69            is_fatal_fn: None,
70        }
71    }
72
73    /// Sets the liveness check.
74    pub fn with_validate<F>(mut self, f: F) -> Self
75    where
76        F: for<'a> Fn(&'a T) -> BoxFuture<'a, bool> + Send + Sync + 'static,
77    {
78        self.validate_fn = Some(Box::new(f));
79        self
80    }
81
82    /// Sets the release-time reset.
83    pub fn with_reset<F>(mut self, f: F) -> Self
84    where
85        F: for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync + 'static,
86    {
87        self.reset_fn = Some(Box::new(f));
88        self
89    }
90
91    /// Sets the disposal hook.
92    pub fn with_close<F>(mut self, f: F) -> Self
93    where
94        F: for<'a> Fn(&'a T) -> BoxFuture<'a, Result<(), FactoryError>> + Send + Sync + 'static,
95    {
96        self.close_fn = Some(Box::new(f));
97        self
98    }
99
100    /// Sets the fatal-error classifier.
101    pub fn with_is_fatal_error<F>(mut self, f: F) -> Self
102    where
103        F: Fn(&(dyn Error + 'static)) -> bool + Send + Sync + 'static,
104    {
105        self.is_fatal_fn = Some(Box::new(f));
106        self
107    }
108}
109
110impl<T> AsyncConnectionFactory<T> for AsyncFnFactory<T>
111where
112    T: Send + Sync,
113{
114    fn create(&self) -> impl Future<Output = Result<T, FactoryError>> + Send {
115        (self.create_fn)()
116    }
117
118    async fn validate(&self, conn: &T) -> bool {
119        match &self.validate_fn {
120            Some(f) => f(conn).await,
121            None => true,
122        }
123    }
124
125    async fn reset(&self, conn: &T) -> Result<(), FactoryError> {
126        match &self.reset_fn {
127            Some(f) => f(conn).await,
128            None => Ok(()),
129        }
130    }
131
132    async fn close(&self, conn: &T) -> Result<(), FactoryError> {
133        match &self.close_fn {
134            Some(f) => f(conn).await,
135            None => Ok(()),
136        }
137    }
138
139    fn is_fatal_error(&self, err: &(dyn Error + 'static)) -> bool {
140        match &self.is_fatal_fn {
141            Some(f) => f(err),
142            None => false,
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[tokio::test]
152    async fn defaults_apply_when_unset() {
153        let f = AsyncFnFactory::<u32>::new(|| Box::pin(async { Ok::<_, FactoryError>(7u32) }));
154        assert_eq!(f.create().await.unwrap(), 7u32);
155        assert!(f.validate(&7).await);
156        assert!(f.reset(&7).await.is_ok());
157        assert!(f.close(&7).await.is_ok());
158        let err: FactoryError = "x".into();
159        assert!(!f.is_fatal_error(&*err));
160    }
161
162    #[tokio::test]
163    async fn custom_hooks_are_used() {
164        let f = AsyncFnFactory::<u32>::new(|| Box::pin(async { Ok::<_, FactoryError>(1u32) }))
165            .with_validate(|c: &u32| {
166                let v = *c == 1;
167                Box::pin(async move { v }) as _
168            })
169            .with_reset(|_| Box::pin(async { Err::<(), FactoryError>("reset boom".into()) }) as _)
170            .with_close(|_| Box::pin(async { Err::<(), FactoryError>("close boom".into()) }) as _)
171            .with_is_fatal_error(|e| e.to_string() == "fatal");
172        assert!(f.validate(&1).await);
173        assert_eq!(f.reset(&1).await.unwrap_err().to_string(), "reset boom");
174        assert_eq!(f.close(&1).await.unwrap_err().to_string(), "close boom");
175        let fatal: FactoryError = "fatal".into();
176        assert!(f.is_fatal_error(&*fatal));
177    }
178
179    #[tokio::test]
180    async fn trait_defaults_apply_to_a_minimal_impl() {
181        struct Minimal;
182        impl AsyncConnectionFactory<u32> for Minimal {
183            async fn create(&self) -> Result<u32, FactoryError> {
184                Ok(1)
185            }
186        }
187        let m = Minimal;
188        assert_eq!(m.create().await.unwrap(), 1);
189        assert!(m.validate(&1).await);
190        assert!(m.reset(&1).await.is_ok());
191        assert!(m.close(&1).await.is_ok());
192        let e: FactoryError = "x".into();
193        assert!(!m.is_fatal_error(&*e));
194    }
195}