rok-core 0.6.0

Core primitives for the rok ecosystem — errors, crypto, i18n, config, DI, and more
Documentation
#[cfg(feature = "config-macros")]
pub use rok_config_macros::Config;
#[cfg(feature = "config-macros")]
pub use rok_config_macros::RokConfig;

pub trait FromEnv: Sized {
    fn from_env() -> Self;
}

/// Trait for configuration structs that can be auto-discovered and loaded
/// from `config/{key}.toml` files at application boot.
///
/// Implementors also receive a `fn load()` method via the derive macro.
pub trait Configurable: Sized + serde::de::DeserializeOwned + Send + Sync + 'static {
    /// The config key used to look up files in the `config/` directory.
    fn key() -> &'static str;
}

/// Loads a config from `config/{key}.toml`, falling back to env vars.
pub fn load_config<T: Configurable>() -> Option<T> {
    // Try config/{key}.toml first
    let path = std::path::Path::new("config").join(format!("{}.toml", T::key()));
    if path.exists() {
        let content = std::fs::read_to_string(&path).ok()?;
        if let Ok(cfg) = toml::from_str(&content) {
            return Some(cfg);
        }
    }
    None
}

/// Auto-discover and register all configs found in `config/*.toml`.
/// Called during `App::boot()`.
pub fn discover_configs() -> Vec<(String, std::path::PathBuf)> {
    let config_dir = std::path::Path::new("config");
    if !config_dir.is_dir() {
        return Vec::new();
    }

    let mut entries = Vec::new();
    if let Ok(dir) = std::fs::read_dir(config_dir) {
        for entry in dir.flatten() {
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("toml") {
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string()) {
                    entries.push((stem, path));
                }
            }
        }
    }
    entries
}

pub struct Config;

impl Config {
    pub fn load<T: FromEnv>() -> T {
        let _ = ::dotenvy::dotenv();
        T::from_env()
    }
}