use std::panic::Location;
use serde::de::Deserialize;
use crate::{Profile, Provider, Metadata};
use crate::error::{Kind, Result};
use crate::value::{Value, Map, Dict, Tag, ConfiguredValueDe};
use crate::coalesce::{Coalescible, Order};
#[derive(Clone, Debug)]
pub struct Figment {
pub(crate) profile: Profile,
pub(crate) metadata: Map<Tag, Metadata>,
pub(crate) value: Result<Map<Profile, Dict>>,
}
impl Figment {
pub fn new() -> Self {
Figment {
metadata: Map::new(),
profile: Profile::Default,
value: Ok(Map::new()),
}
}
#[track_caller]
pub fn from<T: Provider>(provider: T) -> Self {
Figment::new().merge(provider)
}
#[track_caller]
fn provide<T: Provider>(mut self, provider: T, order: Order) -> Self {
if let Some(map) = provider.__metadata_map() {
self.metadata.extend(map);
}
if let Some(profile) = provider.profile() {
self.profile = self.profile.coalesce(profile, order);
}
let mut metadata = provider.metadata();
metadata.provide_location = Some(Location::caller());
let tag = Tag::next();
self.metadata.insert(tag, metadata);
self.value = match (provider.data(), self.value) {
(Ok(_), e@Err(_)) => e,
(Err(e), Ok(_)) => Err(e.retagged(tag)),
(Err(e), Err(prev)) => Err(e.retagged(tag).chain(prev)),
(Ok(mut new), Ok(old)) => {
new.iter_mut()
.map(|(p, map)| std::iter::repeat(p).zip(map.values_mut()))
.flatten()
.for_each(|(p, v)| v.map_tag(|t| *t = tag.for_profile(p)));
Ok(old.coalesce(new, order))
}
};
self
}
#[track_caller]
pub fn join<T: Provider>(self, provider: T) -> Self {
self.provide(provider, Order::Join)
}
#[track_caller]
pub fn merge<T: Provider>(self, provider: T) -> Self {
self.provide(provider, Order::Merge)
}
pub fn select<P: Into<Profile>>(mut self, profile: P) -> Self {
self.profile = profile.into();
self
}
fn merged(&self) -> Result<Value> {
let mut map = self.value.clone()?;
let def = map.remove(&Profile::Default).unwrap_or_default();
let global = map.remove(&Profile::Global).unwrap_or_default();
let map = match map.remove(&self.profile) {
Some(v) if self.profile.is_custom() => def.merge(v).merge(global),
_ => def.merge(global)
};
Ok(Value::Dict(Tag::Default, map))
}
pub fn extract<'a, T: Deserialize<'a>>(&self) -> Result<T> {
let merged = self.merged().map_err(|e| e.resolved(self))?;
T::deserialize(ConfiguredValueDe::from(self, &merged))
}
pub fn extract_inner<'a, T: Deserialize<'a>>(&self, key: &str) -> Result<T> {
let merged = self.merged().map_err(|e| e.resolved(self))?;
let inner = merged.find(key).ok_or_else(|| Kind::MissingField(key.to_string().into()))?;
T::deserialize(ConfiguredValueDe::from(self, &inner))
}
pub fn metadata(&self) -> impl Iterator<Item = &Metadata> {
self.metadata.values()
}
pub fn profile(&self) -> &Profile {
&self.profile
}
pub fn profiles(&self) -> impl Iterator<Item = &Profile> {
self.value.as_ref()
.ok()
.map(|v| v.keys())
.into_iter()
.flatten()
}
pub fn find_value(&self, key: &str) -> Result<Value> {
self.merged()?
.find(key)
.ok_or_else(|| Kind::MissingField(key.to_string().into()).into())
}
pub fn find_metadata(&self, key: &str) -> Option<&Metadata> {
self.metadata.get(&self.find_value(key).ok()?.tag())
}
pub fn get_metadata(&self, tag: Tag) -> Option<&Metadata> {
self.metadata.get(&tag)
}
}
impl Provider for Figment {
fn metadata(&self) -> Metadata { Metadata::default() }
fn data(&self) -> Result<Map<Profile, Dict>> { self.value.clone() }
fn profile(&self) -> Option<Profile> {
Some(self.profile.clone())
}
fn __metadata_map(&self) -> Option<Map<Tag, Metadata>> {
Some(self.metadata.clone())
}
}
impl Default for Figment {
fn default() -> Self {
Figment::new()
}
}
#[test]
#[cfg(test)]
fn is_send_sync() {
fn check_for_send_sync<T: Send + Sync>() {}
check_for_send_sync::<Figment>();
}