ssh_commander_pg/
config.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Serialize, Deserialize)]
15pub enum PgAuthMethod {
16 Password { password: String },
19 Keychain { account: String },
22}
23
24impl std::fmt::Debug for PgAuthMethod {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 PgAuthMethod::Password { .. } => f
28 .debug_struct("PgAuthMethod::Password")
29 .field("password", &"<redacted>")
30 .finish(),
31 PgAuthMethod::Keychain { account } => f
32 .debug_struct("PgAuthMethod::Keychain")
33 .field("account", account)
34 .finish(),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
45pub enum PgTlsMode {
46 Disable,
48 #[default]
50 Prefer,
51 Require,
53 VerifyFull,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct PgConfig {
59 pub host: String,
60 pub port: u16,
61 pub database: String,
62 pub user: String,
63 pub auth: PgAuthMethod,
64 #[serde(default)]
65 pub tls: PgTlsMode,
66 #[serde(default)]
69 pub application_name: Option<String>,
70 #[serde(default)]
72 pub connect_timeout_secs: Option<u64>,
73 #[serde(default)]
77 pub max_pool_size: Option<u32>,
78 #[serde(default)]
83 pub idle_timeout_secs: Option<u64>,
84 #[serde(default)]
88 pub min_idle_connections: Option<u32>,
89}
90
91impl PgConfig {
92 pub fn local(database: impl Into<String>, user: impl Into<String>) -> Self {
95 Self {
96 host: "127.0.0.1".to_string(),
97 port: 5432,
98 database: database.into(),
99 user: user.into(),
100 auth: PgAuthMethod::Password {
101 password: String::new(),
102 },
103 tls: PgTlsMode::Disable,
104 application_name: Some("r-shell".to_string()),
105 connect_timeout_secs: Some(10),
106 max_pool_size: None,
107 idle_timeout_secs: None,
108 min_idle_connections: None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn local_defaults_disable_tls_and_set_app_name() {
119 let cfg = PgConfig::local("mydb", "alice");
120 assert_eq!(cfg.host, "127.0.0.1");
121 assert_eq!(cfg.port, 5432);
122 assert_eq!(cfg.database, "mydb");
123 assert_eq!(cfg.user, "alice");
124 assert_eq!(cfg.tls, PgTlsMode::Disable);
125 assert_eq!(cfg.application_name.as_deref(), Some("r-shell"));
126 }
127
128 #[test]
129 fn tls_mode_default_is_prefer() {
130 assert_eq!(PgTlsMode::default(), PgTlsMode::Prefer);
131 }
132
133 #[test]
134 fn config_round_trips_through_serde() {
135 let cfg = PgConfig {
136 host: "db.example.com".to_string(),
137 port: 5433,
138 database: "app".to_string(),
139 user: "svc".to_string(),
140 auth: PgAuthMethod::Keychain {
141 account: "postgres:profile-1".to_string(),
142 },
143 tls: PgTlsMode::VerifyFull,
144 application_name: Some("r-shell".to_string()),
145 connect_timeout_secs: Some(15),
146 max_pool_size: Some(10),
147 idle_timeout_secs: Some(120),
148 min_idle_connections: Some(0),
149 };
150 let json = serde_json::to_string(&cfg).expect("serialize");
151 let back: PgConfig = serde_json::from_str(&json).expect("deserialize");
152 assert_eq!(back.host, cfg.host);
153 assert_eq!(back.tls, cfg.tls);
154 assert_eq!(back.max_pool_size, Some(10));
155 assert_eq!(back.idle_timeout_secs, Some(120));
156 assert_eq!(back.min_idle_connections, Some(0));
157 }
158
159 #[test]
160 fn local_defaults_pool_settings_to_none() {
161 let cfg = PgConfig::local("db", "u");
164 assert_eq!(cfg.max_pool_size, None);
165 assert_eq!(cfg.idle_timeout_secs, None);
166 assert_eq!(cfg.min_idle_connections, None);
167 }
168
169 #[test]
170 fn debug_redacts_direct_password() {
171 let cfg = PgConfig {
172 auth: PgAuthMethod::Password {
173 password: "super-secret-pg-password".to_string(),
174 },
175 ..PgConfig::local("db", "u")
176 };
177 let rendered = format!("{cfg:?}");
178 assert!(!rendered.contains("super-secret-pg-password"));
179 assert!(rendered.contains("<redacted>"));
180 }
181
182 #[test]
183 fn debug_preserves_keychain_account() {
184 let auth = PgAuthMethod::Keychain {
185 account: "postgres:profile-1".to_string(),
186 };
187 let rendered = format!("{auth:?}");
188 assert!(rendered.contains("postgres:profile-1"));
189 }
190}