use diesel::{
PgConnection,
r2d2::{Builder, ConnectionManager, Pool},
};
use crate::{Error, PgPool};
pub fn build_pool(database_url: impl AsRef<str>) -> Result<PgPool, Error> {
build_pool_with(database_url, |builder| builder)
}
pub fn build_pool_with<F>(database_url: impl AsRef<str>, configure: F) -> Result<PgPool, Error>
where
F: FnOnce(Builder<ConnectionManager<PgConnection>>) -> Builder<ConnectionManager<PgConnection>>,
{
let manager = ConnectionManager::<PgConnection>::new(database_url.as_ref());
configure(Pool::builder())
.build(manager)
.map_err(Error::from)
}
#[cfg(test)]
mod tests {
use std::error::Error as StdError;
use std::time::Duration;
use lets_expect::{AssertionError, AssertionResult, *};
use super::*;
fn lazy_pool_with<F>(url: &'static str, configure: F) -> Result<PgPool, Error>
where
F: FnOnce(
Builder<ConnectionManager<PgConnection>>,
) -> Builder<ConnectionManager<PgConnection>>,
{
build_pool_with(url, |builder| configure(builder.min_idle(Some(0))))
}
fn default_pool(url: &'static str) -> Result<PgPool, Error> {
lazy_pool_with(url, |builder| builder)
}
fn pool_with_max_size(url: &'static str, max_size: u32) -> Result<PgPool, Error> {
lazy_pool_with(url, move |builder| {
builder
.max_size(max_size)
.connection_timeout(Duration::from_millis(5))
})
}
fn equals_max_size(expected: u32) -> impl Fn(&PgPool) -> AssertionResult {
move |pool| {
if pool.max_size() == expected {
Ok(())
} else {
Err(AssertionError::new(vec![format!(
"expected max_size {expected}, got {}",
pool.max_size()
)]))
}
}
}
fn eager_pool(url: &'static str) -> Result<PgPool, Error> {
build_pool_with(url, |builder| {
builder
.max_size(1)
.connection_timeout(Duration::from_millis(50))
})
}
fn equals_connection_timeout(expected: Duration) -> impl Fn(&PgPool) -> AssertionResult {
move |pool| {
if pool.connection_timeout() == expected {
Ok(())
} else {
Err(AssertionError::new(vec![format!(
"expected connection_timeout {expected:?}, got {:?}",
pool.connection_timeout()
)]))
}
}
}
fn displays_pool_acquisition_failure(error: &Error) -> AssertionResult {
let actual = error.to_string();
if actual.starts_with("failed to acquire PostgreSQL connection from r2d2 pool: ")
&& actual.contains("check that DATABASE_URL points to a reachable PostgreSQL server")
{
Ok(())
} else {
Err(AssertionError::new(vec![format!(
"expected pool acquisition failure display, got {actual:?}"
)]))
}
}
fn exposes_pool_error_as_source(error: &Error) -> AssertionResult {
match StdError::source(error) {
Some(_) => Ok(()),
None => Err(AssertionError::new(vec![
"expected the r2d2 pool error to be exposed as the source, got no source"
.to_owned(),
])),
}
}
lets_expect! {
expect(default_pool(url)) {
let url = "postgres://127.0.0.1:1/unused";
when build_pool_with_uses_the_r2d2_defaults {
to returns_a_pool_with_the_r2d2_default_capacity {
be_ok_and equals_max_size(10)
}
to returns_a_pool_with_the_r2d2_default_connection_timeout {
be_ok_and equals_connection_timeout(Duration::from_secs(30))
}
}
}
expect(pool_with_max_size(url, max_size)) {
let url = "postgres://127.0.0.1:1/unused";
let max_size = 4;
when build_pool_with_applies_a_custom_max_size_and_timeout {
to honours_the_supplied_max_size {
be_ok_and equals_max_size(4)
}
to honours_the_supplied_connection_timeout {
be_ok_and equals_connection_timeout(Duration::from_millis(5))
}
}
when build_pool_with_applies_a_minimum_capacity {
let max_size = 1;
to keeps_the_supplied_capacity {
be_ok_and equals_max_size(1)
}
}
}
expect(eager_pool(url)) {
let url = "postgres://127.0.0.1:1/unused";
when the_server_is_unreachable_and_initialization_is_eager {
to returns_a_pool_error {
be_err_and match_pattern!(Error::Pool(_))
}
to displays_the_pool_acquisition_failure {
be_err_and displays_pool_acquisition_failure
}
to exposes_the_pool_error_as_the_source {
be_err_and exposes_pool_error_as_source
}
}
}
}
}