#[macro_export]
macro_rules! settings {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident, prefix = $prefix:literal { $($fields:tt)* }
) => {
$crate::settings! { @parse [$(#[$meta])*] ($vis) $name (::core::option::Option::Some($prefix)) [] $($fields)* }
};
(
$(#[$meta:meta])*
$vis:vis struct $name:ident { $($fields:tt)* }
) => {
$crate::settings! { @parse [$(#[$meta])*] ($vis) $name (::core::option::Option::None) [] $($fields)* }
};
(@parse $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt) => {
$crate::settings! { @emit $meta $vis $name $prefix $acc }
};
(@parse $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $($rest:tt)+) => {
$crate::settings! { @attrs $meta $vis $name $prefix $acc [] false (::core::option::Option::None) [] $($rest)+ }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt [$($docs:tt)*] $secret:tt $env:tt $req:tt #[doc $($doc:tt)+] $($rest:tt)+) => {
$crate::settings! { @attrs $meta $vis $name $prefix $acc [$($docs)* #[doc $($doc)+]] $secret $env $req $($rest)+ }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $docs:tt $secret:tt $env:tt $req:tt #[secret] $($rest:tt)+) => {
$crate::settings! { @attrs $meta $vis $name $prefix $acc $docs true $env $req $($rest)+ }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $docs:tt $secret:tt $env:tt $req:tt #[env($var:literal)] $($rest:tt)+) => {
$crate::settings! { @attrs $meta $vis $name $prefix $acc $docs $secret (::core::option::Option::Some($var)) $req $($rest)+ }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $docs:tt $secret:tt $env:tt $req:tt #[required_in($($profile:literal),+ $(,)?)] $($rest:tt)+) => {
$crate::settings! { @attrs $meta $vis $name $prefix $acc $docs $secret $env [$($profile),+] $($rest)+ }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $docs:tt $secret:tt $env:tt $req:tt $field:ident: Option<$ty:ty> = $default:literal $($rest:tt)*) => {
::core::compile_error!("an `Option` settings field cannot take a default: drop the `Option` (a defaulted field is always present) or drop the default");
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt [$($acc:tt)*] $docs:tt $secret:tt $env:tt $req:tt $field:ident: Option<$ty:ty> $(, $($rest:tt)*)?) => {
$crate::settings! { @parse $meta $vis $name $prefix [$($acc)* { $docs $field (optional) ($ty) $secret $env $req () }] $($($rest)*)? }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt [$($acc:tt)*] $docs:tt $secret:tt $env:tt $req:tt $field:ident: $ty:ty = $default:literal $(, $($rest:tt)*)?) => {
$crate::settings! { @parse $meta $vis $name $prefix [$($acc)* { $docs $field (defaulted) ($ty) $secret $env $req ($default) }] $($($rest)*)? }
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt $acc:tt $docs:tt $secret:tt $env:tt [$($profile:literal),+] $field:ident: $ty:ty $(, $($rest:tt)*)?) => {
::core::compile_error!("`#[required_in(…)]` on an already-required field does nothing: make it `Option<T>` (unset is fine elsewhere) or give it a default");
};
(@attrs $meta:tt $vis:tt $name:ident $prefix:tt [$($acc:tt)*] $docs:tt $secret:tt $env:tt $req:tt $field:ident: $ty:ty $(, $($rest:tt)*)?) => {
$crate::settings! { @parse $meta $vis $name $prefix [$($acc)* { $docs $field (required) ($ty) $secret $env $req () }] $($($rest)*)? }
};
(@emit [$(#[$meta:meta])*] ($vis:vis) $name:ident ($prefix:expr) [$({ [$($docs:tt)*] $field:ident ($kind:ident) ($ty:ty) $secret:tt ($env:expr) [$($req:literal),*] ($($default:literal)?) })*]) => {
$(#[$meta])*
#[derive(Clone)]
$vis struct $name {
$( $($docs)* pub $field: $crate::settings!(@ty $kind ($ty)), )*
}
#[allow(dead_code)]
impl $name {
pub fn from_env() -> ::core::result::Result<Self, $crate::settings::SettingsError> {
Self::from_source(|var| ::std::env::var(var).ok())
}
pub fn from_source(mut source: impl ::core::ops::FnMut(&str) -> ::core::option::Option<::std::string::String>) -> ::core::result::Result<Self, $crate::settings::SettingsError> {
#[allow(unused_mut)]
let mut errors: ::std::vec::Vec<$crate::settings::FieldError> = ::std::vec::Vec::new();
#[allow(unused_variables)]
let profile = $crate::settings::profile(&mut source);
$(
let $field = {
let var = $crate::settings::env_name($prefix, $env, ::core::stringify!($field));
let raw = $crate::settings::lookup(&mut source, &var);
$crate::settings!(@get $kind ($ty), raw, var, $secret, errors, profile, [$($req),*] $(, $default)?)
};
)*
if !errors.is_empty() {
return ::core::result::Result::Err($crate::settings::SettingsError { errors });
}
::core::result::Result::Ok(Self {
$( $field: $field.expect("field parsed: no error was recorded for it"), )*
})
}
pub fn var_names() -> ::std::vec::Vec<::std::string::String> {
::std::vec![ $( $crate::settings::env_name($prefix, $env, ::core::stringify!($field)), )* ]
}
#[allow(unused_variables)] pub fn required_var_names(profile: &str) -> ::std::vec::Vec<::std::string::String> {
#[allow(unused_mut)]
let mut vars: ::std::vec::Vec<::std::string::String> = ::std::vec::Vec::new();
$(
if $crate::settings!(@required_here $kind [$($req),*], profile) {
vars.push($crate::settings::env_name($prefix, $env, ::core::stringify!($field)));
}
)*
vars
}
}
impl ::core::fmt::Debug for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(::core::stringify!($name))
$( .field(::core::stringify!($field), $crate::settings!(@dbg $secret $kind (self.$field))) )*
.finish()
}
}
};
(@ty optional ($ty:ty)) => { ::core::option::Option<$ty> };
(@ty defaulted ($ty:ty)) => { $ty };
(@ty required ($ty:ty)) => { $ty };
(@get required ($ty:ty), $raw:expr, $var:expr, $secret:tt, $errors:ident, $profile:ident, []) => {
$crate::settings::require::<$ty>($raw, &$var, $secret, &mut $errors)
};
(@get optional ($ty:ty), $raw:expr, $var:expr, $secret:tt, $errors:ident, $profile:ident, []) => {
$crate::settings::optional::<$ty>($raw, &$var, $secret, &mut $errors)
};
(@get optional ($ty:ty), $raw:expr, $var:expr, $secret:tt, $errors:ident, $profile:ident, [$($req:literal),+]) => {
$crate::settings::optional_required_in::<$ty>($raw, &$var, $secret, &mut $errors, &$profile, &[$($req),+])
};
(@get defaulted ($ty:ty), $raw:expr, $var:expr, $secret:tt, $errors:ident, $profile:ident, [], $default:literal) => {
$crate::settings::with_default::<$ty>($raw, $default, &$var, $secret, &mut $errors)
};
(@get defaulted ($ty:ty), $raw:expr, $var:expr, $secret:tt, $errors:ident, $profile:ident, [$($req:literal),+], $default:literal) => {
$crate::settings::with_default_required_in::<$ty>($raw, $default, &$var, $secret, &mut $errors, &$profile, &[$($req),+])
};
(@required_here required [], $profile:expr) => { true };
(@required_here $kind:ident [], $profile:expr) => { false };
(@required_here $kind:ident [$($req:literal),+], $profile:expr) => { [$($req),+].iter().any(|named| *named == $profile) };
(@dbg true optional ($($e:tt)+)) => { &$($e)+.as_ref().map(|_| "***") };
(@dbg true $kind:ident ($($e:tt)+)) => { &"***" };
(@dbg false $kind:ident ($($e:tt)+)) => { &$($e)+ };
}