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