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 crate::register_default_backends();
88 let guard = self.container.start().await?;
89 Ok(PostgresGuard {
90 guard,
91 username: self.username,
92 password: self.password,
93 database: self.database,
94 })
95 }
96}
97
98impl Default for PostgresContainer {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104pub struct PostgresGuard {
106 guard: ContainerGuard,
107 username: String,
108 password: String,
109 database: String,
110}
111
112impl PostgresGuard {
113 pub fn username(&self) -> &str {
115 &self.username
116 }
117
118 pub fn password(&self) -> &str {
120 &self.password
121 }
122
123 pub fn database_name(&self) -> &str {
125 &self.database
126 }
127
128 pub fn connection_string(&self) -> String {
131 format!(
132 "postgres://{}:{}@{}:{}/{}",
133 self.username,
134 self.password,
135 self.guard.host(),
136 self.guard.get_mapped_port(PostgresContainer::PORT).unwrap(),
137 self.database,
138 )
139 }
140
141 pub async fn stop(self) -> Result<()> {
143 self.guard.stop().await
144 }
145}
146
147impl std::ops::Deref for PostgresGuard {
148 type Target = ContainerGuard;
149 fn deref(&self) -> &ContainerGuard {
150 &self.guard
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn defaults_are_the_test_trio() {
160 let c = PostgresContainer::new();
161 assert_eq!(c.username, "test");
162 assert_eq!(c.password, "test");
163 assert_eq!(c.database, "test");
164 }
165
166 #[test]
167 fn builders_override_the_defaults() {
168 let c = PostgresContainer::new()
169 .with_username("alice")
170 .with_password("s3cret")
171 .with_database("app");
172 assert_eq!(c.username, "alice");
173 assert_eq!(c.password, "s3cret");
174 assert_eq!(c.database, "app");
175 }
176}