chuchi_postgres/
database.rs1use deadpool_postgres::{CreatePoolError, Pool, PoolError, Runtime};
2
3use tokio_postgres::Error as PgError;
4use tokio_postgres::NoTls;
5
6pub use deadpool::managed::TimeoutType;
7pub use deadpool_postgres::{Config as PgConfig, ConfigError};
8
9use crate::connection::ConnectionOwned;
10use crate::migrations::Migrations;
11use crate::table::TableOwned;
12use crate::table::TableTemplate;
13
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum DatabaseError {
17 #[error("The database configuration is invalid {0}")]
18 Config(ConfigError),
19
20 #[error("Getting a connection timed out {0:?}")]
21 Timeout(TimeoutType),
22
23 #[error("Connection error {0}")]
24 Connection(#[from] crate::Error),
25
26 #[error("Postgres error {0}")]
27 Other(#[from] PgError),
28}
29
30#[cfg(feature = "chuchi")]
31mod impl_chuchi {
32 use std::error::Error as StdError;
33
34 use super::*;
35 use chuchi::error::{ErrorKind, ServerErrorKind};
36
37 impl chuchi::extractor::ExtractorError for DatabaseError {
38 fn error_kind(&self) -> ErrorKind {
39 ErrorKind::Server(ServerErrorKind::InternalServerError)
40 }
41
42 fn into_std(self) -> Box<dyn StdError + Send + Sync> {
43 Box::new(self)
44 }
45 }
46}
47
48#[derive(Clone, Debug, Default)]
54pub struct Config {
55 pg_config: PgConfig,
56 migration_table: Option<String>,
57}
58
59impl Config {
60 pub fn from_pg_config(pg_config: PgConfig) -> Self {
62 Self {
63 pg_config,
64 ..Default::default()
65 }
66 }
67
68 pub fn host(mut self, host: impl Into<String>) -> Self {
70 self.pg_config.host = Some(host.into());
71 self
72 }
73
74 pub fn dbname(mut self, dbname: impl Into<String>) -> Self {
76 self.pg_config.dbname = Some(dbname.into());
77 self
78 }
79
80 pub fn user(mut self, user: impl Into<String>) -> Self {
82 self.pg_config.user = Some(user.into());
83 self
84 }
85
86 pub fn password(mut self, password: impl Into<String>) -> Self {
88 self.pg_config.password = Some(password.into());
89 self
90 }
91
92 pub fn migration_table(mut self, table: impl Into<String>) -> Self {
94 self.migration_table = Some(table.into());
95 self
96 }
97
98 pub fn pg_config(&self) -> &PgConfig {
100 &self.pg_config
101 }
102
103 pub fn pg_config_mut(&mut self) -> &mut PgConfig {
105 &mut self.pg_config
106 }
107}
108
109#[derive(Debug, Clone)]
113pub struct Database {
114 pool: Pool,
115 migrations: Migrations,
116}
117
118impl Database {
119 pub async fn new(
121 name: impl Into<String>,
122 user: impl Into<String>,
123 password: impl Into<String>,
124 ) -> Result<Self, DatabaseError> {
125 Self::with_host("localhost", name, user, password).await
126 }
127
128 pub async fn with_host(
130 host: impl Into<String>,
131 name: impl Into<String>,
132 user: impl Into<String>,
133 password: impl Into<String>,
134 ) -> Result<Self, DatabaseError> {
135 Self::with_cfg(
136 Config::default()
137 .host(host)
138 .dbname(name)
139 .user(user)
140 .password(password),
141 )
142 .await
143 }
144
145 pub async fn with_cfg(cfg: Config) -> Result<Self, DatabaseError> {
147 let pool = cfg
148 .pg_config
149 .create_pool(Some(Runtime::Tokio1), NoTls)
150 .map_err(|e| match e {
151 CreatePoolError::Config(e) => DatabaseError::Config(e),
152 CreatePoolError::Build(_) => unreachable!(
153 "since we provide a runtime this should never happen"
154 ),
155 })?;
156
157 let this = Self {
158 pool,
159 migrations: Migrations::new(cfg.migration_table),
160 };
161
162 let mut db = this.get().await?;
164
165 this.migrations.init(&mut db).await?;
166
167 Ok(this)
168 }
169
170 pub async fn get(&self) -> Result<ConnectionOwned, DatabaseError> {
172 self.pool
173 .get()
174 .await
175 .map_err(|e| match e {
176 PoolError::Timeout(tim) => DatabaseError::Timeout(tim),
177 PoolError::Backend(e) => e.into(),
178 PoolError::Closed => todo!("when can a pool be closed?"),
179 PoolError::NoRuntimeSpecified => unreachable!(),
180 PoolError::PostCreateHook(e) => {
181 todo!("what is this error {e:?}?")
182 }
183 })
184 .map(ConnectionOwned)
185 }
186
187 pub fn migrations(&self) -> Migrations {
189 self.migrations.clone()
190 }
191
192 pub fn table_owned<T>(&self, name: &'static str) -> TableOwned<T>
197 where
198 T: TableTemplate,
199 {
200 TableOwned::new(self.clone(), name)
201 }
202}