use crate::{AnyParser, Error, MergeCase, Parse, Result, Value, DEFAULT_KEYS_SEPARATOR};
use blake2b_simd::Hash;
use serde::de::DeserializeOwned;
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
pub struct Config {
parsers: Vec<AnyParser>,
value: Value,
case_on: bool,
hash: Hash,
sealed_suffix: String,
keys_delimiter: String,
}
impl Config {
pub fn reload(&mut self) -> Result<&mut Self> {
let mut value = Value::default();
for (idx, parser) in self.parsers.iter_mut().enumerate() {
value = parser
.parse(&value)
.map_err(|e| Error::ParseValue(e, idx + 1))?
.merge_with_case(&value, self.case_on);
}
value.seal(&self.sealed_suffix);
self.hash = blake2b_simd::blake2b(&value.as_bytes());
self.value = value;
Ok(self)
}
#[inline]
pub fn hash(&self) -> String {
format!("BLAKE2b: {}", self.hash.to_hex())
}
#[inline]
pub fn get_by_keys<I, K, T>(&self, keys: I) -> Result<Option<T>>
where
I: IntoIterator<Item = K>,
K: AsRef<str>,
T: DeserializeOwned,
{
self.value.get_by_keys(keys)
}
#[inline]
pub fn get_by_key_path<T, P>(&self, path: P) -> Result<Option<T>>
where
T: DeserializeOwned,
P: AsRef<str>,
{
self.value
.get_by_key_path_with_delim(path, &self.keys_delimiter)
}
#[inline]
pub fn get_by_key_path_with_delim<T, P, D>(&self, path: P, delim: D) -> Result<Option<T>>
where
T: DeserializeOwned,
P: AsRef<str>,
D: AsRef<str>,
{
self.value.get_by_key_path_with_delim(path, delim)
}
#[inline]
pub fn get<T: DeserializeOwned>(&self) -> Result<T> {
self.value.get()
}
#[inline]
pub fn get_value(&self) -> &Value {
&self.value
}
}
impl Debug for Config {
#[inline]
fn fmt(&self, f: &mut Formatter) -> FmtResult {
f.write_fmt(format_args!(
"Config {{ parsers: size({}), value: {:?}, case_on: {:?}, hash: {:?}, sealed_suffix: {:?}, keys_delimiter: {:?} }}",
self.parsers.len(),
self.value,
self.case_on,
self.hash,
self.sealed_suffix,
self.keys_delimiter,
))
}
}
impl Display for Config {
#[inline]
fn fmt(&self, f: &mut Formatter) -> FmtResult {
f.write_fmt(format_args!("Config: {}\n{}", self.hash(), self.value))
}
}
impl PartialEq for Config {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}
impl Eq for Config {}
impl PartialOrd for Config {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Config {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.hash.as_bytes().cmp(other.hash.as_bytes())
}
}
pub struct ConfigBuilder {
parsers: Vec<AnyParser>,
sealed_suffix: String,
keys_delimiter: String,
auto_case_on: bool,
merge_case: MergeCase,
}
impl ConfigBuilder {
#[inline]
pub fn append_parser<P>(mut self, parser: P) -> Self
where
P: Parse + 'static,
{
self.auto_case_on = self.auto_case_on && parser.is_case_sensitive();
self.parsers.push(Box::new(parser));
self
}
#[inline]
pub fn sealed_suffix<S>(mut self, suffix: S) -> Self
where
S: Into<String>,
{
self.sealed_suffix = suffix.into();
self
}
#[inline]
pub fn keys_delimiter<D>(mut self, delim: D) -> Self
where
D: Into<String>,
{
self.keys_delimiter = delim.into();
self
}
#[inline]
pub fn merge_case(mut self, case: MergeCase) -> Self {
self.merge_case = case;
self
}
pub fn load(self) -> Result<Config> {
let value = Value::default();
let hash = blake2b_simd::blake2b(&value.as_bytes());
let case_on = if MergeCase::Auto == self.merge_case {
self.auto_case_on
} else {
MergeCase::Sensitive == self.merge_case
};
let mut config = Config {
parsers: self.parsers,
value,
case_on,
hash,
sealed_suffix: self.sealed_suffix,
keys_delimiter: self.keys_delimiter,
};
config.reload()?;
Ok(config)
}
#[inline]
pub fn load_one<P>(parser: P) -> Result<Config>
where
P: Parse + 'static,
{
ConfigBuilder::default().append_parser(parser).load()
}
#[inline]
pub fn load_from<I, P>(parsers: I) -> Result<Config>
where
I: IntoIterator<Item = P>,
P: Parse + 'static,
{
parsers
.into_iter()
.fold(ConfigBuilder::default(), |s, p| s.append_parser(p))
.load()
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self {
parsers: Default::default(),
sealed_suffix: Default::default(),
keys_delimiter: DEFAULT_KEYS_SEPARATOR.to_string(),
auto_case_on: true,
merge_case: Default::default(),
}
}
}