rustrails_record/
connection.rs1use rustrails_support::runtime;
2use sea_orm::{ConnectOptions, Database, DatabaseConnection};
3
4#[derive(Debug, thiserror::Error)]
6pub enum ConnectionError {
7 #[error("connection failed: {0}")]
9 ConnectionFailed(String),
10 #[error("not connected")]
12 NotConnected,
13}
14
15#[derive(Clone, Debug)]
17pub struct ConnectionPool {
18 db: DatabaseConnection,
19}
20
21impl ConnectionPool {
22 pub async fn connect(url: &str) -> Result<Self, ConnectionError> {
24 let db = Database::connect(url)
25 .await
26 .map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))?;
27 Ok(Self { db })
28 }
29
30 pub fn connect_sync(url: &str) -> Result<Self, ConnectionError> {
32 runtime::block_on(Self::connect(url))
33 }
34
35 pub async fn connect_with_options(options: ConnectOptions) -> Result<Self, ConnectionError> {
37 let db = Database::connect(options)
38 .await
39 .map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))?;
40 Ok(Self { db })
41 }
42
43 pub fn connect_with_options_sync(options: ConnectOptions) -> Result<Self, ConnectionError> {
45 runtime::block_on(Self::connect_with_options(options))
46 }
47
48 pub fn connection(&self) -> &DatabaseConnection {
50 &self.db
51 }
52
53 pub async fn ping(&self) -> Result<(), ConnectionError> {
55 self.db
56 .ping()
57 .await
58 .map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))
59 }
60
61 pub fn ping_sync(&self) -> Result<(), ConnectionError> {
63 runtime::block_on(self.ping())
64 }
65
66 pub async fn close(self) -> Result<(), ConnectionError> {
68 self.db
69 .close()
70 .await
71 .map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))
72 }
73
74 pub fn close_sync(self) -> Result<(), ConnectionError> {
76 runtime::block_on(self.close())
77 }
78}
79
80pub async fn establish(url: &str) -> Result<ConnectionPool, ConnectionError> {
82 ConnectionPool::connect(url).await
83}
84
85pub fn establish_sync(url: &str) -> Result<ConnectionPool, ConnectionError> {
87 runtime::block_on(establish(url))
88}
89
90#[cfg(test)]
91mod tests {
92 use rustrails_support::{database, runtime};
93 use sea_orm::{ConnectOptions, DatabaseBackend};
94
95 use super::{ConnectionError, ConnectionPool, establish, establish_sync};
96
97 fn run_sync_connection_test(test: impl FnOnce() + Send + 'static) {
98 std::thread::spawn(move || {
99 let _rt = runtime::init_runtime();
100 database::establish("sqlite::memory:")
101 .expect("sqlite in-memory connection should succeed");
102 test();
103 })
104 .join()
105 .unwrap();
106 }
107
108 #[tokio::test]
109 async fn connect_to_in_memory_sqlite() {
110 let pool = ConnectionPool::connect("sqlite::memory:")
111 .await
112 .expect("sqlite in-memory connection should succeed");
113
114 assert_eq!(
115 pool.connection().get_database_backend(),
116 DatabaseBackend::Sqlite
117 );
118 }
119
120 #[tokio::test]
121 async fn connect_with_options_uses_same_backend() {
122 let mut options = ConnectOptions::new("sqlite::memory:");
123 options.sqlx_logging(false);
124
125 let pool = ConnectionPool::connect_with_options(options)
126 .await
127 .expect("sqlite in-memory connection should succeed");
128
129 assert_eq!(
130 pool.connection().get_database_backend(),
131 DatabaseBackend::Sqlite
132 );
133 }
134
135 #[tokio::test]
136 async fn ping_succeeds_for_live_connection() {
137 let pool = establish("sqlite::memory:")
138 .await
139 .expect("sqlite in-memory connection should succeed");
140
141 pool.ping().await.expect("ping should succeed");
142 }
143
144 #[tokio::test]
145 async fn close_succeeds_for_live_connection() {
146 let pool = establish("sqlite::memory:")
147 .await
148 .expect("sqlite in-memory connection should succeed");
149
150 pool.close().await.expect("close should succeed");
151 }
152
153 #[test]
154 fn connect_sync_to_in_memory_sqlite() {
155 run_sync_connection_test(|| {
156 let pool = ConnectionPool::connect_sync("sqlite::memory:")
157 .expect("sqlite in-memory connection should succeed");
158
159 assert_eq!(
160 pool.connection().get_database_backend(),
161 DatabaseBackend::Sqlite
162 );
163 });
164 }
165
166 #[test]
167 fn connect_with_options_sync_uses_same_backend() {
168 run_sync_connection_test(|| {
169 let mut options = ConnectOptions::new("sqlite::memory:");
170 options.sqlx_logging(false);
171
172 let pool = ConnectionPool::connect_with_options_sync(options)
173 .expect("sqlite in-memory connection should succeed");
174
175 assert_eq!(
176 pool.connection().get_database_backend(),
177 DatabaseBackend::Sqlite
178 );
179 });
180 }
181
182 #[test]
183 fn ping_sync_succeeds_for_live_connection() {
184 run_sync_connection_test(|| {
185 let pool = establish_sync("sqlite::memory:")
186 .expect("sqlite in-memory connection should succeed");
187
188 pool.ping_sync().expect("ping should succeed");
189 });
190 }
191
192 #[test]
193 fn close_sync_succeeds_for_live_connection() {
194 run_sync_connection_test(|| {
195 let pool = establish_sync("sqlite::memory:")
196 .expect("sqlite in-memory connection should succeed");
197
198 pool.close_sync().expect("close should succeed");
199 });
200 }
201
202 #[test]
203 fn establish_sync_creates_connection_pool() {
204 run_sync_connection_test(|| {
205 let pool = establish_sync("sqlite::memory:")
206 .expect("sqlite in-memory connection should succeed");
207
208 assert_eq!(
209 pool.connection().get_database_backend(),
210 DatabaseBackend::Sqlite
211 );
212 });
213 }
214
215 #[tokio::test]
216 async fn invalid_url_returns_connection_error() {
217 let result = ConnectionPool::connect("not-a-valid-database-url").await;
218
219 assert!(matches!(result, Err(ConnectionError::ConnectionFailed(_))));
220 }
221}