use std::collections::HashMap;
use std::hash::BuildHasher;
use crate::env::{Ctx, Env};
use crate::error::{ConfroidError, Result};
pub trait FromEnv: Sized {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self>;
fn is_present(ctx: &Ctx, env: &Env) -> bool;
}
macro_rules! impl_scalar {
($($ty:ty),+ $(,)?) => {
$(
impl FromEnv for $ty {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
match env.get(&ctx.var) {
None => Err(ConfroidError::EnvVarNotFound {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
}),
Some(raw) => raw.parse::<$ty>().map_err(|e| {
ConfroidError::EnvVarInvalid {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
value: raw.to_string(),
parser_error: Box::new(e),
}
}),
}
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
env.get(&ctx.var).is_some()
}
}
)+
};
}
impl_scalar!(
u8,
u16,
u32,
u64,
u128,
usize,
i8,
i16,
i32,
i64,
i128,
isize,
f32,
f64,
bool,
char,
String,
std::num::NonZeroU16,
std::net::IpAddr,
std::net::Ipv4Addr,
std::net::Ipv6Addr,
std::net::SocketAddr,
std::net::SocketAddrV4,
std::net::SocketAddrV6,
std::path::PathBuf,
);
macro_rules! impl_string_scalar {
($ty:ty, $convert:expr) => {
impl FromEnv for $ty {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
env.get(&ctx.var)
.map($convert)
.ok_or_else(|| ConfroidError::EnvVarNotFound {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
})
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
env.get(&ctx.var).is_some()
}
}
};
}
impl_string_scalar!(Box<str>, |raw: &str| Box::<str>::from(raw));
impl_string_scalar!(std::sync::Arc<str>, |raw: &str| {
std::sync::Arc::<str>::from(raw)
});
#[cfg(feature = "url")]
impl_scalar!(url::Url);
#[cfg(feature = "humantime")]
#[doc(hidden)]
pub fn parse_humantime_duration(ctx: &Ctx, env: &Env) -> Result<std::time::Duration> {
match env.get(&ctx.var) {
None => Err(ConfroidError::EnvVarNotFound {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
}),
Some(raw) => humantime::parse_duration(raw).map_err(|error| ConfroidError::EnvVarInvalid {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
value: raw.to_string(),
parser_error: Box::new(error),
}),
}
}
impl<T: FromEnv> FromEnv for Option<T> {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
if Self::is_present(ctx, env) {
Ok(Some(T::from_env(ctx, env)?))
} else {
Ok(None)
}
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
let is_empty_leaf = env.get(&ctx.var).is_some_and(str::is_empty)
&& env.immediate_children(&ctx.var).is_empty();
!is_empty_leaf && T::is_present(ctx, env)
}
}
impl<T: FromEnv> FromEnv for Vec<T> {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
let segments = env.immediate_children(&ctx.var);
if segments.is_empty() {
return Err(ConfroidError::EnvVarNotFound {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
});
}
let mut indices = Vec::with_capacity(segments.len());
let mut errors = Vec::new();
for seg in &segments {
match seg.parse::<usize>() {
Ok(i) => indices.push(i),
Err(_) => errors.push(ConfroidError::VecIndexInvalid {
var_name: format!("{}__{}", ctx.var, seg),
field: ctx.path.clone(),
index: seg.clone(),
}),
}
}
if !errors.is_empty() {
return Err(ConfroidError::from_errors(errors));
}
indices.sort_unstable();
for (expected, &got) in indices.iter().enumerate() {
if expected != got {
return Err(ConfroidError::VecIndexGap {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
missing: expected,
});
}
}
let mut out = Vec::with_capacity(indices.len());
let mut errors = Vec::new();
for i in 0..indices.len() {
match T::from_env(&ctx.index(i), env) {
Ok(value) => out.push(value),
Err(error) => errors.extend(error.into_errors()),
}
}
if errors.is_empty() {
Ok(out)
} else {
Err(ConfroidError::from_errors(errors))
}
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
!env.immediate_children(&ctx.var).is_empty()
}
}
impl<T: FromEnv, S: BuildHasher + Default> FromEnv for HashMap<String, T, S> {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
let keys = env.immediate_children(&ctx.var);
if keys.is_empty() {
return Err(ConfroidError::EnvVarNotFound {
var_name: ctx.var.clone(),
field: ctx.path.clone(),
});
}
let mut map = HashMap::with_hasher(S::default());
let mut errors = Vec::new();
for key in &keys {
match T::from_env(&ctx.key(key), env) {
Ok(value) => {
map.insert(key.clone(), value);
}
Err(error) => errors.extend(error.into_errors()),
}
}
if errors.is_empty() {
Ok(map)
} else {
Err(ConfroidError::from_errors(errors))
}
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
!env.immediate_children(&ctx.var).is_empty()
}
}