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 adjoin<T: Provider>(self, provider: T) -> Self {
self.provide(provider, Order::Adjoin)
}
#[track_caller]
pub fn merge<T: Provider>(self, provider: T) -> Self {
self.provide(provider, Order::Merge)
}
#[track_caller]
pub fn admerge<T: Provider>(self, provider: T) -> Self {
self.provide(provider, Order::Admerge)
}
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().map_err(|e| e.resolved(self))?;
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 focus(&self, key: &str) -> Self {
fn try_focus(figment: &Figment, key: &str) -> Result<Map<Profile, Dict>> {
let map = figment.value.clone().map_err(|e| e.resolved(figment))?;
let new_map = map.into_iter()
.filter_map(|(k, v)| {
let focused = Value::Dict(Tag::Default, v).find(key)?;
let dict = focused.into_dict()?;
Some((k, dict))
})
.collect();
Ok(new_map)
}
Figment {
profile: self.profile.clone(),
metadata: self.metadata.clone(),
value: try_focus(self, key)
}
}
pub fn extract<'a, T: Deserialize<'a>>(&self) -> Result<T> {
T::deserialize(ConfiguredValueDe::from(self, &self.merged()?))
}
pub fn extract_inner<'a, T: Deserialize<'a>>(&self, key: &str) -> Result<T> {
T::deserialize(ConfiguredValueDe::from(self, &self.find_value(key)?))
}
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>();
}