1use super::BindingValue;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
13#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
14#[serde(tag = "service", rename_all = "lowercase")]
18pub enum PostgresBinding {
19 Aurora(AuroraPostgresBinding),
21 #[serde(rename = "cloud-sql")]
23 CloudSql(CloudSqlPostgresBinding),
24 #[serde(rename = "flexible-server")]
26 FlexibleServer(FlexibleServerPostgresBinding),
27 External(ExternalPostgresBinding),
29 #[serde(rename = "local-postgres")]
31 Local(LocalPostgresBinding),
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
37#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
38#[serde(rename_all = "camelCase")]
39pub struct AuroraPostgresBinding {
40 pub cluster_endpoint: BindingValue<String>,
41 pub port: BindingValue<u16>,
42 pub database: BindingValue<String>,
43 pub username: BindingValue<String>,
44 pub password_secret_arn: BindingValue<String>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
51#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
52#[serde(rename_all = "camelCase")]
53pub struct CloudSqlPostgresBinding {
54 pub host: BindingValue<String>,
55 pub port: BindingValue<u16>,
56 pub database: BindingValue<String>,
57 pub username: BindingValue<String>,
58 pub password_secret_name: BindingValue<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
65#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
66#[serde(rename_all = "camelCase")]
67pub struct FlexibleServerPostgresBinding {
68 pub host: BindingValue<String>,
69 pub port: BindingValue<u16>,
70 pub database: BindingValue<String>,
71 pub username: BindingValue<String>,
72 pub password_secret_uri: BindingValue<String>,
74}
75
76#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
80#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
81#[serde(rename_all = "camelCase")]
82pub struct ExternalPostgresBinding {
83 pub host: BindingValue<String>,
84 pub port: BindingValue<u16>,
85 pub database: BindingValue<String>,
86 pub username: BindingValue<String>,
87 pub password: String,
91}
92
93#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
97#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
98#[serde(rename_all = "camelCase")]
99pub struct LocalPostgresBinding {
100 pub host: BindingValue<String>,
101 pub port: BindingValue<u16>,
102 pub database: BindingValue<String>,
103 pub username: BindingValue<String>,
104 pub password: String,
105}
106
107impl std::fmt::Debug for ExternalPostgresBinding {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("ExternalPostgresBinding")
112 .field("host", &self.host)
113 .field("port", &self.port)
114 .field("database", &self.database)
115 .field("username", &self.username)
116 .field("password", &"<redacted>")
117 .finish()
118 }
119}
120
121impl std::fmt::Debug for LocalPostgresBinding {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("LocalPostgresBinding")
124 .field("host", &self.host)
125 .field("port", &self.port)
126 .field("database", &self.database)
127 .field("username", &self.username)
128 .field("password", &"<redacted>")
129 .finish()
130 }
131}
132
133impl PostgresBinding {
134 pub fn local(
136 host: impl Into<BindingValue<String>>,
137 port: u16,
138 database: impl Into<BindingValue<String>>,
139 username: impl Into<BindingValue<String>>,
140 password: impl Into<String>,
141 ) -> Self {
142 Self::Local(LocalPostgresBinding {
143 host: host.into(),
144 port: BindingValue::value(port),
145 database: database.into(),
146 username: username.into(),
147 password: password.into(),
148 })
149 }
150
151 pub fn external(
153 host: impl Into<BindingValue<String>>,
154 port: u16,
155 database: impl Into<BindingValue<String>>,
156 username: impl Into<BindingValue<String>>,
157 password: impl Into<String>,
158 ) -> Self {
159 Self::External(ExternalPostgresBinding {
160 host: host.into(),
161 port: BindingValue::value(port),
162 database: database.into(),
163 username: username.into(),
164 password: password.into(),
165 })
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn local_binding_uses_local_postgres_tag() {
175 let binding = PostgresBinding::local("127.0.0.1", 5432, "db", "alien", "secret");
176 let json = serde_json::to_string(&binding).unwrap();
177 assert!(json.contains(r#""service":"local-postgres""#));
178 let deserialized: PostgresBinding = serde_json::from_str(&json).unwrap();
179 assert_eq!(binding, deserialized);
180 }
181
182 #[test]
183 fn external_binding_uses_external_tag() {
184 let binding = PostgresBinding::external("db.internal", 5432, "app", "alien", "secret");
185 let json = serde_json::to_string(&binding).unwrap();
186 assert!(json.contains(r#""service":"external""#));
187 let deserialized: PostgresBinding = serde_json::from_str(&json).unwrap();
188 assert_eq!(binding, deserialized);
189 }
190
191 #[test]
192 fn cloud_variants_keep_hyphenated_tags() {
193 let aurora = PostgresBinding::Aurora(AuroraPostgresBinding {
194 cluster_endpoint: "cluster.rds.amazonaws.com".into(),
195 port: BindingValue::value(5432),
196 database: "db".into(),
197 username: "alien".into(),
198 password_secret_arn: "arn:aws:secretsmanager:...".into(),
199 });
200 assert!(serde_json::to_string(&aurora)
201 .unwrap()
202 .contains(r#""service":"aurora""#));
203
204 let cloud_sql = PostgresBinding::CloudSql(CloudSqlPostgresBinding {
205 host: "10.0.0.5".into(),
206 port: BindingValue::value(5432),
207 database: "db".into(),
208 username: "alien".into(),
209 password_secret_name: "pg-credentials".into(),
210 });
211 assert!(serde_json::to_string(&cloud_sql)
212 .unwrap()
213 .contains(r#""service":"cloud-sql""#));
214
215 let flexible = PostgresBinding::FlexibleServer(FlexibleServerPostgresBinding {
216 host: "10.0.0.6".into(),
217 port: BindingValue::value(5432),
218 database: "db".into(),
219 username: "alien".into(),
220 password_secret_uri: "https://vault.vault.azure.net/secrets/pg".into(),
221 });
222 assert!(serde_json::to_string(&flexible)
223 .unwrap()
224 .contains(r#""service":"flexible-server""#));
225 }
226}