rightsize_modules/
postgres.rs1use rightsize::{Container, ContainerGuard, Result, Wait};
8
9pub struct PostgresContainer {
11 container: Container,
12 username: String,
13 password: String,
14 database: String,
15}
16
17impl PostgresContainer {
18 const PORT: u16 = 5432;
19
20 pub fn new() -> Self {
23 Self::with_image("postgres:18-alpine")
24 }
25
26 pub fn with_image(image: &str) -> Self {
28 let username = "test".to_string();
29 let password = "test".to_string();
30 let database = "test".to_string();
31 let container = Container::new(image)
32 .with_exposed_ports(&[Self::PORT])
33 .with_env("POSTGRES_USER", &username)
34 .with_env("POSTGRES_PASSWORD", &password)
35 .with_env("POSTGRES_DB", &database)
36 .with_env("DOCKER_PG_LLVM_DEPS", "")
46 .waiting_for(Wait::for_log_message(
53 ".*database system is ready to accept connections.*",
54 2,
55 ));
56 Self {
57 container,
58 username,
59 password,
60 database,
61 }
62 }
63
64 pub fn with_username(mut self, username: &str) -> Self {
66 self.username = username.to_string();
67 self.container = self.container.with_env("POSTGRES_USER", username);
68 self
69 }
70
71 pub fn with_password(mut self, password: &str) -> Self {
73 self.password = password.to_string();
74 self.container = self.container.with_env("POSTGRES_PASSWORD", password);
75 self
76 }
77
78 pub fn with_database(mut self, database: &str) -> Self {
80 self.database = database.to_string();
81 self.container = self.container.with_env("POSTGRES_DB", database);
82 self
83 }
84
85 pub async fn start(self) -> Result<PostgresGuard> {
87 let guard = self.container.start().await?;
88 Ok(PostgresGuard {
89 guard,
90 username: self.username,
91 password: self.password,
92 database: self.database,
93 })
94 }
95}
96
97impl Default for PostgresContainer {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103pub struct PostgresGuard {
105 guard: ContainerGuard,
106 username: String,
107 password: String,
108 database: String,
109}
110
111impl PostgresGuard {
112 pub fn username(&self) -> &str {
114 &self.username
115 }
116
117 pub fn password(&self) -> &str {
119 &self.password
120 }
121
122 pub fn database_name(&self) -> &str {
124 &self.database
125 }
126
127 pub fn connection_string(&self) -> String {
130 format!(
131 "postgres://{}:{}@{}:{}/{}",
132 self.username,
133 self.password,
134 self.guard.host(),
135 self.guard.get_mapped_port(PostgresContainer::PORT).unwrap(),
136 self.database,
137 )
138 }
139
140 pub async fn stop(self) -> Result<()> {
142 self.guard.stop().await
143 }
144}
145
146impl std::ops::Deref for PostgresGuard {
147 type Target = ContainerGuard;
148 fn deref(&self) -> &ContainerGuard {
149 &self.guard
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn defaults_are_the_test_trio() {
159 let c = PostgresContainer::new();
160 assert_eq!(c.username, "test");
161 assert_eq!(c.password, "test");
162 assert_eq!(c.database, "test");
163 }
164
165 #[test]
166 fn builders_override_the_defaults() {
167 let c = PostgresContainer::new()
168 .with_username("alice")
169 .with_password("s3cret")
170 .with_database("app");
171 assert_eq!(c.username, "alice");
172 assert_eq!(c.password, "s3cret");
173 assert_eq!(c.database, "app");
174 }
175}