Skip to main content

doido_model/
config.rs

1//! Per-environment database configuration loaded from `config/<env>.yml`.
2//!
3//! Mirrors `doido-controller`'s config: [`Config`] is a trait so applications
4//! can supply their own backing store, and [`YamlConfig`] is the default
5//! implementation that deserializes the `database` section of the YAML file for
6//! the environment reported by [`Environment::get_env`].
7
8use doido_core::Environment;
9use serde::Deserialize;
10
11/// Re-exported so `config::LoggerConfig` resolves; the logger config lives in
12/// `doido-core` alongside the logger it drives. The model layer reads it for the
13/// `sql` toggle (whether sea-orm logs each statement).
14pub use doido_core::logger::LoggerConfig;
15
16/// Database connection settings.
17#[derive(Debug, Clone, Deserialize)]
18pub struct DatabaseConfig {
19    /// Connection URL, e.g. `postgres://localhost/my_app_development` or
20    /// `sqlite://db/development.db`.
21    pub url: String,
22    /// Maximum pooled connections (`config/database.yml` `pool:`).
23    #[serde(default)]
24    pub pool: Option<u32>,
25    /// Connection acquire timeout, in seconds (`checkout_timeout:`).
26    #[serde(default)]
27    pub connect_timeout: Option<u64>,
28}
29
30impl Default for DatabaseConfig {
31    fn default() -> Self {
32        Self {
33            url: "sqlite://db/development.db".to_string(),
34            pool: None,
35            connect_timeout: None,
36        }
37    }
38}
39
40/// Model-layer configuration. Used as a trait object (`Box<dyn Config>`) so the
41/// backing store can be swapped without touching call sites.
42pub trait Config: Send + Sync {
43    /// Database connection settings.
44    fn database(&self) -> &DatabaseConfig;
45    /// Logging settings; the model layer reads the `sql` toggle.
46    fn logger(&self) -> &LoggerConfig;
47}
48
49/// File-based [`Config`] deserialized from the `database` and `logger` sections
50/// of `config/<env>.yml`. Other sections (e.g. `server`) are ignored.
51#[derive(Debug, Clone, Default, Deserialize)]
52pub struct YamlConfig {
53    #[serde(default)]
54    pub database: DatabaseConfig,
55    #[serde(default)]
56    pub logger: LoggerConfig,
57}
58
59impl Config for YamlConfig {
60    fn database(&self) -> &DatabaseConfig {
61        &self.database
62    }
63
64    fn logger(&self) -> &LoggerConfig {
65        &self.logger
66    }
67}
68
69impl YamlConfig {
70    /// Loads `config/<env>.yml` for the environment from [`Environment::get_env`].
71    pub fn load() -> std::io::Result<Self> {
72        Self::load_env(Environment::get_env())
73    }
74
75    /// Loads `config/<env>.yml` for a specific environment.
76    pub fn load_env(env: Environment) -> std::io::Result<Self> {
77        let path = format!("config/{}.yml", env.as_str());
78        let contents = std::fs::read_to_string(&path)?;
79        Self::from_yaml(&contents)
80    }
81
82    /// Parses a [`YamlConfig`] from a YAML string.
83    pub fn from_yaml(yaml: &str) -> std::io::Result<Self> {
84        serde_norway::from_str(yaml)
85            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
86    }
87}
88
89/// Loads the current environment's configuration as a trait object, falling
90/// back to [`Default`] values when the file is missing or invalid.
91pub fn load() -> Box<dyn Config> {
92    Box::new(YamlConfig::load().unwrap_or_default())
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn parses_database_url_and_ignores_other_sections() {
101        let yaml = "server:\n  bind: 0.0.0.0\n  port: 3000\ndatabase:\n  url: postgres://localhost/app_development\n";
102        let config = YamlConfig::from_yaml(yaml).unwrap();
103        assert_eq!(
104            config.database().url,
105            "postgres://localhost/app_development"
106        );
107    }
108
109    #[test]
110    fn defaults_when_database_section_absent() {
111        let config = YamlConfig::from_yaml("server:\n  port: 3000\n").unwrap();
112        assert_eq!(config.database().url, "sqlite://db/development.db");
113    }
114}