1use std::time::Duration;
2
3use duration_string::DurationString;
4use secrecy::{ExposeSecret, SecretString};
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct Config {
10 pub user: String,
11 pub dbname: String,
12 pub host: String,
13 pub port: u16,
14 #[serde(skip_serializing)]
15 pub password: Option<SecretString>,
16 pub connect_timeout: Option<DurationString>,
17 pub options: Option<String>,
18}
19
20impl Config {
21 pub fn connection_string(&self) -> SecretString {
22 SecretString::from(format!(
23 "user={user} dbname={dbname} host={host} port={port}{password}{timeout}{options}",
24 user = self.user,
25 dbname = self.dbname,
26 host = self.host,
27 port = self.port,
28 password = match &self.password {
29 Some(p) => format!(" password={}", p.expose_secret()),
30 None => "".to_string(),
31 },
32 timeout = match self.connect_timeout {
33 Some(c) => {
34 let d: Duration = c.into();
35 format!(" connect_timeout={}", d.as_secs())
36 }
37 None => "".to_string(),
38 },
39 options = match &self.options {
40 Some(o) => format!(" {}", o),
41 None => "".to_string(),
42 },
43 ))
44 }
45
46 pub fn connection_url(&self) -> SecretString {
47 SecretString::from(format!(
48 "postgresql://{user}{password}@{host}:{port}/{dbname}{timeout}{options}",
49 user = self.user,
50 password = match &self.password {
51 Some(p) => format!(":{}", p.expose_secret()),
52 None => "".to_string(),
53 },
54 host = self.host,
55 port = self.port,
56 dbname = self.dbname,
57 timeout = match self.connect_timeout {
58 Some(c) => {
59 let d: Duration = c.into();
60 format!("?connect_timeout={}", d.as_secs())
61 }
62 None => "".to_string(),
63 },
64 options = match &self.options {
65 Some(o) => {
66 format!(
67 "{}{}",
68 match self.connect_timeout {
69 Some(_) => "&".to_string(),
70 None => "?".to_string(),
71 },
72 o
73 )
74 }
75 None => "".to_string(),
76 }
77 ))
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use duration_string::DurationString;
84 use secrecy::ExposeSecret;
85
86 use crate::postgres::Config;
87
88 #[test]
89 fn connection_string() {
90 [
91 (
92 Config { ..init_config() },
93 "user=user dbname=dbname host=host port=1111 password=password connect_timeout=1 option=option".to_string(),
94 ),
95 (
96 Config {
97 password: None,
98 ..init_config()
99 },
100 "user=user dbname=dbname host=host port=1111 connect_timeout=1 option=option".to_string(),
101 ),
102 (
103 Config {
104 connect_timeout: None,
105 ..init_config()
106 },
107 "user=user dbname=dbname host=host port=1111 password=password option=option".to_string(),
108 ),
109 (
110 Config {
111 options: None,
112 ..init_config()
113 },
114 "user=user dbname=dbname host=host port=1111 password=password connect_timeout=1".to_string(),
115 ),
116 ]
117 .iter()
118 .for_each(|(config, expected)| assert_eq!(expected, config.connection_string().expose_secret()))
119 }
120
121 #[test]
122 fn connection_url_test() {
123 [
124 (
125 Config { ..init_config() },
126 "postgresql://user:password@host:1111/dbname?connect_timeout=1&option=option"
127 .to_string(),
128 ),
129 (
130 Config {
131 password: None,
132 ..init_config()
133 },
134 "postgresql://user@host:1111/dbname?connect_timeout=1&option=option".to_string(),
135 ),
136 (
137 Config {
138 connect_timeout: None,
139 ..init_config()
140 },
141 "postgresql://user:password@host:1111/dbname?option=option".to_string(),
142 ),
143 (
144 Config {
145 options: None,
146 ..init_config()
147 },
148 "postgresql://user:password@host:1111/dbname?connect_timeout=1".to_string(),
149 ),
150 ]
151 .iter()
152 .for_each(|(conf, expected)| assert_eq!(expected, conf.connection_url().expose_secret()))
153 }
154
155 fn init_config() -> Config {
156 Config {
157 user: "user".to_string(),
158 dbname: "dbname".to_string(),
159 host: "host".to_string(),
160 port: 1111,
161 password: Some("password".to_string().into()),
162 connect_timeout: Some(DurationString::from_string("1s".to_string()).unwrap()),
163 options: Some("option=option".to_string()),
164 }
165 }
166}