env_loader/
lib.rs

1pub use env_loader_convert::convert;
2mod error;
3mod inner_value;
4#[cfg(test)]
5mod tests;
6mod validators;
7use error::{ConfigLoaderError, ConstraintValidationError};
8use inner_value::{InnerValue, Value};
9use std::collections::HashMap;
10use std::env;
11use validators::Constraint;
12
13pub struct ConfigLoader(HashMap<String, InnerValue>);
14
15fn parse_constraint(constraint_mask: &str) -> Option<Vec<Constraint>> {
16    if constraint_mask == ",,," || constraint_mask.is_empty() {
17        return None;
18    }
19    let splitted: Vec<&str> = constraint_mask.split(',').collect();
20    let mut res = vec![];
21    for (index, value) in splitted.iter().enumerate() {
22        if splitted[index].is_empty() {
23            continue;
24        }
25        match index {
26            0 => res.push(Constraint::Max(value.parse().unwrap())),
27            1 => res.push(Constraint::Min(value.parse().unwrap())),
28            2 => res.push(Constraint::Optional),
29            3 => res.push(Constraint::NotEmpty),
30            // 4 => {
31            //     let minmax: Vec<i64> = value
32            //         .split(';')
33            //         .map(|v| v.parse::<i64>().unwrap())
34            //         .collect();
35            //     res.push(Constraint::MinMax(minmax[0], minmax[1]))
36            // }
37            4 => res.push(Constraint::Len(value.parse().unwrap())),
38            _ => continue,
39        }
40    }
41    Some(res)
42}
43
44fn check(
45    val: &inner_value::InnerValue,
46    constraints: &Option<Vec<Constraint>>,
47) -> Result<bool, ConstraintValidationError> {
48    match constraints {
49        Some(constraints) => match val {
50            InnerValue::Int(val) => validators::check_num(*val as i64, constraints),
51            InnerValue::Long(val) => validators::check_num(*val, constraints),
52            InnerValue::Str(val) => validators::check_str(val, constraints),
53            _ => Ok(true),
54        },
55        None => Ok(true),
56    }
57}
58
59impl ConfigLoader {
60    pub fn new<T>(names: T, path: Option<&'static str>) -> Result<Self, ConfigLoaderError>
61    where
62        T: IntoIterator<Item = (&'static str, Value, String)>,
63    {
64        let dotenv_reading_result = if let Some(path) = path {
65            dotenv::from_filename(path)
66        } else {
67            dotenv::dotenv()
68        };
69
70        if dotenv_reading_result.is_err() {
71            return Err(ConfigLoaderError::NoEnvFile);
72        }
73
74        let mut store = HashMap::new();
75        for (name, typing, constraints) in names {
76            let value = env::var(name);
77            let constraints = parse_constraint(&constraints);
78            let is_optional_value = constraints
79                .as_ref()
80                .is_some_and(|constraints| constraints.contains(&Constraint::Optional));
81            if value.is_err() && !is_optional_value {
82                return Err(ConfigLoaderError::ValueNotInEnv(format!(
83                    "{} not in env file. Add it to file or mark as optional",
84                    name
85                )));
86            }
87            match value {
88                Ok(value) => {
89                    let res: InnerValue = (value, &typing).into();
90
91                    match res {
92                        InnerValue::None => return Err(ConfigLoaderError::WrongConvertion),
93                        val => match check(&val, &constraints) {
94                            Err(e) => {
95                                return Err(ConfigLoaderError::ValueValidationFail(e));
96                            }
97
98                            Ok(_) => store.insert(String::from(name), val),
99                        },
100                    };
101                }
102                Err(_) => {
103                    if !is_optional_value {
104                        return Err(ConfigLoaderError::ValueNotInEnv(format!(
105                            "{} not in env file. Add it to file or mark as optional",
106                            name
107                        )));
108                    }
109                }
110            }
111        }
112        Ok(Self(store))
113    }
114
115    pub fn get<T>(&self, name: &'static str) -> Result<T, ConfigLoaderError>
116    where
117        Option<T>: From<InnerValue>,
118    {
119        let val = self.0.get(name);
120        if val.is_none() {
121            return Err(ConfigLoaderError::IsNotPartOfRuntime(name));
122        }
123
124        match (*val.unwrap()).clone().into() {
125            Some(inner) => Ok(inner),
126            None => Err(ConfigLoaderError::WrongTypeTryingToGet(name)),
127        }
128    }
129}