#[cfg(feature = "dotenv")]
pub mod dotenv;
pub mod error;
pub mod lock;
pub mod parse;
#[cfg(feature = "watch")]
pub mod watcher;
pub use error::EnvLockError;
pub use parse::{FromEnvStr, Secret};
#[doc(hidden)]
#[cfg(feature = "serde")]
pub use serde;
#[doc(hidden)]
#[cfg(feature = "tracing")]
pub use tracing;
#[macro_export]
macro_rules! env_struct {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident {
prefix = $prefix:literal,
$($key:ident : $ty:ty $(= $def:expr)?),* $(,)?
}
) => {
$crate::__env_struct_impl! { $(#[$meta])* $vis $name $prefix [ $($key : $ty $(= $def)?),* ] }
};
(
$(#[$meta:meta])*
$vis:vis struct $name:ident {
$($key:ident : $ty:ty $(= $def:expr)?),* $(,)?
}
) => {
$crate::__env_struct_impl! { $(#[$meta])* $vis $name "" [ $($key : $ty $(= $def)?),* ] }
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __env_struct_impl {
(
$(#[$meta:meta])* $vis:vis $name:ident $prefix:literal [ $($key:ident : $ty:ty $(= $def:expr)?),* $(,)? ]
) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive($crate::serde::Serialize, $crate::serde::Deserialize))]
$vis struct $name {
$(pub $key: $ty,)*
}
impl $name {
pub fn load() -> Self {
Self::try_load().unwrap_or_else(|e| panic!("{}", e))
}
pub fn try_load() -> Result<Self, $crate::EnvLockError> {
$(
#[allow(non_snake_case)]
let $key: $ty = $crate::load_internal!(
@read_field $ty, concat!($prefix, stringify!($key)) $(, $def)?
);
)*
#[cfg(feature = "tracing")]
{
let __result = Self { $($key: $key.clone()),* };
$crate::tracing::info!(config = ?__result, "lockedenv loaded");
}
Ok(Self { $($key),* })
}
pub fn from_map(__map: &std::collections::HashMap<String, String>) -> Self {
Self::try_from_map(__map).unwrap_or_else(|e| panic!("{}", e))
}
pub fn try_from_map(
__map: &std::collections::HashMap<String, String>,
) -> Result<Self, $crate::EnvLockError> {
$(
#[allow(non_snake_case)]
let $key: $ty = {
let __full_key = concat!($prefix, stringify!($key));
match __map.get(__full_key) {
Some(__val) => {
<$ty as $crate::parse::FromEnvStr>::from_env_str(__val)
.map_err(|e| {
let __found = if <$ty as $crate::parse::FromEnvStr>::REDACT_IN_ERRORS {
"[REDACTED]".into()
} else {
__val.to_string()
};
$crate::EnvLockError::parse_error(
__full_key.into(),
__found,
e,
)
})?
}
None => $crate::load_internal!(@map_none $ty, __full_key $(, $def)?),
}
};
)*
Ok(Self { $($key),* })
}
}
};
}
#[macro_export]
macro_rules! try_load {
{ prefix = $prefix:literal, $($rest:tt)* } => {
$crate::load_internal!(@env $prefix, $($rest)* )
};
{ $($rest:tt)* } => {
$crate::load_internal!(@env "", $($rest)* )
};
}
#[macro_export]
macro_rules! load {
{ $($rest:tt)* } => {
$crate::try_load! { $($rest)* }.unwrap_or_else(|e| panic!("{}", e))
};
}
#[macro_export]
macro_rules! try_from_map {
(map: $map:expr, prefix = $prefix:literal, $($rest:tt)*) => {
$crate::load_internal!(@map $map, $prefix, $($rest)*)
};
(map: $map:expr, $($rest:tt)*) => {
$crate::load_internal!(@map $map, "", $($rest)*)
};
}
#[macro_export]
macro_rules! from_map {
(map: $map:expr, $($rest:tt)*) => {
$crate::try_from_map!(map: $map, $($rest)*).unwrap_or_else(|e| panic!("{}", e))
};
}
#[macro_export]
macro_rules! try_check {
{ map: $map:expr, prefix = $prefix:literal, $($rest:tt)* } => {
$crate::__check_internal!(@map $map, $prefix, $($rest)*)
};
{ map: $map:expr, $($key:ident : $ty:ty $(= $def:expr)?),* $(,)? } => {
$crate::__check_internal!(@map $map, "", $($key: $ty $(= $def)?),*)
};
{ prefix = $prefix:literal, $($rest:tt)* } => {
$crate::__check_internal!(@env $prefix, $($rest)*)
};
{ $($key:ident : $ty:ty $(= $def:expr)?),* $(,)? } => {
$crate::__check_internal!(@env "", $($key: $ty $(= $def)?),*)
};
}
#[macro_export]
macro_rules! check {
{ map: $map:expr, prefix = $prefix:literal, $($rest:tt)* } => {
$crate::try_check! { map: $map, prefix = $prefix, $($rest)* }
.unwrap_or_else(|__errs| {
let __msg = __errs.iter()
.map(|e| format!(" - {}", e))
.collect::<Vec<_>>()
.join("\n");
panic!("{} configuration error(s):\n{}", __errs.len(), __msg)
})
};
{ map: $map:expr, $($rest:tt)* } => {
$crate::try_check! { map: $map, $($rest)* }
.unwrap_or_else(|__errs| {
let __msg = __errs.iter()
.map(|e| format!(" - {}", e))
.collect::<Vec<_>>()
.join("\n");
panic!("{} configuration error(s):\n{}", __errs.len(), __msg)
})
};
{ $($rest:tt)* } => {
$crate::try_check! { $($rest)* }
.unwrap_or_else(|__errs| {
let __msg = __errs.iter()
.map(|e| format!(" - {}", e))
.collect::<Vec<_>>()
.join("\n");
panic!("{} configuration error(s):\n{}", __errs.len(), __msg)
})
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __check_internal {
(@env $prefix:expr, $($key:ident : $ty:ty $(= $def:expr)?),* $(,)?) => {{
#[allow(non_snake_case)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive($crate::serde::Serialize, $crate::serde::Deserialize))]
struct __CheckConfig { $($key: $ty,)* }
let mut __errors: Vec<$crate::EnvLockError> = Vec::new();
$(
#[allow(non_snake_case)]
let $key: Result<$ty, $crate::EnvLockError> =
$crate::__check_read_env!($ty, concat!($prefix, stringify!($key)) $(, $def)?);
if let Err(ref __e) = $key {
__errors.push(__e.clone());
}
)*
if __errors.is_empty() {
#[allow(clippy::unwrap_used)]
Ok(__CheckConfig { $($key: $key.unwrap()),* })
} else {
Err(__errors)
}
}};
(@map $map:expr, $prefix:expr, $($key:ident : $ty:ty $(= $def:expr)?),* $(,)?) => {{
#[allow(non_snake_case)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive($crate::serde::Serialize, $crate::serde::Deserialize))]
struct __CheckConfig { $($key: $ty,)* }
let __check_map = &$map;
let mut __errors: Vec<$crate::EnvLockError> = Vec::new();
$(
#[allow(non_snake_case)]
let $key: Result<$ty, $crate::EnvLockError> = {
let __full_key = concat!($prefix, stringify!($key));
match __check_map.get(__full_key) {
Some(__val) => {
<$ty as $crate::parse::FromEnvStr>::from_env_str(__val)
.map_err(|e| {
let __found = if <$ty as $crate::parse::FromEnvStr>::REDACT_IN_ERRORS {
"[REDACTED]".into()
} else {
__val.to_string()
};
$crate::EnvLockError::parse_error(__full_key.into(), __found, e)
})
}
None => $crate::__check_map_none!($ty, __full_key $(, $def)?),
}
};
if let Err(ref __e) = $key {
__errors.push(__e.clone());
}
)*
if __errors.is_empty() {
#[allow(clippy::unwrap_used)]
Ok(__CheckConfig { $($key: $key.unwrap()),* })
} else {
Err(__errors)
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __check_read_env {
($ty:ty, $key:expr, $def:expr) => {
$crate::lock::__read_default::<$ty>($key, $def)
};
($ty:ty, $key:expr) => {
$crate::lock::__read_required::<$ty>($key)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __check_map_none {
($ty:ty, $key:expr, $def:expr) => {
Ok::<$ty, $crate::EnvLockError>($def)
};
($ty:ty, $key:expr) => {
$crate::lock::__missing_value::<$ty>($key)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! load_internal {
(@env $prefix:expr,
$($key:ident : $ty:ty $(= $def:expr)?),* $(,)? ) => {
{
#[allow(non_snake_case)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive($crate::serde::Serialize, $crate::serde::Deserialize))]
struct __EnvLockConfig {
$( $key: $ty, )*
}
let cfg = (|| -> Result<__EnvLockConfig, $crate::EnvLockError> {
$(
#[allow(non_snake_case)]
let $key: $ty = $crate::load_internal!(@read_field $ty, concat!($prefix, stringify!($key)) $(, $def)? );
)*
let result = __EnvLockConfig { $( $key ),* };
$crate::load_internal!(@log result);
Ok(result)
})();
cfg
}
};
(@map $map:expr, $prefix:expr,
$($key:ident : $ty:ty $(= $def:expr)?),* $(,)? ) => {
{
#[allow(non_snake_case)]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive($crate::serde::Serialize, $crate::serde::Deserialize))]
struct __EnvLockConfig {
$( $key: $ty, )*
}
let __env_lock_map = &$map;
let cfg = (|| -> Result<__EnvLockConfig, $crate::EnvLockError> {
$(
#[allow(non_snake_case)]
let $key: $ty = {
let full_key = concat!($prefix, stringify!($key));
let v_opt = __env_lock_map.get(full_key);
match v_opt {
Some(val) => {
<$ty as $crate::parse::FromEnvStr>::from_env_str(val)
.map_err(|e| {
let found = if <$ty as $crate::parse::FromEnvStr>::REDACT_IN_ERRORS {
"[REDACTED]".into()
} else {
val.to_string()
};
$crate::EnvLockError::parse_error(full_key.into(), found, e)
})?
}
None => $crate::load_internal!(@map_none $ty, full_key $(, $def)?),
}
};
)*
let result = __EnvLockConfig { $( $key ),* };
$crate::load_internal!(@log result);
Ok(result)
})();
cfg
}
};
(@log $cfg:ident) => {
#[cfg(feature = "tracing")]
$crate::tracing::info!(config = ?$cfg, "lockedenv loaded");
};
(@map_none $ty:ty, $key:expr) => {
$crate::lock::__missing_value::<$ty>($key)?
};
(@map_none $ty:ty, $key:expr, $def:expr) => {
$def
};
(@read_field $ty:ty, $key:expr, $def:expr) => {
$crate::lock::__read_default::<$ty>($key, $def)?
};
(@read_field $ty:ty, $key:expr) => {
$crate::lock::__read_required::<$ty>($key)?
};
}
#[cfg(feature = "dotenv")]
#[macro_export]
macro_rules! load_dotenv {
(path: $path:expr, $($rest:tt)*) => {
{
$crate::dotenv::load_file($path).unwrap_or_else(|e| panic!("{}", e));
$crate::load! { $($rest)* }
}
};
}
#[cfg(feature = "dotenv")]
#[macro_export]
macro_rules! try_load_dotenv {
(path: $path:expr, $($rest:tt)*) => {
{
$crate::dotenv::load_file($path)?;
$crate::try_load! { $($rest)* }
}
};
}
#[cfg(feature = "watch")]
#[macro_export]
macro_rules! watch {
(keys = [$($key:expr),* $(,)?], interval_secs = $secs:expr, on_drift = $cb:expr) => {
$crate::watcher::start(
vec![$($key.to_string()),*],
std::time::Duration::from_secs($secs),
$cb,
)
};
(keys = [$($key:expr),* $(,)?], interval_ms = $ms:expr, on_drift = $cb:expr) => {
$crate::watcher::start(
vec![$($key.to_string()),*],
std::time::Duration::from_millis($ms),
$cb,
)
};
(keys = [$($key:expr),* $(,)?], on_drift = $cb:expr) => {
$crate::watcher::start(
vec![$($key.to_string()),*],
std::time::Duration::from_secs(5),
$cb,
)
};
}