#![allow(private_bounds)]
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::config_prefix_view::ConfigPrefixView;
use crate::config_reader::ConfigReader;
use crate::source::ConfigSource;
use crate::utils;
use crate::{ConfigError, ConfigResult, Property};
use qubit_value::multi_values::{
MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
MultiValuesSingleSetter,
};
use qubit_value::MultiValues;
use qubit_value::ValueError;
#[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: crate::constants::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: crate::constants::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()
}
pub fn get<T>(&self, name: &str) -> ConfigResult<T>
where
MultiValues: MultiValuesFirstGetter<T>,
{
let property = self
.properties
.get(name)
.ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
property.get_first::<T>().map_err(|e| match e {
ValueError::NoValue => ConfigError::PropertyHasNoValue(name.to_string()),
ValueError::TypeMismatch { expected, actual } => {
ConfigError::type_mismatch_at(name, expected, actual)
}
ValueError::ConversionFailed { from, to } => {
ConfigError::conversion_error_at(name, format!("From {from} to {to}"))
}
ValueError::ConversionError(msg) => ConfigError::conversion_error_at(name, msg),
ValueError::IndexOutOfBounds { index, len } => {
ConfigError::IndexOutOfBounds { index, len }
}
ValueError::JsonSerializationError(msg) => {
ConfigError::conversion_error_at(name, format!("JSON serialization error: {msg}"))
}
ValueError::JsonDeserializationError(msg) => {
ConfigError::conversion_error_at(name, format!("JSON deserialization error: {msg}"))
}
})
}
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
.properties
.get(name)
.ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
property.get::<T>().map_err(|e| match e {
ValueError::NoValue => ConfigError::PropertyHasNoValue(name.to_string()),
ValueError::TypeMismatch { expected, actual } => {
ConfigError::type_mismatch_at(name, expected, actual)
}
ValueError::ConversionFailed { from, to } => {
ConfigError::conversion_error_at(name, format!("From {from} to {to}"))
}
ValueError::ConversionError(msg) => ConfigError::conversion_error_at(name, msg),
ValueError::IndexOutOfBounds { index, len } => {
ConfigError::IndexOutOfBounds { index, len }
}
ValueError::JsonSerializationError(msg) => {
ConfigError::conversion_error_at(name, format!("JSON serialization error: {msg}"))
}
ValueError::JsonDeserializationError(msg) => {
ConfigError::conversion_error_at(name, format!("JSON deserialization error: {msg}"))
}
})
}
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>,
{
if let Some(prop) = self.properties.get(name) {
if prop.is_final() {
return Err(ConfigError::PropertyIsFinal(name.to_string()));
}
}
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>,
{
if let Some(prop) = self.properties.get(name) {
if prop.is_final() {
return Err(ConfigError::PropertyIsFinal(name.to_string()));
}
}
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>,
{
match self.properties.get(name) {
None => Ok(None),
Some(prop) if prop.is_empty() => Ok(None),
Some(_) => self.get::<T>(name).map(Some),
}
}
pub fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
where
MultiValues: MultiValuesGetter<T>,
{
match self.properties.get(name) {
None => Ok(None),
Some(prop) if prop.is_empty() => Ok(None),
Some(_) => self.get_list::<T>(name).map(Some),
}
}
pub fn get_optional_string(&self, name: &str) -> ConfigResult<Option<String>> {
match self.properties.get(name) {
None => Ok(None),
Some(prop) if prop.is_empty() => Ok(None),
Some(_) => self.get_string(name).map(Some),
}
}
pub fn get_optional_string_list(&self, name: &str) -> ConfigResult<Option<Vec<String>>> {
match self.properties.get(name) {
None => Ok(None),
Some(prop) if prop.is_empty() => Ok(None),
Some(_) => self.get_string_list(name).map(Some),
}
}
pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
where
T: serde::de::DeserializeOwned,
{
use serde_json::{Map, Value as JsonValue};
let sub = self.subconfig(prefix, true)?;
let mut map = Map::new();
for (key, prop) in &sub.properties {
let json_val = property_to_json_value(prop);
map.insert(key.clone(), json_val);
}
let json_obj = JsonValue::Object(map);
serde_json::from_value(json_obj).map_err(|e| ConfigError::DeserializeError {
path: prefix.to_string(),
message: e.to_string(),
})
}
#[inline]
pub fn properties_mut(&mut self) -> &mut HashMap<String, Property> {
&mut self.properties
}
}
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)
}
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: serde::de::DeserializeOwned,
{
Config::deserialize(self, prefix)
}
#[inline]
fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
Config::prefix_view(self, prefix)
}
}
fn property_to_json_value(prop: &Property) -> serde_json::Value {
use qubit_value::MultiValues;
use serde_json::Value as JsonValue;
let mv = prop.value();
match mv {
MultiValues::Empty(_) => JsonValue::Null,
MultiValues::Bool(v) => {
if v.len() == 1 {
JsonValue::Bool(v[0])
} else {
JsonValue::Array(v.iter().map(|b| JsonValue::Bool(*b)).collect())
}
}
MultiValues::Int8(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::Int16(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::Int32(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::Int64(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::IntSize(v) => scalar_or_array(v, |x| {
JsonValue::Number(serde_json::Number::from(*x as i64))
}),
MultiValues::UInt8(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::UInt16(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::UInt32(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::UInt64(v) => scalar_or_array(v, |x| JsonValue::Number((*x).into())),
MultiValues::UIntSize(v) => scalar_or_array(v, |x| {
JsonValue::Number(serde_json::Number::from(*x as u64))
}),
MultiValues::Float32(v) => scalar_or_array(v, |x| {
serde_json::Number::from_f64(*x as f64)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null)
}),
MultiValues::Float64(v) => scalar_or_array(v, |x| {
serde_json::Number::from_f64(*x)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null)
}),
MultiValues::String(v) => scalar_or_array(v, |x| JsonValue::String(x.clone())),
MultiValues::Duration(v) => {
scalar_or_array(v, |x| JsonValue::String(format!("{}ms", x.as_millis())))
}
MultiValues::Url(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::StringMap(v) => {
if v.len() == 1 {
let obj: serde_json::Map<String, JsonValue> = v[0]
.iter()
.map(|(k, val)| (k.clone(), JsonValue::String(val.clone())))
.collect();
JsonValue::Object(obj)
} else {
JsonValue::Array(
v.iter()
.map(|m| {
let obj: serde_json::Map<String, JsonValue> = m
.iter()
.map(|(k, val)| (k.clone(), JsonValue::String(val.clone())))
.collect();
JsonValue::Object(obj)
})
.collect(),
)
}
}
MultiValues::Json(v) => {
if v.len() == 1 {
v[0].clone()
} else {
JsonValue::Array(v.clone())
}
}
MultiValues::Char(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::BigInteger(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::BigDecimal(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::DateTime(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::Date(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::Time(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::Instant(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::Int128(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
MultiValues::UInt128(v) => scalar_or_array(v, |x| JsonValue::String(x.to_string())),
}
}
fn scalar_or_array<T, F>(v: &[T], f: F) -> serde_json::Value
where
F: Fn(&T) -> serde_json::Value,
{
if v.len() == 1 {
f(&v[0])
} else {
serde_json::Value::Array(v.iter().map(f).collect())
}
}
impl Default for Config {
#[inline]
fn default() -> Self {
Self::new()
}
}