predawn/config/
mod.rs

1pub mod logger;
2pub mod openapi;
3pub mod server;
4
5use std::{
6    env,
7    ops::Deref,
8    path::{Path, PathBuf},
9    sync::LazyLock,
10};
11
12use config::{ConfigError, File, ValueKind};
13use serde::Deserialize;
14
15use crate::environment::Environment;
16
17#[derive(Clone)]
18pub struct Config {
19    inner: config::Config,
20}
21
22impl Config {
23    pub fn new(config: config::Config) -> Self {
24        Self { inner: config }
25    }
26
27    pub fn load(env: &Environment) -> Result<Self, ConfigError> {
28        static DEFAULT_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
29            let mut dir_path = match env::var("CARGO_MANIFEST_DIR") {
30                Ok(dir) => PathBuf::from(dir),
31                Err(_) => {
32                    let binary_file_path =
33                        env::args().next().expect("failed to get binary file path");
34
35                    Path::new(&binary_file_path)
36                        .parent()
37                        .expect("failed to get parent directory of binary file")
38                        .canonicalize()
39                        .expect("failed to get canonical, absolute form of the path to the parent directory of binary file")
40                }
41            };
42
43            dir_path.push("config");
44            dir_path
45        });
46
47        Self::from_folder(env, DEFAULT_FOLDER.as_path())
48    }
49
50    pub fn from_folder(env: &Environment, path: &Path) -> Result<Self, ConfigError> {
51        let app_cfg = path.join("app.toml");
52        let env_cfg = path.join(format!("app-{}.toml", env));
53        let env = config::Environment::default().separator("_");
54
55        let mut builder = config::Config::builder();
56
57        for cfg in [app_cfg, env_cfg].into_iter() {
58            tracing::info!("try to load config `{}`", cfg.display());
59
60            if cfg.exists() {
61                builder = builder.add_source(File::from(cfg))
62            } else {
63                tracing::info!("not found config `{}`", cfg.display());
64            }
65        }
66
67        let config = builder.add_source(env).build()?;
68
69        Ok(Self { inner: config })
70    }
71
72    pub fn is_debug(&self) -> bool {
73        self.inner.get_bool("debug").unwrap_or_default()
74    }
75
76    pub fn get<'de, T>(&self) -> Result<T, ConfigError>
77    where
78        T: ConfigPrefix + Deserialize<'de>,
79    {
80        match self.inner.get::<T>(T::PREFIX) {
81            Ok(o) => Ok(o),
82            Err(e) => {
83                let ConfigError::NotFound(_) = &e else {
84                    return Err(e);
85                };
86
87                let v = config::Value::new(None, ValueKind::Table(Default::default()));
88
89                match T::deserialize(v) {
90                    Ok(o) => Ok(o),
91                    Err(_) => Err(e),
92                }
93            }
94        }
95    }
96}
97
98impl Deref for Config {
99    type Target = config::Config;
100
101    fn deref(&self) -> &Self::Target {
102        &self.inner
103    }
104}
105
106pub trait ConfigPrefix {
107    const PREFIX: &'static str;
108}