pg_embedded_setup_unpriv/cluster/delegation.rs
1//! Delegation methods that forward `TestCluster` calls to `TestClusterConnection`.
2//!
3//! This module provides ergonomic access to database lifecycle operations directly from
4//! `TestCluster`, eliminating the need for callers to explicitly call `.connection()`
5//! before invoking methods like `create_database` or `drop_database`.
6
7use super::lifecycle::DatabaseName;
8use super::temporary_database::TemporaryDatabase;
9use super::{ClusterHandle, TestCluster};
10use crate::CleanupMode;
11use crate::error::BootstrapResult;
12
13/// Generates delegation methods on `TestCluster` that forward to `TestClusterConnection`.
14///
15/// Each invocation generates a method that calls `self.connection().$method(...)`.
16macro_rules! delegate_to_connection {
17 // Single argument, unit return
18 (
19 $(#[$meta:meta])*
20 fn $name:ident(&self, $arg:ident: $arg_ty:ty) -> BootstrapResult<()>
21 ) => {
22 $(#[$meta])*
23 pub fn $name(&self, $arg: $arg_ty) -> BootstrapResult<()> {
24 self.connection().$name($arg)
25 }
26 };
27
28 // Two arguments, unit return
29 (
30 $(#[$meta:meta])*
31 fn $name:ident(&self, $arg1:ident: $arg1_ty:ty, $arg2:ident: $arg2_ty:ty) -> BootstrapResult<()>
32 ) => {
33 $(#[$meta])*
34 pub fn $name(&self, $arg1: $arg1_ty, $arg2: $arg2_ty) -> BootstrapResult<()> {
35 self.connection().$name($arg1, $arg2)
36 }
37 };
38
39 // Single argument, custom return type
40 (
41 $(#[$meta:meta])*
42 fn $name:ident(&self, $arg:ident: $arg_ty:ty) -> BootstrapResult<$ret:ty>
43 ) => {
44 $(#[$meta])*
45 pub fn $name(&self, $arg: $arg_ty) -> BootstrapResult<$ret> {
46 self.connection().$name($arg)
47 }
48 };
49
50 // Two arguments, custom return type
51 (
52 $(#[$meta:meta])*
53 fn $name:ident(&self, $arg1:ident: $arg1_ty:ty, $arg2:ident: $arg2_ty:ty) -> BootstrapResult<$ret:ty>
54 ) => {
55 $(#[$meta])*
56 pub fn $name(&self, $arg1: $arg1_ty, $arg2: $arg2_ty) -> BootstrapResult<$ret> {
57 self.connection().$name($arg1, $arg2)
58 }
59 };
60}
61
62impl TestCluster {
63 /// Overrides the cleanup mode used when the cluster is dropped.
64 ///
65 /// # Examples
66 /// ```no_run
67 /// use pg_embedded_setup_unpriv::{CleanupMode, TestCluster};
68 ///
69 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
70 /// let cluster = TestCluster::new()?.with_cleanup_mode(CleanupMode::Full);
71 /// # drop(cluster);
72 /// # Ok(())
73 /// # }
74 /// ```
75 #[must_use]
76 pub fn with_cleanup_mode(mut self, cleanup_mode: CleanupMode) -> Self {
77 self.guard.bootstrap.cleanup_mode = cleanup_mode;
78 self.handle = ClusterHandle::new(self.guard.bootstrap.clone());
79 self
80 }
81}
82
83impl TestCluster {
84 delegate_to_connection! {
85 /// Creates a new database with the given name.
86 ///
87 /// Delegates to [`crate::TestClusterConnection::create_database`].
88 ///
89 /// # Errors
90 ///
91 /// Returns an error if the database already exists or if the connection
92 /// fails.
93 ///
94 /// # Examples
95 ///
96 /// ```no_run
97 /// use pg_embedded_setup_unpriv::TestCluster;
98 ///
99 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
100 /// let cluster = TestCluster::new()?;
101 /// cluster.create_database("my_test_db")?;
102 /// # Ok(())
103 /// # }
104 /// ```
105 fn create_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<()>
106 }
107
108 delegate_to_connection! {
109 /// Creates a new database by cloning an existing template.
110 ///
111 /// Delegates to [`crate::TestClusterConnection::create_database_from_template`].
112 ///
113 /// # Errors
114 ///
115 /// Returns an error if the target database already exists, the template
116 /// does not exist, the template has active connections, or if the
117 /// connection fails.
118 ///
119 /// # Examples
120 ///
121 /// ```no_run
122 /// use pg_embedded_setup_unpriv::TestCluster;
123 ///
124 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
125 /// let cluster = TestCluster::new()?;
126 /// cluster.create_database("my_template")?;
127 /// // ... run migrations on my_template ...
128 /// cluster.create_database_from_template("test_db", "my_template")?;
129 /// # Ok(())
130 /// # }
131 /// ```
132 fn create_database_from_template(&self, name: impl Into<DatabaseName>, template: impl Into<DatabaseName>) -> BootstrapResult<()>
133 }
134
135 delegate_to_connection! {
136 /// Drops an existing database.
137 ///
138 /// Delegates to [`crate::TestClusterConnection::drop_database`].
139 ///
140 /// # Errors
141 ///
142 /// Returns an error if the database does not exist, has active connections,
143 /// or if the connection fails.
144 ///
145 /// # Examples
146 ///
147 /// ```no_run
148 /// use pg_embedded_setup_unpriv::TestCluster;
149 ///
150 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
151 /// let cluster = TestCluster::new()?;
152 /// cluster.create_database("temp_db")?;
153 /// cluster.drop_database("temp_db")?;
154 /// # Ok(())
155 /// # }
156 /// ```
157 fn drop_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<()>
158 }
159
160 delegate_to_connection! {
161 /// Checks whether a database with the given name exists.
162 ///
163 /// Delegates to [`crate::TestClusterConnection::database_exists`].
164 ///
165 /// # Errors
166 ///
167 /// Returns an error if the connection fails.
168 ///
169 /// # Examples
170 ///
171 /// ```no_run
172 /// use pg_embedded_setup_unpriv::TestCluster;
173 ///
174 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
175 /// let cluster = TestCluster::new()?;
176 /// assert!(cluster.database_exists("postgres")?);
177 /// assert!(!cluster.database_exists("nonexistent")?);
178 /// # Ok(())
179 /// # }
180 /// ```
181 fn database_exists(&self, name: impl Into<DatabaseName>) -> BootstrapResult<bool>
182 }
183
184 /// Ensures a template database exists, creating it if necessary.
185 ///
186 /// Delegates to [`crate::TestClusterConnection::ensure_template_exists`].
187 ///
188 /// # Errors
189 ///
190 /// Returns an error if database creation fails or if `setup_fn` returns
191 /// an error.
192 ///
193 /// # Examples
194 ///
195 /// ```no_run
196 /// use pg_embedded_setup_unpriv::TestCluster;
197 ///
198 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
199 /// let cluster = TestCluster::new()?;
200 ///
201 /// // Ensure template exists, running migrations if needed
202 /// cluster.ensure_template_exists("my_template", |db_name| {
203 /// // Run migrations on the newly created template database
204 /// Ok(())
205 /// })?;
206 ///
207 /// // Clone the template for each test
208 /// cluster.create_database_from_template("test_db_1", "my_template")?;
209 /// # Ok(())
210 /// # }
211 /// ```
212 pub fn ensure_template_exists<F>(
213 &self,
214 name: impl Into<DatabaseName>,
215 setup_fn: F,
216 ) -> BootstrapResult<()>
217 where
218 F: FnOnce(&str) -> BootstrapResult<()>,
219 {
220 self.connection().ensure_template_exists(name, setup_fn)
221 }
222
223 delegate_to_connection! {
224 /// Creates a temporary database that is dropped when the guard is dropped.
225 ///
226 /// Delegates to [`crate::TestClusterConnection::temporary_database`].
227 ///
228 /// # Errors
229 ///
230 /// Returns an error if the database already exists or if the connection
231 /// fails.
232 ///
233 /// # Examples
234 ///
235 /// ```no_run
236 /// use pg_embedded_setup_unpriv::TestCluster;
237 ///
238 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
239 /// let cluster = TestCluster::new()?;
240 /// let temp_db = cluster.temporary_database("my_temp_db")?;
241 ///
242 /// // Database is dropped automatically when temp_db goes out of scope
243 /// # Ok(())
244 /// # }
245 /// ```
246 fn temporary_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<TemporaryDatabase>
247 }
248
249 delegate_to_connection! {
250 /// Creates a temporary database from a template.
251 ///
252 /// Delegates to [`crate::TestClusterConnection::temporary_database_from_template`].
253 ///
254 /// # Errors
255 ///
256 /// Returns an error if the target database already exists, the template
257 /// does not exist, the template has active connections, or if the
258 /// connection fails.
259 ///
260 /// # Examples
261 ///
262 /// ```no_run
263 /// use pg_embedded_setup_unpriv::TestCluster;
264 ///
265 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
266 /// let cluster = TestCluster::new()?;
267 /// cluster.ensure_template_exists("migrated_template", |_| Ok(()))?;
268 ///
269 /// let temp_db = cluster.temporary_database_from_template("test_db", "migrated_template")?;
270 ///
271 /// // Database is dropped automatically when temp_db goes out of scope
272 /// # Ok(())
273 /// # }
274 /// ```
275 fn temporary_database_from_template(&self, name: impl Into<DatabaseName>, template: impl Into<DatabaseName>) -> BootstrapResult<TemporaryDatabase>
276 }
277}