use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
ffi::OsString,
fmt::Display,
hash::{BuildHasher, Hash},
marker::PhantomData,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
path::PathBuf,
time::{Duration, SystemTime},
};
use serde::{de::DeserializeOwned, Deserialize};
use crate::{Configuration, ConfigurationBuilder, Error, MissingValue, UnexpectedSecret};
macro_rules! impl_multi_source_via_option {
($type:ty) => {
impl Configuration for $type {
type Builder = Option<Self>;
}
};
($($type:ty),* $(,)?) => {
$(
impl_multi_source_via_option! { $type }
)*
};
}
impl_multi_source_via_option! {
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
f32, f64,
SocketAddr, SocketAddrV4, SocketAddrV6, IpAddr, Ipv4Addr, Ipv6Addr,
Duration, SystemTime,
String, OsString, PathBuf, char, bool,
}
type KeyOf<C> = <C as KeyedContainer>::Key;
type ItemOf<C> = <C as IntoIterator>::Item;
type ValueOf<C> = <C as KeyedContainer>::Value;
type TargetOf<B> = <B as ConfigurationBuilder>::Target;
type BuilderOf<T> = <T as Configuration>::Builder;
#[derive(Debug, Default, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
#[serde(from = "Container")]
pub enum UnkeyedContainerBuilder<Container, Target> {
#[default]
Unspecified,
Some(Container),
_PhantomData(PhantomData<fn() -> Target>),
}
impl<Container, Target> From<Container> for UnkeyedContainerBuilder<Container, Target> {
fn from(value: Container) -> Self {
Self::Some(value)
}
}
impl<Container, Target> ConfigurationBuilder for UnkeyedContainerBuilder<Container, Target>
where
Self: DeserializeOwned,
Container: IntoIterator + 'static,
ItemOf<Container>: ConfigurationBuilder,
Target: Default + FromIterator<TargetOf<ItemOf<Container>>>,
for<'a> &'a Container: IntoIterator<Item = &'a ItemOf<Container>>,
{
type Target = Target;
fn merge(self, other: Self) -> Self {
if matches!(self, Self::Unspecified) {
other
} else {
self
}
}
fn try_build(self) -> Result<Self::Target, Error> {
match self {
Self::Unspecified => Err(Error::MissingValue(MissingValue::default())),
Self::Some(val) => val
.into_iter()
.map(ConfigurationBuilder::try_build)
.collect(),
Self::_PhantomData(_) => unreachable!("PhantomData is never instantiated"),
}
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
match self {
Self::Unspecified => Ok(false),
Self::Some(val) => val
.into_iter()
.map(ConfigurationBuilder::contains_non_secret_data)
.enumerate()
.find(|(_index, result)| result.is_err())
.map(|(index, result)| result.map_err(|err| err.prepend(index.to_string())))
.unwrap_or(Ok(true)),
Self::_PhantomData(_) => unreachable!("PhantomData is never instantiated"),
}
}
}
impl<T> Configuration for Vec<T>
where
T: Configuration,
BuilderOf<T>: 'static,
{
type Builder = UnkeyedContainerBuilder<Vec<BuilderOf<T>>, Self>;
}
impl<T> Configuration for BTreeSet<T>
where
T: Configuration + Ord,
BuilderOf<T>: Ord + 'static,
{
type Builder = UnkeyedContainerBuilder<BTreeSet<BuilderOf<T>>, Self>;
}
impl<T, S> Configuration for HashSet<T, S>
where
T: Configuration + Eq + Hash,
BuilderOf<T>: Hash + Eq + 'static,
S: BuildHasher + Default + 'static,
{
type Builder = UnkeyedContainerBuilder<HashSet<BuilderOf<T>, S>, Self>;
}
trait KeyedContainer {
type Key;
type Value;
fn insert(&mut self, k: Self::Key, v: Self::Value);
fn remove(&mut self, k: &Self::Key) -> Option<Self::Value>;
}
#[derive(Debug, Default, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
#[serde(from = "Container")]
pub enum KeyedContainerBuilder<Container, Target> {
#[default]
Unspecified,
Some(Container),
_PhantomData(PhantomData<fn() -> Target>),
}
impl<Container, Target> From<Container> for KeyedContainerBuilder<Container, Target> {
fn from(value: Container) -> Self {
Self::Some(value)
}
}
impl<Container, Target> ConfigurationBuilder for KeyedContainerBuilder<Container, Target>
where
Self: DeserializeOwned,
Container:
KeyedContainer + IntoIterator<Item = (KeyOf<Container>, ValueOf<Container>)> + 'static,
KeyOf<Container>: Display,
ValueOf<Container>: ConfigurationBuilder + 'static,
Target: Default + FromIterator<(KeyOf<Container>, TargetOf<ValueOf<Container>>)>,
for<'a> &'a Container: IntoIterator<Item = (&'a KeyOf<Container>, &'a ValueOf<Container>)>,
{
type Target = Target;
fn merge(self, other: Self) -> Self {
match (self, other) {
(Self::_PhantomData(_), _) | (_, Self::_PhantomData(_)) => {
unreachable!("PhantomData is never instantiated")
}
(Self::Unspecified, other) => other,
(us, Self::Unspecified) => us,
(Self::Some(mut us), Self::Some(other)) => {
for (key, their_val) in other {
let val = if let Some(our_val) = us.remove(&key) {
our_val.merge(their_val)
} else {
their_val
};
us.insert(key, val);
}
Self::Some(us)
}
}
}
fn try_build(self) -> Result<Self::Target, Error> {
match self {
Self::Unspecified => Err(Error::MissingValue(MissingValue::default())),
Self::Some(val) => val
.into_iter()
.map(|(key, value)| Ok((key, value.try_build()?)))
.collect(),
Self::_PhantomData(_) => unreachable!("PhantomData is never instantiated"),
}
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
match self {
Self::Unspecified => Ok(false),
Self::Some(val) => val
.into_iter()
.map(|(key, value)| (key, value.contains_non_secret_data()))
.find(|(_key, result)| result.is_err())
.map(|(key, result)| result.map_err(|err| err.prepend(key.to_string())))
.unwrap_or(Ok(true)),
Self::_PhantomData(_) => unreachable!("PhantomData is never instantiated"),
}
}
}
impl<K, V> KeyedContainer for BTreeMap<K, V>
where
K: Ord,
{
type Key = K;
type Value = V;
fn insert(&mut self, k: Self::Key, v: Self::Value) {
self.insert(k, v);
}
fn remove(&mut self, k: &Self::Key) -> Option<Self::Value> {
self.remove(k)
}
}
impl<K, V> Configuration for BTreeMap<K, V>
where
K: Ord + Display + DeserializeOwned + 'static,
V: Configuration,
BuilderOf<V>: 'static,
{
type Builder = KeyedContainerBuilder<BTreeMap<K, BuilderOf<V>>, Self>;
}
impl<K, V, S> KeyedContainer for HashMap<K, V, S>
where
K: Hash + Eq,
S: BuildHasher + Default,
{
type Key = K;
type Value = V;
fn insert(&mut self, k: Self::Key, v: Self::Value) {
self.insert(k, v);
}
fn remove(&mut self, k: &Self::Key) -> Option<Self::Value> {
self.remove(k)
}
}
impl<K, V, S> Configuration for HashMap<K, V, S>
where
K: Hash + Eq + Display + DeserializeOwned + 'static,
V: Configuration,
BuilderOf<V>: 'static,
S: Default + BuildHasher + 'static,
{
type Builder = KeyedContainerBuilder<HashMap<K, BuilderOf<V>, S>, Self>;
}
impl<T, const N: usize> Configuration for [T; N]
where
[BuilderOf<T>; N]: DeserializeOwned + Default,
T: Configuration,
{
type Builder = [BuilderOf<T>; N];
}
impl<T, const N: usize> ConfigurationBuilder for [T; N]
where
Self: DeserializeOwned + Default,
T: ConfigurationBuilder,
{
type Target = [TargetOf<T>; N];
fn merge(self, other: Self) -> Self {
let mut iter = other.into_iter();
self.map(|us| us.merge(iter.next().unwrap()))
}
fn try_build(self) -> Result<Self::Target, Error> {
self.into_iter()
.enumerate()
.map(|(index, val)| {
val.try_build().map_err(|err| match err {
Error::MissingValue(err) => Error::MissingValue(err.prepend(index.to_string())),
err => err,
})
})
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|vec: Vec<_>| {
Error::MissingValue(MissingValue::default().prepend(vec.len().to_string()))
})
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
self.iter()
.map(ConfigurationBuilder::contains_non_secret_data)
.enumerate()
.try_fold(false, |has_secret, (index, val)| {
Ok(val.map_err(|err| err.prepend(index.to_string()))? || has_secret)
})
}
}
impl<T> Configuration for PhantomData<T> {
type Builder = Self;
}
impl<T> ConfigurationBuilder for PhantomData<T> {
type Target = Self;
fn merge(self, _other: Self) -> Self {
self
}
fn try_build(self) -> Result<Self::Target, Error> {
Ok(self)
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
Ok(false)
}
}
impl<T: Configuration> Configuration for Option<T>
where
OptionBuilder<BuilderOf<T>>: DeserializeOwned,
{
type Builder = OptionBuilder<BuilderOf<T>>;
}
#[derive(Debug, Default, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
#[serde(from = "Option<T>")]
pub enum OptionBuilder<T> {
#[default]
Unspecified,
None,
Some(T),
}
impl<T> From<Option<T>> for OptionBuilder<T> {
fn from(opt: Option<T>) -> Self {
opt.map_or(Self::None, |val| Self::Some(val))
}
}
impl<T: ConfigurationBuilder> ConfigurationBuilder for OptionBuilder<T>
where
Self: DeserializeOwned,
{
type Target = Option<TargetOf<T>>;
fn merge(self, other: Self) -> Self {
match (self, other) {
(Self::Some(us), Self::Some(other)) => Self::Some(us.merge(other)),
(Self::Unspecified, other) => other,
(us, _) => us,
}
}
fn try_build(self) -> Result<Self::Target, Error> {
match self {
Self::Unspecified | Self::None => Ok(None),
Self::Some(val) => Ok(Some(val.try_build()?)),
}
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
match self {
Self::Some(data) => data.contains_non_secret_data(),
Self::None => Ok(true),
Self::Unspecified => Ok(false),
}
}
}