Skip to main content

qm_pg/
config.rs

1use serde::Deserialize;
2use std::sync::Arc;
3
4/// PostgreSQL connection configuration.
5#[derive(Deserialize)]
6pub struct Config {
7    host: Option<Arc<str>>,
8    port: Option<u16>,
9    max_connections: Option<u32>,
10    min_connections: Option<u32>,
11    acquire_timeout: Option<u64>,
12    idle_timeout: Option<u64>,
13    max_lifetime: Option<u64>,
14    username: Option<Arc<str>>,
15    password: Option<Arc<str>>,
16    database: Option<Arc<str>>,
17    root_username: Option<Arc<str>>,
18    root_password: Option<Arc<str>>,
19    root_database: Option<Arc<str>>,
20    #[serde(skip)]
21    address: Option<Arc<str>>,
22    #[serde(skip)]
23    root_address: Option<Arc<str>>,
24}
25
26impl Config {
27    /// Creates a new Config from environment variables with default PG_ prefix.
28    pub fn new() -> envy::Result<Self> {
29        ConfigBuilder::default().build()
30    }
31
32    /// Creates a new ConfigBuilder for custom configuration.
33    pub fn builder<'a>() -> ConfigBuilder<'a> {
34        ConfigBuilder::default()
35    }
36
37    /// Returns the maximum number of connections in the pool.
38    pub fn max_connections(&self) -> u32 {
39        self.max_connections.unwrap_or(32)
40    }
41
42    /// Returns the minimum number of connections in the pool.
43    pub fn min_connections(&self) -> u32 {
44        self.min_connections.unwrap_or(0)
45    }
46
47    /// Returns the connection acquire timeout in seconds.
48    pub fn acquire_timeout(&self) -> u64 {
49        self.acquire_timeout.unwrap_or(30)
50    }
51
52    /// Returns the idle connection timeout in seconds.
53    pub fn idle_timeout(&self) -> u64 {
54        self.idle_timeout.unwrap_or(10 * 60)
55    }
56
57    /// Returns the maximum lifetime of a connection in seconds.
58    pub fn max_lifetime(&self) -> u64 {
59        self.max_lifetime.unwrap_or(30 * 60)
60    }
61
62    /// Returns the database name, if set.
63    pub fn database(&self) -> Option<&str> {
64        self.database.as_deref()
65    }
66
67    /// Returns the database username, if set.
68    pub fn username(&self) -> Option<&str> {
69        self.username.as_deref()
70    }
71
72    /// Returns the database password, if set.
73    pub fn password(&self) -> Option<&str> {
74        self.password.as_deref()
75    }
76
77    /// Returns the root database name, if set.
78    pub fn root_database(&self) -> Option<&str> {
79        self.root_database.as_deref()
80    }
81
82    /// Returns the PostgreSQL connection address.
83    pub fn address(&self) -> &str {
84        self.address.as_deref().unwrap()
85    }
86
87    /// Returns the PostgreSQL root connection address.
88    pub fn root_address(&self) -> &str {
89        self.root_address.as_deref().unwrap()
90    }
91}
92
93/// Builder for PostgreSQL configuration with custom prefix support.
94#[derive(Default)]
95pub struct ConfigBuilder<'a> {
96    prefix: Option<&'a str>,
97}
98
99impl<'a> ConfigBuilder<'a> {
100    /// Sets a custom environment variable prefix.
101    pub fn with_prefix(mut self, prefix: &'a str) -> Self {
102        self.prefix = Some(prefix);
103        self
104    }
105
106    /// Builds the Config from environment variables.
107    pub fn build(self) -> envy::Result<Config> {
108        let mut cfg: Config = if let Some(prefix) = self.prefix {
109            envy::prefixed(prefix)
110        } else {
111            envy::prefixed("PG_")
112        }
113        .from_env()?;
114        let host = cfg.host.as_deref().unwrap_or("127.0.0.1");
115        let port = cfg.port.unwrap_or(5432);
116        let mut address = match (cfg.username.as_deref(), cfg.password.as_deref()) {
117            (Some(username), Some(password)) => {
118                format!("postgresql://{}:{}@{}:{}/", username, password, host, port)
119            }
120            (Some(username), None) => format!("postgresql://{}@{}:{}/", username, host, port),
121            _ => format!("postgresql://{}:{}/", host, port),
122        };
123        if let Some(database) = cfg.database.as_deref() {
124            address.push_str(database);
125        }
126        cfg.address = Some(Arc::from(address));
127        let mut root_address = match (cfg.root_username.as_deref(), cfg.root_password.as_deref()) {
128            (Some(root_username), Some(root_password)) => format!(
129                "postgresql://{}:{}@{}:{}/",
130                root_username, root_password, host, port
131            ),
132            (Some(root_username), None) => {
133                format!("postgresql://{}@{}:{}/", root_username, host, port)
134            }
135            _ => format!("postgresql://{}:{}/", host, port),
136        };
137        if let Some(root_database) = cfg.root_database.as_deref() {
138            root_address.push_str(root_database);
139        }
140        cfg.root_address = Some(Arc::from(root_address));
141        Ok(cfg)
142    }
143}