1use crate::core::errors::PoolError;
4use crate::resilience::breaker::BreakerConfig;
5use crate::resilience::retry::RetryPolicy;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum IdleStrategy {
10 Lifo,
12 Fifo,
14}
15
16impl IdleStrategy {
17 pub fn as_str(&self) -> &'static str {
19 match self {
20 IdleStrategy::Lifo => "lifo",
21 IdleStrategy::Fifo => "fifo",
22 }
23 }
24}
25
26impl std::fmt::Display for IdleStrategy {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.write_str(self.as_str())
29 }
30}
31
32#[derive(Debug, Clone)]
41pub struct PoolConfig {
42 pub min_size: usize,
45 pub max_size: usize,
47
48 pub acquire_timeout: Option<f64>,
51 pub create_timeout: Option<f64>,
54 pub max_waiters: Option<usize>,
57
58 pub max_lifetime: Option<f64>,
61 pub lifetime_jitter: f64,
63 pub max_idle_time: Option<f64>,
65 pub max_uses: Option<u64>,
67 pub idle_strategy: IdleStrategy,
69
70 pub validate_after_idle: Option<f64>,
73 pub maintenance_interval: Option<f64>,
75 pub leak_detection_timeout: Option<f64>,
77 pub validate_idle: bool,
80
81 pub retry: RetryPolicy,
84 pub breaker: Option<BreakerConfig>,
86}
87
88impl Default for PoolConfig {
89 fn default() -> Self {
90 Self {
91 min_size: 0,
92 max_size: 10,
93 acquire_timeout: Some(30.0),
94 create_timeout: Some(10.0),
95 max_waiters: None,
96 max_lifetime: Some(1800.0),
97 lifetime_jitter: 0.1,
98 max_idle_time: Some(600.0),
99 max_uses: None,
100 idle_strategy: IdleStrategy::Lifo,
101 validate_after_idle: Some(30.0),
102 maintenance_interval: Some(30.0),
103 leak_detection_timeout: None,
104 validate_idle: false,
105 retry: RetryPolicy::default(),
106 breaker: Some(BreakerConfig::default()),
107 }
108 }
109}
110
111impl PoolConfig {
112 pub fn validate(&self) -> Result<(), PoolError> {
114 let need = |ok: bool, msg: &str| -> Result<(), PoolError> {
115 if ok {
116 Ok(())
117 } else {
118 Err(PoolError::Config(msg.to_string()))
119 }
120 };
121
122 need(self.max_size >= 1, "max_size must be >= 1")?;
123 need(
124 self.min_size <= self.max_size,
125 "min_size must be within [0, max_size]",
126 )?;
127 need(
128 self.acquire_timeout.map(|v| v >= 0.0).unwrap_or(true),
129 "acquire_timeout must be >= 0",
130 )?;
131 for (value, name) in [
132 (self.create_timeout, "create_timeout"),
133 (self.max_lifetime, "max_lifetime"),
134 (self.max_idle_time, "max_idle_time"),
135 (self.maintenance_interval, "maintenance_interval"),
136 (self.leak_detection_timeout, "leak_detection_timeout"),
137 ] {
138 need(
139 value.map(|v| v > 0.0).unwrap_or(true),
140 &format!("{name} must be > 0 or None"),
141 )?;
142 }
143 need(
144 self.validate_after_idle.map(|v| v >= 0.0).unwrap_or(true),
145 "validate_after_idle must be >= 0 or None",
146 )?;
147 need(
148 (0.0..=1.0).contains(&self.lifetime_jitter),
149 "lifetime_jitter must be within [0, 1]",
150 )?;
151 need(
152 self.max_uses.map(|v| v >= 1).unwrap_or(true),
153 "max_uses must be >= 1 or None",
154 )?;
155 self.retry.validate()?;
158 if let Some(b) = &self.breaker {
159 b.validate()?;
160 }
161 Ok(())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn idle_strategy_wire_values() {
171 assert_eq!(IdleStrategy::Lifo.as_str(), "lifo");
172 assert_eq!(IdleStrategy::Fifo.as_str(), "fifo");
173 assert_eq!(IdleStrategy::Fifo.to_string(), "fifo");
174 assert_eq!(format!("{:?}", IdleStrategy::Lifo), "Lifo");
175 }
176
177 #[test]
178 fn defaults_match_spec() {
179 let c = PoolConfig::default();
180 assert_eq!(c.min_size, 0);
181 assert_eq!(c.max_size, 10);
182 assert_eq!(c.acquire_timeout, Some(30.0));
183 assert_eq!(c.create_timeout, Some(10.0));
184 assert_eq!(c.max_waiters, None);
185 assert_eq!(c.max_lifetime, Some(1800.0));
186 assert_eq!(c.lifetime_jitter, 0.1);
187 assert_eq!(c.max_idle_time, Some(600.0));
188 assert_eq!(c.max_uses, None);
189 assert_eq!(c.idle_strategy, IdleStrategy::Lifo);
190 assert_eq!(c.validate_after_idle, Some(30.0));
191 assert_eq!(c.maintenance_interval, Some(30.0));
192 assert_eq!(c.leak_detection_timeout, None);
193 assert!(!c.validate_idle);
194 assert!(c.breaker.is_some());
195 assert!(c.validate().is_ok());
196 assert!(format!("{c:?}").contains("PoolConfig"));
197 assert!(c.clone().validate().is_ok());
198 }
199
200 #[test]
201 fn validation_rejects_bad_values() {
202 let bad = |c: PoolConfig| c.validate().unwrap_err().to_string();
203 assert_eq!(
204 bad(PoolConfig {
205 max_size: 0,
206 ..Default::default()
207 }),
208 "max_size must be >= 1"
209 );
210 assert_eq!(
211 bad(PoolConfig {
212 min_size: 5,
213 max_size: 4,
214 ..Default::default()
215 }),
216 "min_size must be within [0, max_size]"
217 );
218 assert_eq!(
219 bad(PoolConfig {
220 acquire_timeout: Some(-1.0),
221 ..Default::default()
222 }),
223 "acquire_timeout must be >= 0"
224 );
225 assert_eq!(
226 bad(PoolConfig {
227 create_timeout: Some(0.0),
228 ..Default::default()
229 }),
230 "create_timeout must be > 0 or None"
231 );
232 assert_eq!(
233 bad(PoolConfig {
234 max_lifetime: Some(-2.0),
235 ..Default::default()
236 }),
237 "max_lifetime must be > 0 or None"
238 );
239 assert_eq!(
240 bad(PoolConfig {
241 validate_after_idle: Some(-1.0),
242 ..Default::default()
243 }),
244 "validate_after_idle must be >= 0 or None"
245 );
246 assert_eq!(
247 bad(PoolConfig {
248 lifetime_jitter: 1.5,
249 ..Default::default()
250 }),
251 "lifetime_jitter must be within [0, 1]"
252 );
253 assert_eq!(
254 bad(PoolConfig {
255 max_uses: Some(0),
256 ..Default::default()
257 }),
258 "max_uses must be >= 1 or None"
259 );
260 assert!(
262 PoolConfig {
263 retry: RetryPolicy {
264 max_attempts: 0,
265 ..Default::default()
266 },
267 ..Default::default()
268 }
269 .validate()
270 .is_err()
271 );
272 assert!(
273 PoolConfig {
274 breaker: Some(BreakerConfig {
275 failure_threshold: 0,
276 ..Default::default()
277 }),
278 ..Default::default()
279 }
280 .validate()
281 .is_err()
282 );
283 assert!(
285 PoolConfig {
286 create_timeout: None,
287 max_lifetime: None,
288 max_idle_time: None,
289 maintenance_interval: None,
290 leak_detection_timeout: None,
291 validate_after_idle: None,
292 acquire_timeout: None,
293 breaker: None,
294 ..Default::default()
295 }
296 .validate()
297 .is_ok()
298 );
299 }
300}