#![doc = include_str!("../README.md")]
use std::{env::VarError, str::FromStr};
#[derive(Debug, thiserror::Error)]
#[error("error reading {key} env var: {reason}")]
pub struct Error<T> {
pub key: &'static str,
#[source]
pub reason: T,
}
impl<T> Error<T> {
pub fn new(key: &'static str, reason: T) -> Self {
Self { key, reason }
}
pub fn map_reason<U, F>(self, map: F) -> Error<U>
where
F: FnOnce(T) -> U,
{
let Self { key, reason } = self;
let reason = (map)(reason);
Error { key, reason }
}
}
#[derive(Debug, thiserror::Error)]
pub enum ValueError<T> {
#[error("value is not a valid unicode")]
NonUnicode,
#[error("unable to parse: {0}")]
Parse(#[source] T),
}
#[derive(Debug, thiserror::Error)]
pub enum MustError<T> {
#[error("not set")]
NotSet,
#[error(transparent)]
Value(ValueError<T>),
}
#[derive(Debug, thiserror::Error)]
pub enum OrParseError<T> {
#[error(transparent)]
Value(ValueError<T>),
#[error("unable to parse the default value while the variable was not set: {0}")]
ParseDefault(T),
}
pub fn maybe<T>(key: &'static str) -> Result<Option<T>, Error<ValueError<T::Err>>>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
let val = match std::env::var(key) {
Ok(val) => val,
Err(VarError::NotPresent) => return Ok(None),
Err(VarError::NotUnicode(_)) => return Err(Error::new(key, ValueError::NonUnicode)),
};
let val = val
.parse()
.map_err(|err| Error::new(key, ValueError::Parse(err)))?;
Ok(Some(val))
}
pub fn must<T>(key: &'static str) -> Result<T, Error<MustError<T::Err>>>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
match maybe(key) {
Ok(Some(val)) => Ok(val),
Ok(None) => Err(Error::new(key, MustError::NotSet)),
Err(err) => Err(err.map_reason(MustError::Value)),
}
}
pub fn or<T>(key: &'static str, default: T) -> Result<T, Error<ValueError<T::Err>>>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
let val = maybe(key)?;
Ok(val.unwrap_or(default))
}
pub fn or_else<T, F>(key: &'static str, default: F) -> Result<T, Error<ValueError<T::Err>>>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
F: FnOnce() -> T,
{
let val = maybe(key)?;
Ok(val.unwrap_or_else(default))
}
pub fn or_parse<T>(
key: &'static str,
default: impl Into<String>,
) -> Result<T, Error<OrParseError<T::Err>>>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
let val = maybe(key).map_err(|err| err.map_reason(OrParseError::Value))?;
if let Some(val) = val {
return Ok(val);
}
let val = default
.into()
.parse()
.map_err(|err| Error::new(key, OrParseError::ParseDefault(err)))?;
Ok(val)
}