Skip to main content

connpool/factories/
sync_factory.rs

1//! `ConnectionFactory`: the ONE seam between the pool and the outside world (blocking).
2
3use std::error::Error;
4
5use crate::core::errors::FactoryError;
6
7/// Implement this to pool anything (DB driver, HTTP client, SSH session, socket…) — or build
8/// one from closures via [`FnFactory`].
9///
10/// `create` must return a **distinct** object each call; the pool tracks borrow state by value
11/// identity and rejects a factory that returns an object it already manages.
12pub trait ConnectionFactory<T>: Send + Sync {
13    /// Opens a new connection. Should enforce its own connect timeout.
14    fn create(&self) -> Result<T, FactoryError>;
15
16    /// A cheap liveness check (e.g. `SELECT 1`). `false` = dead. Defaults to `true`.
17    fn validate(&self, _conn: &T) -> bool {
18        true
19    }
20
21    /// Called on release to scrub session state (e.g. rollback). Defaults to a no-op.
22    fn reset(&self, _conn: &T) -> Result<(), FactoryError> {
23        Ok(())
24    }
25
26    /// Disposes of a connection. Defaults to a no-op — most Rust connections close on `Drop`;
27    /// override only when explicit teardown is needed.
28    fn close(&self, _conn: &T) -> Result<(), FactoryError> {
29        Ok(())
30    }
31
32    /// Whether `err` (raised by user code using the connection) proves the connection is
33    /// broken, so it is discarded without further checks. Defaults to `false`.
34    fn is_fatal_error(&self, _err: &(dyn Error + 'static)) -> bool {
35        false
36    }
37}
38
39/// Lets a boxed factory be used anywhere a `ConnectionFactory<T>` is expected (e.g. a
40/// [`PoolRegistry`](crate::PoolRegistry) whose per-key factories have different concrete types).
41impl<T> ConnectionFactory<T> for Box<dyn ConnectionFactory<T>> {
42    fn create(&self) -> Result<T, FactoryError> {
43        (**self).create()
44    }
45    fn validate(&self, conn: &T) -> bool {
46        (**self).validate(conn)
47    }
48    fn reset(&self, conn: &T) -> Result<(), FactoryError> {
49        (**self).reset(conn)
50    }
51    fn close(&self, conn: &T) -> Result<(), FactoryError> {
52        (**self).close(conn)
53    }
54    fn is_fatal_error(&self, err: &(dyn Error + 'static)) -> bool {
55        (**self).is_fatal_error(err)
56    }
57}
58
59type CreateFn<T> = Box<dyn Fn() -> Result<T, FactoryError> + Send + Sync>;
60type ValidateFn<T> = Box<dyn Fn(&T) -> bool + Send + Sync>;
61type ResetFn<T> = Box<dyn Fn(&T) -> Result<(), FactoryError> + Send + Sync>;
62type CloseFn<T> = Box<dyn Fn(&T) -> Result<(), FactoryError> + Send + Sync>;
63type IsFatalFn = Box<dyn Fn(&(dyn Error + 'static)) -> bool + Send + Sync>;
64
65/// A [`ConnectionFactory`] built from plain closures. Unset hooks fall back to the trait
66/// defaults.
67pub struct FnFactory<T> {
68    create_fn: CreateFn<T>,
69    validate_fn: Option<ValidateFn<T>>,
70    reset_fn: Option<ResetFn<T>>,
71    close_fn: Option<CloseFn<T>>,
72    is_fatal_fn: Option<IsFatalFn>,
73}
74
75impl<T> FnFactory<T> {
76    /// Builds a factory from a `create` closure.
77    pub fn new<F>(create: F) -> Self
78    where
79        F: Fn() -> Result<T, FactoryError> + Send + Sync + 'static,
80    {
81        Self {
82            create_fn: Box::new(create),
83            validate_fn: None,
84            reset_fn: None,
85            close_fn: None,
86            is_fatal_fn: None,
87        }
88    }
89
90    /// Sets the liveness check.
91    pub fn with_validate<F>(mut self, f: F) -> Self
92    where
93        F: Fn(&T) -> bool + Send + Sync + 'static,
94    {
95        self.validate_fn = Some(Box::new(f));
96        self
97    }
98
99    /// Sets the release-time reset.
100    pub fn with_reset<F>(mut self, f: F) -> Self
101    where
102        F: Fn(&T) -> Result<(), FactoryError> + Send + Sync + 'static,
103    {
104        self.reset_fn = Some(Box::new(f));
105        self
106    }
107
108    /// Sets the disposal hook.
109    pub fn with_close<F>(mut self, f: F) -> Self
110    where
111        F: Fn(&T) -> Result<(), FactoryError> + Send + Sync + 'static,
112    {
113        self.close_fn = Some(Box::new(f));
114        self
115    }
116
117    /// Sets the fatal-error classifier.
118    pub fn with_is_fatal_error<F>(mut self, f: F) -> Self
119    where
120        F: Fn(&(dyn Error + 'static)) -> bool + Send + Sync + 'static,
121    {
122        self.is_fatal_fn = Some(Box::new(f));
123        self
124    }
125}
126
127impl<T> ConnectionFactory<T> for FnFactory<T>
128where
129    T: Send + Sync,
130{
131    fn create(&self) -> Result<T, FactoryError> {
132        (self.create_fn)()
133    }
134
135    fn validate(&self, conn: &T) -> bool {
136        match &self.validate_fn {
137            Some(f) => f(conn),
138            None => true,
139        }
140    }
141
142    fn reset(&self, conn: &T) -> Result<(), FactoryError> {
143        match &self.reset_fn {
144            Some(f) => f(conn),
145            None => Ok(()),
146        }
147    }
148
149    fn close(&self, conn: &T) -> Result<(), FactoryError> {
150        match &self.close_fn {
151            Some(f) => f(conn),
152            None => Ok(()),
153        }
154    }
155
156    fn is_fatal_error(&self, err: &(dyn Error + 'static)) -> bool {
157        match &self.is_fatal_fn {
158            Some(f) => f(err),
159            None => false,
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn defaults_apply_when_unset() {
170        let f = FnFactory::new(|| Ok::<_, FactoryError>(7u32));
171        assert_eq!(f.create().unwrap(), 7);
172        assert!(f.validate(&7));
173        assert!(f.reset(&7).is_ok());
174        assert!(f.close(&7).is_ok());
175        let err: FactoryError = "x".into();
176        assert!(!f.is_fatal_error(&*err));
177    }
178
179    #[test]
180    fn custom_hooks_are_used() {
181        let f = FnFactory::new(|| Ok::<_, FactoryError>(1u32))
182            .with_validate(|c| *c == 1)
183            .with_reset(|_| Err::<(), FactoryError>("reset boom".into()))
184            .with_close(|_| Err::<(), FactoryError>("close boom".into()))
185            .with_is_fatal_error(|e| e.to_string() == "fatal");
186        assert!(f.validate(&1));
187        assert!(!f.validate(&2));
188        assert_eq!(f.reset(&1).unwrap_err().to_string(), "reset boom");
189        assert_eq!(f.close(&1).unwrap_err().to_string(), "close boom");
190        let fatal: FactoryError = "fatal".into();
191        let ok: FactoryError = "meh".into();
192        assert!(f.is_fatal_error(&*fatal));
193        assert!(!f.is_fatal_error(&*ok));
194    }
195
196    #[test]
197    fn create_can_fail() {
198        let f = FnFactory::new(|| Err::<u32, FactoryError>("down".into()));
199        assert_eq!(f.create().unwrap_err().to_string(), "down");
200    }
201
202    #[test]
203    fn trait_defaults_apply_to_a_minimal_impl() {
204        struct Minimal;
205        impl ConnectionFactory<u32> for Minimal {
206            fn create(&self) -> Result<u32, FactoryError> {
207                Ok(1)
208            }
209        }
210        let m = Minimal;
211        assert_eq!(m.create().unwrap(), 1);
212        assert!(m.validate(&1));
213        assert!(m.reset(&1).is_ok());
214        assert!(m.close(&1).is_ok());
215        let e: FactoryError = "x".into();
216        assert!(!m.is_fatal_error(&*e));
217    }
218
219    #[test]
220    fn boxed_factory_delegates() {
221        let boxed: Box<dyn ConnectionFactory<u32>> = Box::new(
222            FnFactory::new(|| Ok::<_, FactoryError>(5u32))
223                .with_validate(|c| *c == 5)
224                .with_reset(|_| Ok(()))
225                .with_close(|_| Ok(()))
226                .with_is_fatal_error(|_| true),
227        );
228        assert_eq!(boxed.create().unwrap(), 5);
229        assert!(boxed.validate(&5));
230        assert!(boxed.reset(&5).is_ok());
231        assert!(boxed.close(&5).is_ok());
232        let e: FactoryError = "x".into();
233        assert!(boxed.is_fatal_error(&*e));
234    }
235}