use crate::store::error::{Error, Result};
pub trait AdapterConfig: Send + Sync {
fn adapter_name(&self) -> &'static str;
fn validate(&self) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub url: String,
pub pool_size: Option<u32>,
pub connect_timeout_secs: u64,
pub acquire_timeout_secs: u64,
}
impl ConnectionConfig {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
pool_size: None,
connect_timeout_secs: 10,
acquire_timeout_secs: 30,
}
}
}
impl AdapterConfig for ConnectionConfig {
fn adapter_name(&self) -> &'static str {
"connection"
}
fn validate(&self) -> Result<()> {
if self.url.is_empty() {
return Err(Error::config("connection URL must not be empty"));
}
if self.connect_timeout_secs == 0 {
return Err(Error::config("connect_timeout_secs must be greater than zero"));
}
if self.acquire_timeout_secs == 0 {
return Err(Error::config("acquire_timeout_secs must be greater than zero"));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn valid_config() -> ConnectionConfig {
ConnectionConfig::new("postgres://localhost/test")
}
#[test]
fn new_stores_url() {
let c = valid_config();
assert_eq!(c.url, "postgres://localhost/test");
}
#[test]
fn new_has_no_pool_size() {
let c = valid_config();
assert!(c.pool_size.is_none());
}
#[test]
fn new_has_default_connect_timeout() {
let c = valid_config();
assert_eq!(c.connect_timeout_secs, 10);
}
#[test]
fn new_has_default_acquire_timeout() {
let c = valid_config();
assert_eq!(c.acquire_timeout_secs, 30);
}
#[test]
fn adapter_name_is_connection() {
assert_eq!(valid_config().adapter_name(), "connection");
}
#[test]
fn validate_passes_for_valid_config() {
assert!(valid_config().validate().is_ok());
}
#[test]
fn validate_passes_with_explicit_pool_size() {
let mut c = valid_config();
c.pool_size = Some(10);
assert!(c.validate().is_ok());
}
#[test]
fn validate_fails_for_empty_url() {
let c = ConnectionConfig::new("");
let err = c.validate().unwrap_err();
assert!(matches!(err, Error::Configuration(_)));
assert!(err.to_string().contains("URL must not be empty"));
}
#[test]
fn validate_fails_for_zero_connect_timeout() {
let mut c = valid_config();
c.connect_timeout_secs = 0;
let err = c.validate().unwrap_err();
assert!(matches!(err, Error::Configuration(_)));
assert!(err.to_string().contains("connect_timeout_secs"));
}
#[test]
fn validate_fails_for_zero_acquire_timeout() {
let mut c = valid_config();
c.acquire_timeout_secs = 0;
let err = c.validate().unwrap_err();
assert!(matches!(err, Error::Configuration(_)));
assert!(err.to_string().contains("acquire_timeout_secs"));
}
#[test]
fn clone_is_independent() {
let c = valid_config();
let mut cloned = c.clone();
cloned.url = "other://url".to_string();
assert_eq!(c.url, "postgres://localhost/test");
}
struct MinimalConfig {
pub value: u32,
}
impl AdapterConfig for MinimalConfig {
fn adapter_name(&self) -> &'static str {
"minimal"
}
fn validate(&self) -> Result<()> {
if self.value == 0 {
return Err(Error::config("value must be non-zero"));
}
Ok(())
}
}
#[test]
fn custom_config_validate_passes() {
let cfg = MinimalConfig { value: 1 };
assert!(cfg.validate().is_ok());
assert_eq!(cfg.adapter_name(), "minimal");
}
#[test]
fn custom_config_validate_fails() {
let cfg = MinimalConfig { value: 0 };
assert!(cfg.validate().is_err());
}
}