sql_middleware/postgres/
config.rs

1use super::typed::PgManager;
2use crate::middleware::{ConfigAndPool, DatabaseType, MiddlewarePool, SqlMiddlewareDbError};
3
4/// Minimal Postgres configuration (keeps the public API backward-compatible
5/// with the old `deadpool_postgres::Config` usage).
6#[derive(Clone, Debug, Default)]
7pub struct PgConfig {
8    pub dbname: Option<String>,
9    pub host: Option<String>,
10    pub port: Option<u16>,
11    pub user: Option<String>,
12    pub password: Option<String>,
13}
14
15impl PgConfig {
16    #[must_use]
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    #[must_use]
22    pub fn to_tokio_config(&self) -> tokio_postgres::Config {
23        let mut cfg = tokio_postgres::Config::new();
24        if let Some(dbname) = &self.dbname {
25            cfg.dbname(dbname);
26        }
27        if let Some(host) = &self.host {
28            cfg.host(host);
29        }
30        if let Some(port) = self.port {
31            cfg.port(port);
32        }
33        if let Some(user) = &self.user {
34            cfg.user(user);
35        }
36        if let Some(password) = &self.password {
37            cfg.password(password);
38        }
39        cfg
40    }
41}
42
43/// Options for configuring a Postgres pool.
44#[derive(Clone)]
45pub struct PostgresOptions {
46    pub config: PgConfig,
47    pub translate_placeholders: bool,
48}
49
50impl PostgresOptions {
51    #[must_use]
52    pub fn new(config: PgConfig) -> Self {
53        Self {
54            config,
55            translate_placeholders: false,
56        }
57    }
58
59    #[must_use]
60    pub fn with_translation(mut self, translate_placeholders: bool) -> Self {
61        self.translate_placeholders = translate_placeholders;
62        self
63    }
64}
65
66/// Fluent builder for Postgres options.
67#[derive(Clone)]
68pub struct PostgresOptionsBuilder {
69    opts: PostgresOptions,
70}
71
72impl PostgresOptionsBuilder {
73    #[must_use]
74    pub fn new(config: PgConfig) -> Self {
75        Self {
76            opts: PostgresOptions::new(config),
77        }
78    }
79
80    #[must_use]
81    pub fn translation(mut self, translate_placeholders: bool) -> Self {
82        self.opts.translate_placeholders = translate_placeholders;
83        self
84    }
85
86    #[must_use]
87    pub fn finish(self) -> PostgresOptions {
88        self.opts
89    }
90
91    /// Build a `ConfigAndPool` for `PostgreSQL`.
92    ///
93    /// # Errors
94    ///
95    /// Returns `SqlMiddlewareDbError` if pool creation fails.
96    pub async fn build(self) -> Result<ConfigAndPool, SqlMiddlewareDbError> {
97        ConfigAndPool::new_postgres(self.finish()).await
98    }
99}
100
101impl ConfigAndPool {
102    #[must_use]
103    pub fn postgres_builder(pg_config: PgConfig) -> PostgresOptionsBuilder {
104        PostgresOptionsBuilder::new(pg_config)
105    }
106
107    /// Asynchronous initializer for `ConfigAndPool` with Postgres.
108    ///
109    /// # Errors
110    /// Returns `SqlMiddlewareDbError::ConfigError` if required config fields are missing or `SqlMiddlewareDbError::ConnectionError` if pool creation fails.
111    #[allow(clippy::unused_async)]
112    pub async fn new_postgres(opts: PostgresOptions) -> Result<Self, SqlMiddlewareDbError> {
113        let pg_config = opts.config;
114        let translate_placeholders = opts.translate_placeholders;
115
116        // Validate all required config fields are present
117        if pg_config.dbname.is_none() {
118            return Err(SqlMiddlewareDbError::ConfigError(
119                "dbname is required".to_string(),
120            ));
121        }
122
123        if pg_config.host.is_none() {
124            return Err(SqlMiddlewareDbError::ConfigError(
125                "host is required".to_string(),
126            ));
127        }
128        if pg_config.port.is_none() {
129            return Err(SqlMiddlewareDbError::ConfigError(
130                "port is required".to_string(),
131            ));
132        }
133        if pg_config.user.is_none() {
134            return Err(SqlMiddlewareDbError::ConfigError(
135                "user is required".to_string(),
136            ));
137        }
138        if pg_config.password.is_none() {
139            return Err(SqlMiddlewareDbError::ConfigError(
140                "password is required".to_string(),
141            ));
142        }
143
144        // Attempt to create connection pool
145        let manager = PgManager::new(pg_config.to_tokio_config());
146        let pg_pool = manager.build_pool().await?;
147
148        Ok(ConfigAndPool {
149            pool: MiddlewarePool::Postgres(pg_pool),
150            db_type: DatabaseType::Postgres,
151            translate_placeholders,
152        })
153    }
154}