use super::BindingValue;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(tag = "service", rename_all = "lowercase")]
pub enum PostgresBinding {
Aurora(AuroraPostgresBinding),
#[serde(rename = "cloud-sql")]
CloudSql(CloudSqlPostgresBinding),
#[serde(rename = "flexible-server")]
FlexibleServer(FlexibleServerPostgresBinding),
External(ExternalPostgresBinding),
#[serde(rename = "local-postgres")]
Local(LocalPostgresBinding),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct AuroraPostgresBinding {
pub cluster_endpoint: BindingValue<String>,
pub port: BindingValue<u16>,
pub database: BindingValue<String>,
pub username: BindingValue<String>,
pub password_secret_arn: BindingValue<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct CloudSqlPostgresBinding {
pub host: BindingValue<String>,
pub port: BindingValue<u16>,
pub database: BindingValue<String>,
pub username: BindingValue<String>,
pub password_secret_name: BindingValue<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct FlexibleServerPostgresBinding {
pub host: BindingValue<String>,
pub port: BindingValue<u16>,
pub database: BindingValue<String>,
pub username: BindingValue<String>,
pub password_secret_uri: BindingValue<String>,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct ExternalPostgresBinding {
pub host: BindingValue<String>,
pub port: BindingValue<u16>,
pub database: BindingValue<String>,
pub username: BindingValue<String>,
pub password: String,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct LocalPostgresBinding {
pub host: BindingValue<String>,
pub port: BindingValue<u16>,
pub database: BindingValue<String>,
pub username: BindingValue<String>,
pub password: String,
}
impl std::fmt::Debug for ExternalPostgresBinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExternalPostgresBinding")
.field("host", &self.host)
.field("port", &self.port)
.field("database", &self.database)
.field("username", &self.username)
.field("password", &"<redacted>")
.finish()
}
}
impl std::fmt::Debug for LocalPostgresBinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LocalPostgresBinding")
.field("host", &self.host)
.field("port", &self.port)
.field("database", &self.database)
.field("username", &self.username)
.field("password", &"<redacted>")
.finish()
}
}
impl PostgresBinding {
pub fn local(
host: impl Into<BindingValue<String>>,
port: u16,
database: impl Into<BindingValue<String>>,
username: impl Into<BindingValue<String>>,
password: impl Into<String>,
) -> Self {
Self::Local(LocalPostgresBinding {
host: host.into(),
port: BindingValue::value(port),
database: database.into(),
username: username.into(),
password: password.into(),
})
}
pub fn external(
host: impl Into<BindingValue<String>>,
port: u16,
database: impl Into<BindingValue<String>>,
username: impl Into<BindingValue<String>>,
password: impl Into<String>,
) -> Self {
Self::External(ExternalPostgresBinding {
host: host.into(),
port: BindingValue::value(port),
database: database.into(),
username: username.into(),
password: password.into(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn local_binding_uses_local_postgres_tag() {
let binding = PostgresBinding::local("127.0.0.1", 5432, "db", "alien", "secret");
let json = serde_json::to_string(&binding).unwrap();
assert!(json.contains(r#""service":"local-postgres""#));
let deserialized: PostgresBinding = serde_json::from_str(&json).unwrap();
assert_eq!(binding, deserialized);
}
#[test]
fn external_binding_uses_external_tag() {
let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "secret");
let json = serde_json::to_string(&binding).unwrap();
assert!(json.contains(r#""service":"external""#));
let deserialized: PostgresBinding = serde_json::from_str(&json).unwrap();
assert_eq!(binding, deserialized);
}
#[test]
fn cloud_variants_keep_hyphenated_tags() {
let aurora = PostgresBinding::Aurora(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(),
});
assert!(serde_json::to_string(&aurora)
.unwrap()
.contains(r#""service":"aurora""#));
let cloud_sql = PostgresBinding::CloudSql(CloudSqlPostgresBinding {
host: "10.0.0.5".into(),
port: BindingValue::value(5432),
database: "db".into(),
username: "alien".into(),
password_secret_name: "pg-credentials".into(),
});
assert!(serde_json::to_string(&cloud_sql)
.unwrap()
.contains(r#""service":"cloud-sql""#));
let flexible = PostgresBinding::FlexibleServer(FlexibleServerPostgresBinding {
host: "10.0.0.6".into(),
port: BindingValue::value(5432),
database: "db".into(),
username: "alien".into(),
password_secret_uri: "https://vault.vault.azure.net/secrets/pg".into(),
});
assert!(serde_json::to_string(&flexible)
.unwrap()
.contains(r#""service":"flexible-server""#));
}
}