alien_bindings/providers/postgres/
local.rs1use crate::error::{ErrorData, Result};
2use crate::providers::postgres::{resolve_params, PostgresConnectionInput, ResolvedPostgres};
3use crate::traits::PostgresTlsPolicy;
4use alien_core::bindings::{ExternalPostgresSslMode, PostgresBinding};
5use alien_error::AlienError;
6
7pub type LocalPostgres = ResolvedPostgres;
12
13impl ResolvedPostgres {
14 pub fn from_binding(binding_name: &str, binding: &PostgresBinding) -> Result<Self> {
21 let params = match binding {
22 PostgresBinding::Local(b) => resolve_params(
23 binding_name,
24 PostgresConnectionInput {
25 host: &b.host,
26 port: &b.port,
27 database: &b.database,
28 username: &b.username,
29 password: &b.password,
32 tls: PostgresTlsPolicy::disabled(),
33 },
34 )?,
35 PostgresBinding::External(b) => {
36 let tls = match b.ssl_mode {
37 ExternalPostgresSslMode::VerifyFull => {
38 PostgresTlsPolicy::verify_full_with_system_roots()
39 }
40 ExternalPostgresSslMode::Disable => PostgresTlsPolicy::disabled(),
41 };
42 resolve_params(
43 binding_name,
44 PostgresConnectionInput {
45 host: &b.host,
46 port: &b.port,
47 database: &b.database,
48 username: &b.username,
49 password: &b.password,
50 tls,
51 },
52 )?
53 }
54 PostgresBinding::Aurora(_)
59 | PostgresBinding::CloudSql(_)
60 | PostgresBinding::FlexibleServer(_) => {
61 return Err(AlienError::new(ErrorData::config_invalid(
62 binding_name,
63 "Cloud Postgres bindings carry a password secret locator and must be \
64 resolved by their own cloud provider, not the inline-password provider",
65 )));
66 }
67 };
68 Ok(Self::new(params))
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::traits::{Postgres, SslMode};
76 use alien_core::bindings::BindingValue;
77
78 #[test]
79 fn local_binding_resolves_to_disable_sslmode_connection_string() {
80 let binding = PostgresBinding::local("127.0.0.1", 6543, "db", "alien", "p@ss/word");
81 let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves");
82 let params = pg.connection_params();
83 assert_eq!(params.host, "127.0.0.1");
84 assert_eq!(params.port, 6543);
85 assert_eq!(params.sslmode(), SslMode::Disable);
86 assert_eq!(
88 pg.connection_string(),
89 "postgres://alien:p%40ss%2Fword@127.0.0.1:6543/db?sslmode=disable"
90 );
91 }
92
93 #[test]
94 fn external_binding_defaults_to_verify_full_sslmode() {
95 let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "p@ss/word");
96 let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves");
97
98 assert_eq!(pg.connection_params().sslmode(), SslMode::VerifyFull);
99 assert_eq!(
100 pg.connection_string(),
101 "postgres://alien:p%40ss%2Fword@db.internal:5432/app?sslmode=verify-full"
102 );
103 }
104
105 #[test]
106 fn external_binding_allows_explicit_plaintext_opt_out() {
107 let binding: PostgresBinding = serde_json::from_value(serde_json::json!({
108 "service": "external",
109 "host": "db.internal",
110 "port": 5432,
111 "database": "app",
112 "username": "alien",
113 "password": "secret",
114 "sslMode": "disable",
115 }))
116 .expect("external plaintext binding deserializes");
117 let pg = ResolvedPostgres::from_binding("db", &binding).expect("external binding resolves");
118
119 assert_eq!(pg.connection_params().sslmode(), SslMode::Disable);
120 assert_eq!(
121 pg.connection_string(),
122 "postgres://alien:secret@db.internal:5432/app?sslmode=disable"
123 );
124 }
125
126 #[test]
132 fn connection_string_percent_encodes_rfc3986_sub_delims() {
133 let binding = PostgresBinding::local("h", 5432, "db", "alien", "a!b*c'd(e)f");
134 let pg = ResolvedPostgres::from_binding("db", &binding).expect("local binding resolves");
135 assert_eq!(
136 pg.connection_string(),
137 "postgres://alien:a%21b%2Ac%27d%28e%29f@h:5432/db?sslmode=disable"
138 );
139 }
140
141 #[test]
144 fn cloud_binding_is_rejected_by_the_inline_password_provider() {
145 let binding = PostgresBinding::Aurora(alien_core::bindings::AuroraPostgresBinding {
146 cluster_endpoint: "cluster.rds.amazonaws.com".into(),
147 port: BindingValue::value(5432),
148 database: "db".into(),
149 username: "alien".into(),
150 password_secret_arn: "arn:aws:secretsmanager:...".into(),
151 });
152
153 let error = ResolvedPostgres::from_binding("db", &binding)
154 .expect_err("cloud bindings must not resolve without their secret");
155
156 assert_eq!(error.code, "BINDING_CONFIG_INVALID");
157 }
158}