#![allow(private_bounds)]
use std::collections::HashMap;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value, from_value};
use crate::config_prefix_view::ConfigPrefixView;
use crate::config_reader::ConfigReader;
use crate::constants::DEFAULT_MAX_SUBSTITUTION_DEPTH;
use crate::source::ConfigSource;
use crate::utils;
use crate::{ConfigError, ConfigResult, Property};
use qubit_common::DataType;
use qubit_value::MultiValues;
use qubit_value::multi_values::{
MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
MultiValuesSingleSetter,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Config {
description: Option<String>,
pub(crate) properties: HashMap<String, Property>,
enable_variable_substitution: bool,
max_substitution_depth: usize,
}
impl Config {
#[inline]
pub fn new() -> Self {
Self {
description: None,
properties: HashMap::new(),
enable_variable_substitution: true,
max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
}
}
#[inline]
pub fn with_description(description: &str) -> Self {
Self {
description: Some(description.to_string()),
properties: HashMap::new(),
enable_variable_substitution: true,
max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
}
}
#[inline]
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
#[inline]
pub fn set_description(&mut self, description: Option<String>) {
self.description = description;
}
#[inline]
pub fn is_enable_variable_substitution(&self) -> bool {
self.enable_variable_substitution
}
#[inline]
pub fn set_enable_variable_substitution(&mut self, enable: bool) {
self.enable_variable_substitution = enable;
}
#[inline]
pub fn max_substitution_depth(&self) -> usize {
self.max_substitution_depth
}
#[inline]
pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
ConfigPrefixView::new(self, prefix)
}
#[inline]
pub fn set_max_substitution_depth(&mut self, depth: usize) {
self.max_substitution_depth = depth;
}
#[inline]
pub fn contains(&self, name: &str) -> bool {
self.properties.contains_key(name)
}
#[inline]
pub fn get_property(&self, name: &str) -> Option<&Property> {
self.properties.get(name)
}
#[inline]
pub fn get_property_mut(&mut self, name: &str) -> Option<&mut Property> {
self.properties.get_mut(name)
}
#[inline]
pub fn remove(&mut self, name: &str) -> Option<Property> {
self.properties.remove(name)
}
#[inline]
pub fn clear(&mut self) {
self.properties.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.properties.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.properties.is_empty()
}
pub fn keys(&self) -> Vec<String> {
self.properties.keys().cloned().collect()
}
#[inline]
fn get_property_by_name(&self, name: &str) -> ConfigResult<&Property> {
self.properties
.get(name)
.ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))
}
#[inline]
fn ensure_property_not_final(&self, name: &str) -> ConfigResult<()> {
if let Some(prop) = self.properties.get(name)
&& prop.is_final()
{
return Err(ConfigError::PropertyIsFinal(name.to_string()));
}
Ok(())
}
fn get_optional_when_present<T>(
&self,
name: &str,
read: impl FnOnce(&Self) -> ConfigResult<T>,
) -> ConfigResult<Option<T>> {
match self.properties.get(name) {
None => Ok(None),
Some(prop) if prop.is_empty() => Ok(None),
Some(_) => read(self).map(Some),
}
}
pub fn get<T>(&self, name: &str) -> ConfigResult<T>
where
MultiValues: MultiValuesFirstGetter<T>,
{
let property = self.get_property_by_name(name)?;
property
.get_first::<T>()
.map_err(|e| utils::map_value_error(name, e))
}
pub fn get_or<T>(&self, name: &str, default: T) -> T
where
MultiValues: MultiValuesFirstGetter<T>,
{
self.get(name).unwrap_or(default)
}
pub fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
where
MultiValues: MultiValuesGetter<T>,
{
let property = self.get_property_by_name(name)?;
property
.get::<T>()
.map_err(|e| utils::map_value_error(name, e))
}
pub fn set<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
where
S: for<'a> MultiValuesSetArg<'a>,
<S as MultiValuesSetArg<'static>>::Item: Clone,
MultiValues: MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
{
self.ensure_property_not_final(name)?;
let property = self
.properties
.entry(name.to_string())
.or_insert_with(|| Property::new(name));
property.set(values).map_err(ConfigError::from)
}
pub fn add<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
where
S: for<'a> MultiValuesAddArg<'a, Item = <S as MultiValuesSetArg<'static>>::Item>
+ for<'a> MultiValuesSetArg<'a>,
<S as MultiValuesSetArg<'static>>::Item: Clone,
MultiValues: MultiValuesAdder<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesMultiAdder<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
+ MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
{
self.ensure_property_not_final(name)?;
if let Some(property) = self.properties.get_mut(name) {
property.add(values).map_err(ConfigError::from)
} else {
let mut property = Property::new(name);
let _ = property.set(values);
self.properties.insert(name.to_string(), property);
Ok(())
}
}
pub fn get_string(&self, name: &str) -> ConfigResult<String> {
let value: String = self.get(name)?;
if self.enable_variable_substitution {
utils::substitute_variables(&value, self, self.max_substitution_depth)
} else {
Ok(value)
}
}
pub fn get_string_or(&self, name: &str, default: &str) -> String {
self.get_string(name)
.unwrap_or_else(|_| default.to_string())
}
pub fn get_string_list(&self, name: &str) -> ConfigResult<Vec<String>> {
let values: Vec<String> = self.get_list(name)?;
if self.enable_variable_substitution {
values
.into_iter()
.map(|v| utils::substitute_variables(&v, self, self.max_substitution_depth))
.collect()
} else {
Ok(values)
}
}
pub fn get_string_list_or(&self, name: &str, default: &[&str]) -> Vec<String> {
self.get_string_list(name)
.unwrap_or_else(|_| default.iter().map(|s| s.to_string()).collect())
}
#[inline]
pub fn merge_from_source(&mut self, source: &dyn ConfigSource) -> ConfigResult<()> {
source.load(self)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &Property)> {
self.properties.iter().map(|(k, v)| (k.as_str(), v))
}
#[inline]
pub fn iter_prefix<'a>(
&'a self,
prefix: &'a str,
) -> impl Iterator<Item = (&'a str, &'a Property)> {
self.properties
.iter()
.filter(move |(k, _)| k.starts_with(prefix))
.map(|(k, v)| (k.as_str(), v))
}
#[inline]
pub fn contains_prefix(&self, prefix: &str) -> bool {
self.properties.keys().any(|k| k.starts_with(prefix))
}
pub fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
let mut sub = Config::new();
sub.enable_variable_substitution = self.enable_variable_substitution;
sub.max_substitution_depth = self.max_substitution_depth;
if prefix.is_empty() {
for (k, v) in &self.properties {
sub.properties.insert(k.clone(), v.clone());
}
return Ok(sub);
}
let full_prefix = format!("{prefix}.");
for (k, v) in &self.properties {
if k == prefix || k.starts_with(&full_prefix) {
let new_key = if strip_prefix {
if k == prefix {
prefix.to_string()
} else {
k[full_prefix.len()..].to_string()
}
} else {
k.clone()
};
sub.properties.insert(new_key, v.clone());
}
}
Ok(sub)
}
pub fn is_null(&self, name: &str) -> bool {
self.properties
.get(name)
.map(|p| p.is_empty())
.unwrap_or(false)
}
pub fn get_optional<T>(&self, name: &str) -> ConfigResult<Option<T>>
where
MultiValues: MultiValuesFirstGetter<T>,
{
self.get_optional_when_present(name, |c| c.get(name))
}
pub fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
where
MultiValues: MultiValuesGetter<T>,
{
self.get_optional_when_present(name, |c| c.get_list(name))
}
pub fn get_optional_string(&self, name: &str) -> ConfigResult<Option<String>> {
self.get_optional_when_present(name, |c| c.get_string(name))
}
pub fn get_optional_string_list(&self, name: &str) -> ConfigResult<Option<Vec<String>>> {
self.get_optional_when_present(name, |c| c.get_string_list(name))
}
pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
where
T: DeserializeOwned,
{
let sub = self.subconfig(prefix, true)?;
let mut map = Map::new();
for (key, prop) in &sub.properties {
let json_val = utils::property_to_json_value(prop);
utils::insert_deserialize_value(&mut map, key, json_val);
}
let json_obj = Value::Object(map);
from_value(json_obj).map_err(|e| ConfigError::DeserializeError {
path: prefix.to_string(),
message: e.to_string(),
})
}
pub fn insert_property(&mut self, name: &str, property: Property) -> ConfigResult<()> {
if property.name() != name {
return Err(ConfigError::MergeError(format!(
"Property name mismatch: key '{name}' != property '{}'",
property.name()
)));
}
self.ensure_property_not_final(name)?;
self.properties.insert(name.to_string(), property);
Ok(())
}
#[inline]
pub fn set_null(&mut self, name: &str, data_type: DataType) -> ConfigResult<()> {
self.insert_property(
name,
Property::with_value(name, MultiValues::Empty(data_type)),
)
}
}
impl ConfigReader for Config {
#[inline]
fn is_enable_variable_substitution(&self) -> bool {
Config::is_enable_variable_substitution(self)
}
#[inline]
fn max_substitution_depth(&self) -> usize {
Config::max_substitution_depth(self)
}
#[inline]
fn description(&self) -> Option<&str> {
Config::description(self)
}
#[inline]
fn get_property(&self, name: &str) -> Option<&Property> {
Config::get_property(self, name)
}
#[inline]
fn len(&self) -> usize {
Config::len(self)
}
#[inline]
fn is_empty(&self) -> bool {
Config::is_empty(self)
}
#[inline]
fn keys(&self) -> Vec<String> {
Config::keys(self)
}
#[inline]
fn contains(&self, name: &str) -> bool {
Config::contains(self, name)
}
#[inline]
fn get<T>(&self, name: &str) -> ConfigResult<T>
where
MultiValues: MultiValuesFirstGetter<T>,
{
Config::get(self, name)
}
#[inline]
fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
where
MultiValues: MultiValuesGetter<T>,
{
Config::get_list(self, name)
}
#[inline]
fn get_optional<T>(&self, name: &str) -> ConfigResult<Option<T>>
where
MultiValues: MultiValuesFirstGetter<T>,
{
Config::get_optional(self, name)
}
#[inline]
fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
where
MultiValues: MultiValuesGetter<T>,
{
Config::get_optional_list(self, name)
}
#[inline]
fn contains_prefix(&self, prefix: &str) -> bool {
Config::contains_prefix(self, prefix)
}
#[inline]
fn iter_prefix<'a>(
&'a self,
prefix: &'a str,
) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
Box::new(Config::iter_prefix(self, prefix))
}
#[inline]
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
Box::new(Config::iter(self))
}
#[inline]
fn is_null(&self, name: &str) -> bool {
Config::is_null(self, name)
}
#[inline]
fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
Config::subconfig(self, prefix, strip_prefix)
}
#[inline]
fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
where
T: DeserializeOwned,
{
Config::deserialize(self, prefix)
}
#[inline]
fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
Config::prefix_view(self, prefix)
}
}
impl Default for Config {
#[inline]
fn default() -> Self {
Self::new()
}
}