use serde::de::DeserializeOwned; use std::{ collections::HashMap, env::VarError, io::ErrorKind, path::{Path, PathBuf}, }; use thiserror::Error; /// An error we might encounter when loading an env #[derive(Debug, Error)] pub enum EnvLoadError { #[error("i/o error")] IOError(#[from] std::io::Error), #[error("varerror")] VarError(#[from] VarError), #[error("line parse error: `{on_line}` at char {at_char}")] LineParse { on_line: String, at_char: usize }, #[error("other dotenvy error")] Other(#[from] dotenvy::Error), #[error("missing value {0}")] MissingValue(String), #[error("parse error: {0}")] OtherParseError(String), } pub enum LoadedEnv { /// We loaded config from `.env` and env vars FoundFile { config: T, path: PathBuf }, /// We could not find `.env` and only loaded env vars OnlyVars(T), } impl LoadedEnv { pub fn get_config(&self) -> &T { match self { Self::FoundFile { config, .. } => config, Self::OnlyVars(config) => config, } } } /// Load the configuration type `T` from the current environment, /// including the `.env` if it exists. #[expect(clippy::wildcard_enum_match_arm)] pub fn load_env() -> Result, EnvLoadError> { let env_path = match dotenvy::dotenv() { Ok(path) => Some(path), Err(dotenvy::Error::Io(err)) => match err.kind() { ErrorKind::NotFound => None, _ => return Err(EnvLoadError::IOError(err)), }, Err(dotenvy::Error::EnvVar(err)) => { return Err(EnvLoadError::VarError(err)); } Err(dotenvy::Error::LineParse(on_line, at_char)) => { return Err(EnvLoadError::LineParse { on_line, at_char }); } Err(err) => { return Err(EnvLoadError::Other(err)); } }; match envy::from_env::() { Ok(config) => { if let Some(path) = env_path { return Ok(LoadedEnv::FoundFile { path, config }); } else { return Ok(LoadedEnv::OnlyVars(config)); } } Err(envy::Error::MissingValue(value)) => { return Err(EnvLoadError::MissingValue(value.into())); } Err(envy::Error::Custom(message)) => { return Err(EnvLoadError::OtherParseError(message)); } }; } /// Load an .env file to a hashmap. /// /// This function does not read the current env, /// only parsing vars explicitly declared in the given file. pub fn load_env_dict(p: impl AsRef) -> Result, EnvLoadError> { let mut out = HashMap::new(); for item in dotenvy::from_filename_iter(p)? { let (key, val) = item?; out.insert(key, val); } return Ok(out); }