cfgloader_core/
lib.rs

1use std::env;
2use thiserror::Error;
3
4#[doc(hidden)]
5pub mod fallback {
6    pub fn load_or_default<T>(_env_path: &std::path::Path) -> Result<T, crate::CfgError>
7    where
8        T: Default,
9    {
10        // We need a way to detect if T implements FromEnv
11        // Due to Rust limitations, we use a simple approach: try to call T::load directly
12        // If compilation fails, it means T doesn't implement FromEnv, so we use Default
13
14        // Since we can't detect trait implementation at runtime, we return Default
15        // Users need to explicitly use #[env(...)] to load environment variables
16        Ok(T::default())
17    }
18}
19
20#[derive(Debug, Error)]
21pub enum CfgError {
22    #[error("missing required env: {0}")]
23    MissingEnv(&'static str),
24
25    #[error("failed to parse env {key} value `{value}` into {ty}: {source}")]
26    ParseError {
27        key: &'static str,
28        value: String,
29        ty: &'static str,
30        #[source]
31        source: Box<dyn std::error::Error + Send + Sync>,
32    },
33
34    #[error("failed to load env: {msg}: {source}")]
35    LoadError {
36        msg: &'static str,
37        #[source]
38        source: Box<dyn std::error::Error + Send + Sync>,
39    },
40}
41
42pub trait FromEnv: Sized {
43    fn load(env_path: &std::path::Path) -> Result<Self, CfgError>;
44}
45
46/// Utility function for macros: read env and return `Option<String>`
47pub fn get_env(key: &'static str) -> Option<String> {
48    env::var(key).ok()
49}
50
51/// Utility function for macros: load .env file
52pub fn load_env_file(env_path: &std::path::Path) -> Result<(), CfgError> {
53    // Try to load .env file if it exists, but don't fail if it doesn't
54    match dotenvy::from_path(env_path) {
55        Ok(_) => Ok(()),
56        Err(dotenvy::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
57        Err(e) => Err(CfgError::LoadError {
58            msg: "failed to load .env file",
59            source: Box::new(e),
60        }),
61    }
62}
63
64/// Utility function for macros: parse string to T
65pub fn parse_scalar<T: std::str::FromStr>(key: &'static str, raw: String) -> Result<T, CfgError>
66where
67    <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
68{
69    raw.parse::<T>().map_err(|e| CfgError::ParseError {
70        key,
71        value: raw,
72        ty: std::any::type_name::<T>(),
73        source: Box::new(e),
74    })
75}
76
77/// Split string and parse each part to `Vec<T>`
78pub fn parse_vec<T: std::str::FromStr>(
79    key: &'static str,
80    raw: String,
81    sep: &'static str,
82) -> Result<Vec<T>, CfgError>
83where
84    <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
85{
86    if sep.is_empty() {
87        return Ok(Vec::new());
88    }
89    let mut out = Vec::new();
90    for part in raw.split(sep) {
91        let s = part.trim().to_string();
92        if s.is_empty() {
93            continue;
94        }
95        out.push(parse_scalar::<T>(key, s)?);
96    }
97    Ok(out)
98}