server_env_config/db.rs
1//! The [`DbConfig`] struct represents settings used to establish a connection with a database.
2
3use crate::env::Environment;
4use crate::{env_bool, env_parsable};
5use anyhow::{Context, Result};
6use std::env;
7use std::time::Duration;
8
9/// Settings used to establish a connection with a database, regardless of the engine.
10/// All the values can be initialized with [`DbConfig::init_for()`] method, that uses
11/// environment variables to setup all of them, otherwise all have default values,
12/// except the string connection.
13#[derive(Debug, Clone)]
14pub struct DbConfig {
15 /// Database URL, initialized with the `DATABASE_URL` env
16 pub database_url: String,
17 /// Min connections created at start-up, value set with `MIN_CONNECTIONS` env,
18 /// default 1
19 pub min_connections: u32,
20 /// Max connections allowed, value set with `MAX_CONNECTIONS` env,
21 /// default 10
22 pub max_connections: u32,
23 /// Time allowed to acquire a connection, value set with `ACQUIRE_TIMEOUT_MS` env,
24 /// default 750 milliseconds
25 pub acquire_timeout: Duration,
26 /// Max time a connection can be idle, value set with `IDLE_TIMEOUT_SEC` env,
27 /// default 300 sec (5 min).
28 /// Any connection that remains in the idle queue longer than this will be closed.
29 pub idle_timeout: Duration,
30 /// Whether to test before test the connection at start-up or not,
31 /// value set with `TEST_BEFORE_ACQUIRE` env, default to false
32 pub test_before_acquire: bool,
33}
34
35impl DbConfig {
36 /// Init the object with `env` passed, and the rest of the
37 /// attributes reading its corresponding environment variable,
38 /// otherwise use a default value.
39 ///
40 /// The database string is saved in `self.database_url` with the value found at
41 /// the `DATABASE_URL` environment value, that it's the only one required (there
42 /// is no default value). If `env` passed is [`Environment::Test`] the prefix
43 /// `_test` is added to the string connection, to avoid using by mistake prod/local
44 /// databases, unless the string already ends with the prefix, or the string has
45 /// connection arguments (the `?` symbol in the string).
46 ///
47 /// # Examples
48 /// ```
49 /// use std::env;
50 /// use server_env_config::db::DbConfig;
51 /// use server_env_config::env::Environment;
52 ///
53 /// // Configurations should be actually set by the OS environment
54 /// env::set_var("DATABASE_URL", "postgresql://user:pass@localhost/db");
55 /// env::set_var("MAX_CONNECTIONS", "50");
56 /// env::set_var("IDLE_TIMEOUT_SEC", "60");
57 ///
58 /// let db = DbConfig::init_for(&Environment::Local).unwrap();
59 ///
60 /// assert_eq!(db.database_url, "postgresql://user:pass@localhost/db");
61 /// assert_eq!(db.max_connections, 50);
62 /// // All settings except DATABASE_URL have default values if env variables are not set
63 /// assert_eq!(db.min_connections, 1);
64 /// assert!(!db.test_before_acquire);
65 ///
66 /// env::remove_var("DATABASE_URL"); // if not set, DbConfig cannot be initialized
67 /// let db = DbConfig::init_for(&Environment::Local);
68 /// assert!(db.is_err());
69 /// ```
70 pub fn init_for(env: &Environment) -> Result<Self> {
71 let url = env::var("DATABASE_URL").context("DATABASE_URL must be set")?;
72 let database_url = if *env == Environment::Test && !url.ends_with("_test") && !url.contains('?') {
73 format!("{url}_test")
74 } else {
75 url
76 };
77 let min_connections = env_parsable::<u32>("MIN_CONNECTIONS", 1)?;
78 let max_connections = env_parsable::<u32>("MAX_CONNECTIONS", 10)?;
79 let acquire_timeout = Duration::from_millis(env_parsable::<u64>("ACQUIRE_TIMEOUT_MS", 750)?);
80 let idle_timeout = Duration::from_secs(env_parsable::<u64>("IDLE_TIMEOUT_SEC", 300)?);
81 let test_before_acquire = env_bool("TEST_BEFORE_ACQUIRE", false)?;
82 Ok(DbConfig {
83 database_url,
84 min_connections,
85 max_connections,
86 acquire_timeout,
87 idle_timeout,
88 test_before_acquire,
89 })
90 }
91}
92
93impl ToString for DbConfig {
94 /// This `to_string()` implementation prints out all the config
95 /// values in `.env` format, using as key the environment variable
96 /// used to set-up the config, even if the configuration was
97 /// set in another way, e.g. using a default value.
98 fn to_string(&self) -> String {
99 format!(
100r#"DATABASE_URL="{}"
101MIN_CONNECTIONS={}
102MAX_CONNECTIONS={}
103ACQUIRE_TIMEOUT_MS={}
104IDLE_TIMEOUT_SEC={}
105TEST_BEFORE_ACQUIRE={}"#,
106 self.database_url,
107 self.min_connections,
108 self.max_connections,
109 self.acquire_timeout.as_millis(),
110 self.idle_timeout.as_secs(),
111 self.test_before_acquire,
112 )
113 }
114}