use crate::error::{ErrorData, Result};
use crate::providers::postgres::{resolve_params, PostgresConnectionInput, ResolvedPostgres};
use crate::traits::PostgresTlsPolicy;
use alien_core::bindings::{ExternalPostgresSslMode, PostgresBinding};
use alien_error::AlienError;
pub type LocalPostgres = ResolvedPostgres;
impl ResolvedPostgres {
pub fn from_binding(binding_name: &str, binding: &PostgresBinding) -> Result<Self> {
let params = match binding {
PostgresBinding::Local(b) => resolve_params(
binding_name,
PostgresConnectionInput {
host: &b.host,
port: &b.port,
database: &b.database,
username: &b.username,
password: &b.password,
tls: PostgresTlsPolicy::disabled(),
},
)?,
PostgresBinding::External(b) => {
let tls = match b.ssl_mode {
ExternalPostgresSslMode::VerifyFull => {
PostgresTlsPolicy::verify_full_with_system_roots()
}
ExternalPostgresSslMode::Disable => PostgresTlsPolicy::disabled(),
};
resolve_params(
binding_name,
PostgresConnectionInput {
host: &b.host,
port: &b.port,
database: &b.database,
username: &b.username,
password: &b.password,
tls,
},
)?
}
PostgresBinding::Aurora(_)
| PostgresBinding::CloudSql(_)
| PostgresBinding::FlexibleServer(_) => {
return Err(AlienError::new(ErrorData::config_invalid(
binding_name,
"Cloud Postgres bindings carry a password secret locator and must be \
resolved by their own cloud provider, not the inline-password provider",
)));
}
};
Ok(Self::new(params))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{Postgres, SslMode};
use alien_core::bindings::BindingValue;
#[test]
fn local_binding_resolves_to_disable_sslmode_connection_string() {
let binding = PostgresBinding::local("127.0.0.1", 6543, "db", "alien", "p@ss/word");
let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves");
let params = pg.connection_params();
assert_eq!(params.host, "127.0.0.1");
assert_eq!(params.port, 6543);
assert_eq!(params.sslmode(), SslMode::Disable);
assert_eq!(
pg.connection_string(),
"postgres://alien:p%40ss%2Fword@127.0.0.1:6543/db?sslmode=disable"
);
}
#[test]
fn external_binding_defaults_to_verify_full_sslmode() {
let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "p@ss/word");
let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves");
assert_eq!(pg.connection_params().sslmode(), SslMode::VerifyFull);
assert_eq!(
pg.connection_string(),
"postgres://alien:p%40ss%2Fword@db.internal:5432/app?sslmode=verify-full"
);
}
#[test]
fn external_binding_allows_explicit_plaintext_opt_out() {
let binding: PostgresBinding = serde_json::from_value(serde_json::json!({
"service": "external",
"host": "db.internal",
"port": 5432,
"database": "app",
"username": "alien",
"password": "secret",
"sslMode": "disable",
}))
.expect("external plaintext binding deserializes");
let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves");
assert_eq!(pg.connection_params().sslmode(), SslMode::Disable);
assert_eq!(
pg.connection_string(),
"postgres://alien:secret@db.internal:5432/app?sslmode=disable"
);
}
#[test]
fn connection_string_percent_encodes_rfc3986_sub_delims() {
let binding = PostgresBinding::local("h", 5432, "db", "alien", "a!b*c'd(e)f");
let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves");
assert_eq!(
pg.connection_string(),
"postgres://alien:a%21b%2Ac%27d%28e%29f@h:5432/db?sslmode=disable"
);
}
#[test]
fn cloud_binding_is_rejected_by_the_inline_password_provider() {
let binding = PostgresBinding::Aurora(alien_core::bindings::AuroraPostgresBinding {
cluster_endpoint: "cluster.rds.amazonaws.com".into(),
port: BindingValue::value(5432),
database: "db".into(),
username: "alien".into(),
password_secret_arn: "arn:aws:secretsmanager:...".into(),
});
let error = ResolvedPostgres::from_binding("db", &binding)
.expect_err("cloud bindings must not resolve without their secret");
assert_eq!(error.code, "BINDING_CONFIG_INVALID");
}
}