use std::time::Duration;
use crate::sql::connect_diagnosis::{redact, ConnectDiagnosis, ConnectFault};
use crate::sql::Pool;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
pub struct Preflight {
pub timeout: Duration,
pub probe_writes: bool,
}
impl Default for Preflight {
fn default() -> Self {
Self {
timeout: DEFAULT_TIMEOUT,
probe_writes: true,
}
}
}
impl Preflight {
#[must_use]
pub fn read_only() -> Self {
Self {
probe_writes: false,
..Self::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reachable {
pub endpoint: String,
pub writes_verified: bool,
}
pub async fn check(url: &str, opts: &Preflight) -> Result<Reachable, ConnectDiagnosis> {
let pool = Pool::connect_diagnosed(url, opts.timeout).await?;
let result = probe(&pool, url, opts).await;
pool.close().await;
result
}
async fn probe(pool: &Pool, url: &str, opts: &Preflight) -> Result<Reachable, ConnectDiagnosis> {
raw(pool, "SELECT 1").await.map_err(|e| diagnose(url, &e))?;
if opts.probe_writes {
verify_writes(pool, url).await?;
}
Ok(Reachable {
endpoint: redact(url),
writes_verified: opts.probe_writes,
})
}
async fn raw(pool: &Pool, sql: &str) -> Result<(), crate::sql::ExecError> {
crate::sql::raw_execute_pool(pool, sql, Vec::new()).await?;
Ok(())
}
fn diagnose(url: &str, e: &crate::sql::ExecError) -> ConnectDiagnosis {
match e {
crate::sql::ExecError::Driver(inner) => ConnectDiagnosis::of(url, inner),
other => ConnectDiagnosis::new(ConnectFault::Other, url, other.to_string()),
}
}
async fn verify_writes(pool: &Pool, url: &str) -> Result<(), ConnectDiagnosis> {
static SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let table = format!(
"rustango_preflight_{}_{}",
std::process::id(),
SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
);
if let Err(e) = raw(pool, &format!("CREATE TABLE {table} (id INTEGER)")).await {
let d = diagnose(url, &e);
return Err(match d.fault {
ConnectFault::Other => {
ConnectDiagnosis::new(ConnectFault::PermissionDenied, url, d.detail)
}
_ => d,
});
}
let _ = raw(pool, &format!("DROP TABLE {table}")).await;
Ok(())
}