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}