diesel_rocket_pg/
lib.rs

1/// A wrapper to use PostgreSQL with Diesel and rocket
2mod error;
3
4pub use crate::error::*;
5
6use std::sync::Arc;
7
8use diesel::pg::PgConnection;
9use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
10use r2d2::Builder;
11
12/// The shared PostgreSQL connection pool
13///
14/// ## Example
15///
16/// ```no_run
17/// use diesel_rocket_pg::PgPool;
18/// use diesel::prelude::*;
19/// use rocket::State;
20/// use serde::Serialize;
21///
22/// #[macro_use] extern crate rocket;
23///
24/// diesel::table! {
25///     user (id) {
26///         id -> Int4,
27///         email -> Varchar,
28///         password -> Varchar,
29///     }
30/// }
31///
32/// #[derive(Debug, Queryable, Serialize)]
33/// pub struct AuthUser {
34///     pub id: i32,
35///     pub email: String,
36///     pub password: String,
37/// }
38///
39/// #[get("/user/<mail>")]
40/// async fn get_user(mail: String, pool: &State<PgPool>) -> String {
41///     let result = pool.run(move |conn| {
42///
43///         user::dsl::user.filter(
44///             user::dsl::email.eq(mail)
45///         ).first::<AuthUser>(conn).ok()
46///     }).await.unwrap();
47///
48///     if let Some(u) = result {
49///         format!("{:?}", u)
50///     } else {
51///         "User not found!".to_string()
52///     }
53/// }
54///
55/// #[launch]
56/// fn rocket() -> _ {
57///     let uri = "postgres://user:password123@localhost:5432/database";
58///     let pool = PgPool::create(uri).unwrap();
59///
60///     rocket::build().manage(pool).mount("/", routes![get_user])
61/// }
62/// ```
63#[derive(Debug, Clone)]
64pub struct PgPool(Arc<Pool<ConnectionManager<PgConnection>>>);
65
66/// This type is a wrapped Postgres connection
67pub type PgConn = PooledConnection<ConnectionManager<PgConnection>>;
68
69impl PgPool {
70    /// Creates a new connection pool
71    ///
72    /// ## Arguments
73    /// * `uri` - The postgresql connection string
74    ///
75    /// ## Example
76    /// ```no_run
77    /// use diesel_rocket_pg::PgPool;
78    ///
79    /// fn main() {
80    ///     let uri = "postgres://user:password123@localhost:5432/database";
81    ///     let _pool = PgPool::create(uri).unwrap();
82    ///     /* ... */
83    /// }
84    /// ```
85    pub fn create<T: Into<String>>(uri: T) -> Result<Self> {
86        Self::create_custom(uri, Pool::builder())
87    }
88
89    /// Creates a new connection pool with a custom r2d2 configuration
90    ///
91    /// ## Arguments
92    /// * `uri` - The postgresql connection string
93    /// * `builder` - A `r2d2::Builder` with a custom configuration
94    ///
95    /// ## Example
96    /// ```no_run
97    /// use diesel_rocket_pg::PgPool;
98    /// use r2d2::Pool;
99    ///
100    /// fn main() {
101    ///     let uri = "postgres://user:password123@localhost:5432/database";
102    ///     let builder = Pool::builder()
103    ///         .max_size(50)
104    ///         .min_idle(None)
105    ///         .max_lifetime(Some(Duration::from_secs(5)));
106    ///     let _pool = PgPool::create_custom(uri, builder).unwrap();
107    ///     /* ... */
108    /// }
109    /// ```
110    pub fn create_custom<T: Into<String>>(
111        uri: T,
112        builder: Builder<ConnectionManager<PgConnection>>,
113    ) -> Result<Self> {
114        let conn_man = ConnectionManager::<PgConnection>::new(uri);
115        let pool = builder.build(conn_man).map_err(Error::R2d2)?;
116
117        Ok(Self(Arc::new(pool)))
118    }
119
120    /// Executes a diesel query in a tokio task
121    ///
122    /// ## Example
123    /// ```no_run
124    /// #[get("/user/<mail>")]
125    /// async fn get_user(mail: String, pool: &State<PgPool>) -> AuthUser {
126    ///     let auth_user = pool.run(move |conn| {
127    ///         user::dsl::user.filter(
128    ///             user::dsl::email.eq(mail)
129    ///         ).first::<AuthUser>(conn).unwrap()
130    ///     }).await.unwrap();
131    ///
132    ///     auth_user
133    /// }
134    /// ```
135    pub async fn run<F, R>(&self, func: F) -> Result<R>
136    where
137        F: FnOnce(&mut PgConn) -> R + Send + 'static,
138        R: Send + 'static,
139    {
140        let pool = self.clone();
141        let res: Result<R> = tokio::task::spawn_blocking(move || {
142            let mut conn = pool.0.get().map_err(Error::R2d2)?;
143
144            Ok(func(&mut conn))
145        })
146        .await
147        .map_err(Error::TokioJoin)?;
148
149        res
150    }
151}