use faucet_core::FaucetError;
use sqlx::PgPool;
use sqlx::postgres::{PgConnectOptions, PgPoolOptions, PgSslMode};
use crate::config::{RedshiftConnection, RedshiftCredentials};
pub fn resolve_password(creds: &RedshiftCredentials) -> Result<&str, FaucetError> {
match creds {
RedshiftCredentials::Password { password } => Ok(password.as_str()),
RedshiftCredentials::Iam { .. } => Err(FaucetError::Config(
"redshift: IAM authentication is not yet supported (v1 supports password auth only) \
— use credentials: { type: password, config: { password: … } }"
.into(),
)),
RedshiftCredentials::RedshiftDataApi { .. } => Err(FaucetError::Config(
"redshift: Redshift Data API authentication is not yet supported (v1 supports \
password auth only) — use credentials: { type: password, config: { password: … } }"
.into(),
)),
}
}
pub fn build_connect_options(conn: &RedshiftConnection) -> Result<PgConnectOptions, FaucetError> {
if conn.host.trim().is_empty() {
return Err(FaucetError::Config(
"redshift: `host` must not be empty".into(),
));
}
if conn.database.trim().is_empty() {
return Err(FaucetError::Config(
"redshift: `database` must not be empty".into(),
));
}
if conn.user.trim().is_empty() {
return Err(FaucetError::Config(
"redshift: `user` must not be empty".into(),
));
}
let password = resolve_password(&conn.credentials)?;
let ssl_mode = if conn.tls {
PgSslMode::Require
} else {
PgSslMode::Prefer
};
Ok(PgConnectOptions::new()
.host(&conn.host)
.port(conn.port)
.database(&conn.database)
.username(&conn.user)
.password(password)
.ssl_mode(ssl_mode)
.application_name("faucet"))
}
pub fn build_pool_lazy(
conn: &RedshiftConnection,
max_connections: u32,
) -> Result<PgPool, FaucetError> {
let opts = build_connect_options(conn)?;
Ok(PgPoolOptions::new()
.max_connections(max_connections.max(1))
.connect_lazy_with(opts))
}
pub async fn build_pool(
conn: &RedshiftConnection,
max_connections: u32,
) -> Result<PgPool, FaucetError> {
let opts = build_connect_options(conn)?;
PgPoolOptions::new()
.max_connections(max_connections.max(1))
.connect_with(opts)
.await
.map_err(|e| FaucetError::Config(format!("redshift connection failed: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
fn conn() -> RedshiftConnection {
RedshiftConnection::new("host.example.com", "dev", "admin", "pw")
}
#[test]
fn build_options_succeeds_for_password() {
assert!(build_connect_options(&conn()).is_ok());
}
#[test]
fn build_options_rejects_empty_host() {
let mut c = conn();
c.host = " ".into();
assert!(matches!(
build_connect_options(&c),
Err(FaucetError::Config(_))
));
}
#[test]
fn build_options_rejects_empty_database() {
let mut c = conn();
c.database = String::new();
assert!(matches!(
build_connect_options(&c),
Err(FaucetError::Config(_))
));
}
#[test]
fn build_options_rejects_empty_user() {
let mut c = conn();
c.user = String::new();
assert!(matches!(
build_connect_options(&c),
Err(FaucetError::Config(_))
));
}
#[test]
fn resolve_password_returns_password() {
let creds = RedshiftCredentials::Password {
password: "hunter2".into(),
};
assert_eq!(resolve_password(&creds).unwrap(), "hunter2");
}
#[test]
fn resolve_password_rejects_iam_with_typed_error() {
let creds = RedshiftCredentials::Iam {
region: None,
cluster_identifier: None,
db_user: None,
};
match resolve_password(&creds) {
Err(FaucetError::Config(m)) => assert!(m.contains("IAM"), "got: {m}"),
other => panic!("expected Config error, got {other:?}"),
}
}
#[test]
fn resolve_password_rejects_data_api_with_typed_error() {
let creds = RedshiftCredentials::RedshiftDataApi {
region: None,
cluster_identifier: None,
workgroup_name: None,
secret_arn: None,
db_user: None,
};
match resolve_password(&creds) {
Err(FaucetError::Config(m)) => assert!(m.contains("Data API"), "got: {m}"),
other => panic!("expected Config error, got {other:?}"),
}
}
#[test]
fn build_connect_options_surfaces_unsupported_credentials() {
let mut c = conn();
c.credentials = RedshiftCredentials::Iam {
region: None,
cluster_identifier: None,
db_user: None,
};
assert!(build_connect_options(&c).is_err());
}
#[tokio::test]
async fn build_pool_lazy_does_no_io() {
let pool = build_pool_lazy(&conn(), 4).unwrap();
assert_eq!(pool.size(), 0);
}
}