use std::{env::current_dir, fmt::Display, fs::File, path::PathBuf}; use anyhow::{anyhow, Context, Error}; use serde::de::DeserializeOwned; pub enum Environment { Local, Production, } impl Environment { pub fn as_str(&self) -> &'static str { match self { Self::Local => "local", Self::Production => "production", } } } impl TryFrom for Environment { type Error = anyhow::Error; fn try_from(value: String) -> Result { match value.to_lowercase().as_str() { "local" => Ok(Environment::Local), "production" => Ok(Environment::Production), _ => Err(anyhow!("Couldn't parse environment from '{}'", value)), } } } impl Display for Environment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } pub fn read_configuration( environment_variable: &str, configuration_path_variable: &str, ) -> Result where T: DeserializeOwned, { let environment = std::env::var(environment_variable) .unwrap_or_else(|_| "local".into()) .try_into() .map_err(|error| Error::from(error).context("Failed to parse environment variable"))?; match environment { Environment::Local => load_env_file().context("Failed to load .env")?, _ => (), } let configuration_path = std::env::var(configuration_path_variable) .map(|path| PathBuf::from(path)) .context("Failed to parse configuration path")?; match File::open(&configuration_path) { Ok(file) => serde_json::from_reader(file).map_err(|error| { Error::from(error).context(format!( "Failed to parse configuration file {}", configuration_path.to_string_lossy() )) }), Err(error) => Err(Error::from(error).context(format!( "Failed to open configuration {}", configuration_path.to_string_lossy() ))), } } fn load_env_file() -> Result<(), Error> { let current_dir = current_dir() .context("Failed to get current directory")? .join(".env"); let file = std::fs::read_to_string(¤t_dir).context(format!( "Failed to read .env {}", current_dir.to_string_lossy() ))?; for line in file.split("\n") { if line.len() == 0 { continue; } let mut parts = line.split("="); std::env::set_var(parts.next().unwrap(), parts.next().unwrap()); } Ok(()) }