use crate::{PgBackend, classify};
use persistant_relational::{ConnectionSecret, RelError};
use sqlx::postgres::{PgConnectOptions, PgPoolOptions, PgSslMode};
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum TlsPolicy {
Disable,
Prefer,
#[default]
Require,
VerifyCa {
root_cert: Option<PathBuf>,
},
VerifyFull {
root_cert: Option<PathBuf>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectOptions {
pub tls: TlsPolicy,
pub max_connections: u32,
pub acquire_timeout: Duration,
}
impl Default for ConnectOptions {
fn default() -> Self {
ConnectOptions {
tls: TlsPolicy::default(),
max_connections: 16,
acquire_timeout: Duration::from_secs(10),
}
}
}
impl PgBackend {
pub async fn connect_with(
secret: &ConnectionSecret,
options: &ConnectOptions,
) -> Result<Self, RelError> {
let url = secret.resolve()?;
let mut o = PgConnectOptions::from_str(url.expose())
.map_err(|_| RelError::Backend("connection string is not valid".to_string()))?;
o = match &options.tls {
TlsPolicy::Disable => o.ssl_mode(PgSslMode::Disable),
TlsPolicy::Prefer => o.ssl_mode(PgSslMode::Prefer),
TlsPolicy::Require => o.ssl_mode(PgSslMode::Require),
TlsPolicy::VerifyCa { root_cert } => {
let o = o.ssl_mode(PgSslMode::VerifyCa);
match root_cert {
Some(p) => o.ssl_root_cert(p),
None => o,
}
}
TlsPolicy::VerifyFull { root_cert } => {
let o = o.ssl_mode(PgSslMode::VerifyFull);
match root_cert {
Some(p) => o.ssl_root_cert(p),
None => o,
}
}
};
let pool = PgPoolOptions::new()
.max_connections(options.max_connections)
.acquire_timeout(options.acquire_timeout)
.connect_with(o)
.await
.map_err(classify)?;
Self::from_pool(pool).await
}
pub async fn connection_is_encrypted(&self) -> Result<bool, RelError> {
sqlx::query_scalar("SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid()")
.fetch_one(&self.pool)
.await
.map_err(classify)
}
}