Skip to main content

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}