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: e.to_string(),
}
}),
}
}
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::net::IpAddr,
std::net::Ipv4Addr,
std::net::Ipv6Addr,
std::net::SocketAddr,
std::path::PathBuf,
);
impl<T: FromEnv> FromEnv for Option<T> {
fn from_env(ctx: &Ctx, env: &Env) -> Result<Self> {
if T::is_present(ctx, env) {
Ok(Some(T::from_env(ctx, env)?))
} else {
Ok(None)
}
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
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());
for seg in &segments {
match seg.parse::<usize>() {
Ok(i) => indices.push(i),
Err(_) => {
return Err(ConfroidError::EnvVarInvalid {
var_name: format!("{}__{}", ctx.var, seg),
field: ctx.path.clone(),
value: seg.clone(),
parser_error: "expected a numeric vector index".to_string(),
});
}
}
}
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());
for i in 0..indices.len() {
out.push(T::from_env(&ctx.index(i), env)?);
}
Ok(out)
}
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());
for key in &keys {
map.insert(key.clone(), T::from_env(&ctx.key(key), env)?);
}
Ok(map)
}
fn is_present(ctx: &Ctx, env: &Env) -> bool {
!env.immediate_children(&ctx.var).is_empty()
}
}