predawn 0.9.0

Predawn is a Rust web framework like Spring Boot.
Documentation
pub mod logger;
pub mod openapi;
pub mod server;

use std::{
    env,
    ops::Deref,
    path::{Path, PathBuf},
    sync::LazyLock,
};

use config::{ConfigError, File, ValueKind};
use serde::Deserialize;

use crate::environment::Environment;

#[derive(Clone)]
pub struct Config {
    inner: config::Config,
}

impl Config {
    pub fn new(config: config::Config) -> Self {
        Self { inner: config }
    }

    pub fn load(env: &Environment) -> Result<Self, ConfigError> {
        static DEFAULT_FOLDER: LazyLock<PathBuf> = LazyLock::new(|| {
            let mut dir_path = match env::var("CARGO_MANIFEST_DIR") {
                Ok(dir) => PathBuf::from(dir),
                Err(_) => {
                    let binary_file_path =
                        env::args().next().expect("failed to get binary file path");

                    Path::new(&binary_file_path)
                        .parent()
                        .expect("failed to get parent directory of binary file")
                        .canonicalize()
                        .expect("failed to get canonical, absolute form of the path to the parent directory of binary file")
                }
            };

            dir_path.push("config");
            dir_path
        });

        Self::from_folder(env, DEFAULT_FOLDER.as_path())
    }

    pub fn from_folder(env: &Environment, path: &Path) -> Result<Self, ConfigError> {
        let app_cfg = path.join("app.toml");
        let env_cfg = path.join(format!("app-{}.toml", env));
        let env = config::Environment::default().separator("_");

        let mut builder = config::Config::builder();

        for cfg in [app_cfg, env_cfg].into_iter() {
            tracing::info!("try to load config `{}`", cfg.display());

            if cfg.exists() {
                builder = builder.add_source(File::from(cfg))
            } else {
                tracing::info!("not found config `{}`", cfg.display());
            }
        }

        let config = builder.add_source(env).build()?;

        Ok(Self { inner: config })
    }

    pub fn is_debug(&self) -> bool {
        self.inner.get_bool("debug").unwrap_or_default()
    }

    pub fn get<'de, T>(&self) -> Result<T, ConfigError>
    where
        T: ConfigPrefix + Deserialize<'de>,
    {
        match self.inner.get::<T>(T::PREFIX) {
            Ok(o) => Ok(o),
            Err(e) => {
                let ConfigError::NotFound(_) = &e else {
                    return Err(e);
                };

                let v = config::Value::new(None, ValueKind::Table(Default::default()));

                match T::deserialize(v) {
                    Ok(o) => Ok(o),
                    Err(_) => Err(e),
                }
            }
        }
    }
}

impl Deref for Config {
    type Target = config::Config;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

pub trait ConfigPrefix {
    const PREFIX: &'static str;
}