alien_bindings/providers/postgres/
local.rs1use crate::error::{ErrorData, Result};
2use crate::traits::{Binding, Postgres, PostgresConnectionParams, SslMode};
3use alien_core::bindings::{BindingValue, PostgresBinding};
4use alien_error::{AlienError, Context};
5
6#[derive(Debug)]
9pub struct LocalPostgres {
10 params: PostgresConnectionParams,
11}
12
13impl LocalPostgres {
14 pub fn new(params: PostgresConnectionParams) -> Self {
15 Self { params }
16 }
17
18 pub fn from_binding(binding_name: &str, binding: &PostgresBinding) -> Result<Self> {
25 let params = match binding {
26 PostgresBinding::Local(b) => resolve_params(
27 binding_name,
28 &b.host,
29 &b.port,
30 &b.database,
31 &b.username,
32 &b.password,
33 SslMode::Disable,
34 )?,
35 PostgresBinding::External(b) => resolve_params(
36 binding_name,
37 &b.host,
38 &b.port,
39 &b.database,
40 &b.username,
41 &b.password,
42 SslMode::Prefer,
43 )?,
44 PostgresBinding::Aurora(_)
49 | PostgresBinding::CloudSql(_)
50 | PostgresBinding::FlexibleServer(_) => {
51 let backend = match binding {
52 PostgresBinding::Aurora(_) => "Aurora (AWS)",
53 PostgresBinding::CloudSql(_) => "Cloud SQL (GCP)",
54 PostgresBinding::FlexibleServer(_) => "Azure Flexible Server",
55 _ => "cloud",
56 };
57 return Err(AlienError::new(ErrorData::BindingConfigInvalid {
58 binding_name: binding_name.to_string(),
59 reason: format!(
60 "{backend} Postgres bindings are resolved in-process by the workload SDK, \
61 not this Rust provider"
62 ),
63 }));
64 }
65 };
66 Ok(Self::new(params))
67 }
68}
69
70#[allow(clippy::too_many_arguments)]
71fn resolve_params(
72 binding_name: &str,
73 host: &BindingValue<String>,
74 port: &BindingValue<u16>,
75 database: &BindingValue<String>,
76 username: &BindingValue<String>,
77 password: &str,
78 sslmode: SslMode,
79) -> Result<PostgresConnectionParams> {
80 let invalid = |field: &str| ErrorData::BindingConfigInvalid {
81 binding_name: binding_name.to_string(),
82 reason: format!("Failed to extract '{}' from Postgres binding", field),
83 };
84 Ok(PostgresConnectionParams {
85 host: host
86 .clone()
87 .into_value(binding_name, "host")
88 .context(invalid("host"))?,
89 port: port
90 .clone()
91 .into_value(binding_name, "port")
92 .context(invalid("port"))?,
93 database: database
94 .clone()
95 .into_value(binding_name, "database")
96 .context(invalid("database"))?,
97 username: username
98 .clone()
99 .into_value(binding_name, "username")
100 .context(invalid("username"))?,
101 password: password.to_string(),
103 sslmode,
104 })
105}
106
107impl Binding for LocalPostgres {}
108
109impl Postgres for LocalPostgres {
110 fn connection_params(&self) -> PostgresConnectionParams {
111 self.params.clone()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn local_binding_resolves_to_disable_sslmode_connection_string() {
121 let binding = PostgresBinding::local("127.0.0.1", 6543, "db", "alien", "p@ss/word");
122 let pg = LocalPostgres::from_binding("db", &binding).expect("local binding resolves");
123 let params = pg.connection_params();
124 assert_eq!(params.host, "127.0.0.1");
125 assert_eq!(params.port, 6543);
126 assert_eq!(
128 pg.connection_string(),
129 "postgres://alien:p%40ss%2Fword@127.0.0.1:6543/db?sslmode=disable"
130 );
131 }
132
133 #[test]
138 fn connection_string_percent_encodes_rfc3986_sub_delims() {
139 let binding = PostgresBinding::local("h", 5432, "db", "alien", "a!b*c'd(e)f");
140 let pg = LocalPostgres::from_binding("db", &binding).expect("local binding resolves");
141 assert_eq!(
142 pg.connection_string(),
143 "postgres://alien:a%21b%2Ac%27d%28e%29f@h:5432/db?sslmode=disable"
144 );
145 }
146
147 #[test]
148 fn cloud_binding_resolution_is_rejected_in_this_build() {
149 let binding = PostgresBinding::Aurora(alien_core::bindings::AuroraPostgresBinding {
150 cluster_endpoint: "cluster.rds.amazonaws.com".into(),
151 port: BindingValue::value(5432),
152 database: "db".into(),
153 username: "alien".into(),
154 password_secret_arn: "arn:aws:secretsmanager:...".into(),
155 });
156 assert!(LocalPostgres::from_binding("db", &binding).is_err());
157 }
158}