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