chuchi_postgres/
db.rs

1//! Database module
2//!
3//! ## Axum
4//! If you wan't to use axum the best way is to make a small wrapper around
5//! ConnOwned.
6//!
7//! ```ignore
8//! use chuchi_postgres as pg;
9//! use pg::db::{Conn, Trans, Db};
10//! use axum::extract::{FromRequestParts, FromRef};
11//! use axum::http::request::Parts;
12//!
13//! pub struct ConnOwned(pub pg::db::ConnOwned);
14//!
15//! impl ConnOwned {
16//! 	pub fn conn(&self) -> Conn {
17//! 		self.0.conn()
18//! 	}
19//!
20//! 	pub async fn trans(&mut self) -> Result<Trans, pg::Error> {
21//! 		self.0.trans().await
22//! 	}
23//! }
24//!
25//! impl<S> FromRequestParts<S> for ConnOwned
26//! where
27//! 	S: Send + Sync,
28//! 	Db: FromRef<S>,
29//! {
30//! 	type Rejection = MyError;
31//!
32//! 	async fn from_request_parts(
33//! 		_parts: &mut Parts,
34//! 		state: &S,
35//! 	) -> Result<Self, Self::Rejection> {
36//! 		let db = Db::from_ref(state);
37//! 		db.get()
38//! 			.await
39//! 			.map(Self)
40//! 			.map_err(|e| MyError::Internal(e.to_string()))
41//! 	}
42//! }
43//! ```
44
45use crate::{
46	Connection, Database, Error,
47	connection::{ConnectionOwned, Transaction},
48	database::DatabaseError,
49};
50
51/// This might contain a database or none.
52///
53/// This will be usefull to mocking the database with a memory
54/// database.
55#[derive(Debug, Clone)]
56#[cfg_attr(feature = "chuchi", derive(chuchi::Resource))]
57pub struct Db {
58	pg: Option<Database>,
59}
60
61impl Db {
62	pub fn new_memory() -> Self {
63		Self { pg: None }
64	}
65
66	pub async fn get(&self) -> Result<ConnOwned, DatabaseError> {
67		match &self.pg {
68			Some(pg) => Ok(ConnOwned {
69				pg: Some(pg.get().await?),
70			}),
71			None => Ok(ConnOwned { pg: None }),
72		}
73	}
74}
75
76impl From<Database> for Db {
77	fn from(pg: Database) -> Self {
78		Self { pg: Some(pg) }
79	}
80}
81
82#[derive(Debug)]
83pub struct ConnOwned {
84	pg: Option<ConnectionOwned>,
85}
86
87impl ConnOwned {
88	// connection
89	pub fn conn(&self) -> Conn<'_> {
90		Conn {
91			pg: self.pg.as_ref().map(|pg| pg.connection()),
92		}
93	}
94
95	// or transaction
96	pub async fn trans(&mut self) -> Result<Trans<'_>, Error> {
97		match &mut self.pg {
98			Some(pg) => Ok(Trans {
99				pg: Some(pg.transaction().await?),
100			}),
101			None => Ok(Trans { pg: None }),
102		}
103	}
104}
105
106#[cfg(feature = "chuchi")]
107mod impl_chuchi {
108	use chuchi::{
109		extractor::Extractor, extractor_extract, extractor_prepare,
110		extractor_validate,
111	};
112
113	use super::*;
114
115	impl<'a, R> Extractor<'a, R> for ConnOwned {
116		type Error = DatabaseError;
117		type Prepared = Self;
118
119		extractor_validate!(|validate| {
120			assert!(validate.resources.exists::<Db>(), "Db resource not found");
121		});
122
123		extractor_prepare!(|prepare| {
124			let db = prepare.resources.get::<Db>().unwrap();
125			db.get().await
126		});
127
128		extractor_extract!(|extract| { Ok(extract.prepared) });
129	}
130}
131
132/// This might contain a connection or none.
133#[derive(Debug, Clone, Copy)]
134pub struct Conn<'a> {
135	pg: Option<Connection<'a>>,
136}
137
138impl<'a> Conn<'a> {
139	/// Create a new connection.
140	pub fn new_memory() -> Self {
141		Self { pg: None }
142	}
143
144	/// Get the connection.
145	///
146	/// ## Panics
147	/// If the connection is not set.
148	pub fn pg(self) -> Connection<'a> {
149		self.pg.unwrap()
150	}
151}
152
153/// This might contain a transaction or none.
154#[derive(Debug)]
155pub struct Trans<'a> {
156	pg: Option<Transaction<'a>>,
157}
158
159impl<'a> Trans<'a> {
160	/// Get the connection of the transaction.
161	pub fn conn(&self) -> Conn<'_> {
162		Conn {
163			pg: self.pg.as_ref().map(|pg| pg.connection()),
164		}
165	}
166
167	/// Commit the transaction.
168	///
169	/// ## Note
170	/// Does nothing if it contains a memory Conn
171	pub async fn commit(self) -> Result<(), Error> {
172		match self.pg {
173			Some(pg) => pg.commit().await,
174			None => Ok(()),
175		}
176	}
177
178	/// Rollback the transaction.
179	///
180	/// ## Panics
181	/// If the transaction is not set / this is a memory Conn
182	pub async fn rollback(self) -> Result<(), Error> {
183		match self.pg {
184			Some(pg) => pg.commit().await,
185			None => panic!("rollback not supported"),
186		}
187	}
188}