configuration/
lib.rs

1use std::{env::current_dir, fmt::Display, fs::File, path::PathBuf};
2
3use anyhow::{anyhow, Context, Error};
4use serde::de::DeserializeOwned;
5
6pub enum Environment {
7    Local,
8    Production,
9}
10
11impl Environment {
12    pub fn as_str(&self) -> &'static str {
13        match self {
14            Self::Local => "local",
15            Self::Production => "production",
16        }
17    }
18}
19
20impl TryFrom<String> for Environment {
21    type Error = anyhow::Error;
22
23    fn try_from(value: String) -> Result<Self, Self::Error> {
24        match value.to_lowercase().as_str() {
25            "local" => Ok(Environment::Local),
26            "production" => Ok(Environment::Production),
27            _ => Err(anyhow!("Couldn't parse environment from '{}'", value)),
28        }
29    }
30}
31
32impl Display for Environment {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{}", self.as_str())
35    }
36}
37
38pub fn read_configuration<T>(
39    environment_variable: &str,
40    configuration_path_variable: &str,
41) -> Result<T, Error>
42where
43    T: DeserializeOwned,
44{
45    let environment = std::env::var(environment_variable)
46        .unwrap_or_else(|_| "local".into())
47        .try_into()
48        .map_err(|error| Error::from(error).context("Failed to parse environment variable"))?;
49
50    match environment {
51        Environment::Local => load_env_file().context("Failed to load .env")?,
52        _ => (),
53    }
54
55    let configuration_path = std::env::var(configuration_path_variable)
56        .map(|path| PathBuf::from(path))
57        .context("Failed to parse configuration path")?;
58
59    match File::open(&configuration_path) {
60        Ok(file) => serde_json::from_reader(file).map_err(|error| {
61            Error::from(error).context(format!(
62                "Failed to parse configuration file {}",
63                configuration_path.to_string_lossy()
64            ))
65        }),
66        Err(error) => Err(Error::from(error).context(format!(
67            "Failed to open configuration {}",
68            configuration_path.to_string_lossy()
69        ))),
70    }
71}
72
73fn load_env_file() -> Result<(), Error> {
74    let current_dir = current_dir()
75        .context("Failed to get current directory")?
76        .join(".env");
77    let file = std::fs::read_to_string(&current_dir).context(format!(
78        "Failed to read .env {}",
79        current_dir.to_string_lossy()
80    ))?;
81
82    for line in file.split("\n") {
83        if line.len() == 0 {
84            continue;
85        }
86
87        let mut parts = line.split("=");
88        std::env::set_var(parts.next().unwrap(), parts.next().unwrap());
89    }
90
91    Ok(())
92}