#![allow(private_bounds)]
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value as JsonValue};
use std::collections::HashMap;
use std::path::Path;
use crate::ConfigPropertyMut;
use crate::config_prefix_view::ConfigPrefixView;
use crate::config_reader::ConfigReader;
use crate::config_value_deserializer::ConfigValueDeserializer;
use crate::constants::DEFAULT_MAX_SUBSTITUTION_DEPTH;
use crate::field::ConfigField;
use crate::from::{FromConfig, IntoConfigDefault};
use crate::options::ConfigReadOptions;
use crate::source::{
ConfigSource, EnvConfigSource, EnvFileConfigSource, PropertiesConfigSource, TomlConfigSource,
YamlConfigSource,
};
use crate::utils;
use crate::{ConfigError, ConfigName, ConfigNames, ConfigResult, Property};
use qubit_datatype::{DataConvertTo, DataConverter, DataType};
use qubit_value::multi_values::{
MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
MultiValuesSingleSetter,
};
use qubit_value::{MultiValues, Value as QubitValue};
pub(crate) fn convert_deserialize_number<T>(
key: &str,
options: &ConfigReadOptions,
value: String,
) -> ConfigResult<T>
where
for<'a> DataConverter<'a>: DataConvertTo<T>,
{
match QubitValue::String(value).to_with::<T>(options.conversion_options()) {
Ok(value) => Ok(value),
Err(error) => Err(ConfigError::from((key, error))),
}
}
fn is_child_key(key: &str, prefix: &str) -> bool {
key.len() > prefix.len()
&& key.starts_with(prefix)
&& key.as_bytes().get(prefix.len()) == Some(&b'.')
}
fn scalar_string_is_missing_for_deserialize(
primary: &impl ConfigReader,
fallback: &impl ConfigReader,
key: &str,
property: &Property,
options: &ConfigReadOptions,
) -> ConfigResult<bool> {
let MultiValues::String(values) = property.value() else {
return Ok(false);
};
let [value] = values.as_slice() else {
return Ok(false);
};
let value = if primary.is_enable_variable_substitution() {
utils::substitute_variables_with_fallback(
value,
primary,
fallback,
primary.max_substitution_depth(),
)?
} else {
value.to_string()
};
match options.conversion_options().string.normalize(&value) {
Ok(_) => Ok(false),
Err(qubit_datatype::DataConversionError::NoValue) => Ok(true),
Err(error) => Err(ConfigError::from_data_conversion_error(key, error)),
}
}
#[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,
#[serde(default)]
read_options: ConfigReadOptions,
}
impl Config {
#[inline]
pub fn new() -> Self {
Self {
description: None,
properties: HashMap::new(),
enable_variable_substitution: true,
max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
read_options: ConfigReadOptions::default(),
}
}
#[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,
read_options: ConfigReadOptions::default(),
}
}
#[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 read_options(&self) -> &ConfigReadOptions {
&self.read_options
}
#[inline]
pub fn set_read_options(&mut self, read_options: ConfigReadOptions) -> &mut Self {
self.read_options = read_options;
self
}
#[must_use]
pub fn with_read_options(&self, read_options: ConfigReadOptions) -> Self {
let mut config = self.clone();
config.read_options = read_options;
config
}
#[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: impl ConfigName) -> bool {
name.with_config_name(|name| self.properties.contains_key(name))
}
#[inline]
pub fn get_property(&self, name: impl ConfigName) -> Option<&Property> {
name.with_config_name(|name| self.properties.get(name))
}
#[inline]
pub fn get_property_mut(
&mut self,
name: impl ConfigName,
) -> ConfigResult<Option<ConfigPropertyMut<'_>>> {
name.with_config_name(|name| {
self.ensure_property_not_final(name)?;
Ok(self.properties.get_mut(name).map(ConfigPropertyMut::new))
})
}
pub fn set_final(&mut self, name: impl ConfigName, is_final: bool) -> ConfigResult<()> {
name.with_config_name(|name| {
let property = self
.properties
.get_mut(name)
.ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
if property.is_final() && !is_final {
return Err(ConfigError::PropertyIsFinal(name.to_string()));
}
property.set_final(is_final);
Ok(())
})
}
#[inline]
pub fn remove(&mut self, name: impl ConfigName) -> ConfigResult<Option<Property>> {
name.with_config_name(|name| {
self.ensure_property_not_final(name)?;
Ok(self.properties.remove(name))
})
}
#[inline]
pub fn clear(&mut self) -> ConfigResult<()> {
self.ensure_no_final_properties()?;
self.properties.clear();
Ok(())
}
#[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(())
}
#[inline]
fn ensure_no_final_properties(&self) -> ConfigResult<()> {
if let Some((name, _)) = self.properties.iter().find(|(_, prop)| prop.is_final()) {
return Err(ConfigError::PropertyIsFinal(name.clone()));
}
Ok(())
}
pub fn get<T>(&self, name: impl ConfigName) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::get(self, name)
}
pub fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
where
MultiValues: MultiValuesFirstGetter<T>,
{
name.with_config_name(|name| {
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: impl ConfigName,
default: impl IntoConfigDefault<T>,
) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::get_or(self, name, default)
}
pub fn get_any<T>(&self, names: impl ConfigNames) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::get_any(self, names)
}
pub fn get_optional_any<T>(&self, names: impl ConfigNames) -> ConfigResult<Option<T>>
where
T: FromConfig,
{
<Self as ConfigReader>::get_optional_any(self, names)
}
pub fn get_any_or<T>(
&self,
names: impl ConfigNames,
default: impl IntoConfigDefault<T>,
) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::get_any_or(self, names, default)
}
pub fn get_any_or_with<T>(
&self,
names: impl ConfigNames,
default: impl IntoConfigDefault<T>,
read_options: &ConfigReadOptions,
) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::get_any_or_with(self, names, default, read_options)
}
pub fn read<T>(&self, field: ConfigField<T>) -> ConfigResult<T>
where
T: FromConfig,
{
<Self as ConfigReader>::read(self, field)
}
pub fn read_optional<T>(&self, field: ConfigField<T>) -> ConfigResult<Option<T>>
where
T: FromConfig,
{
<Self as ConfigReader>::read_optional(self, field)
}
pub fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
where
T: FromConfig,
{
<Self as ConfigReader>::get(self, name)
}
pub fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
where
MultiValues: MultiValuesGetter<T>,
{
name.with_config_name(|name| {
let property = self.get_property_by_name(name)?;
if property.is_empty() {
return Ok(Vec::new());
}
property
.get::<T>()
.map_err(|e| utils::map_value_error(name, e))
})
}
pub fn set<S>(&mut self, name: impl ConfigName, 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>,
{
name.with_config_name(|name| {
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: impl ConfigName, 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>,
{
name.with_config_name(|name| {
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);
property.set(values).map_err(ConfigError::from)?;
self.properties.insert(name.to_string(), property);
Ok(())
}
})
}
pub fn get_string(&self, name: impl ConfigName) -> ConfigResult<String> {
<Self as ConfigReader>::get_string(self, name)
}
pub fn get_string_any(&self, names: impl ConfigNames) -> ConfigResult<String> {
<Self as ConfigReader>::get_string_any(self, names)
}
pub fn get_optional_string_any(&self, names: impl ConfigNames) -> ConfigResult<Option<String>> {
<Self as ConfigReader>::get_optional_string_any(self, names)
}
pub fn get_string_any_or(
&self,
names: impl ConfigNames,
default: &str,
) -> ConfigResult<String> {
<Self as ConfigReader>::get_string_any_or(self, names, default)
}
pub fn get_string_or(&self, name: impl ConfigName, default: &str) -> ConfigResult<String> {
<Self as ConfigReader>::get_string_or(self, name, default)
}
pub fn get_string_list(&self, name: impl ConfigName) -> ConfigResult<Vec<String>> {
<Self as ConfigReader>::get_string_list(self, name)
}
pub fn get_string_list_or(
&self,
name: impl ConfigName,
default: &[&str],
) -> ConfigResult<Vec<String>> {
<Self as ConfigReader>::get_string_list_or(self, name, default)
}
#[inline]
pub fn from_source(source: &dyn ConfigSource) -> ConfigResult<Self> {
let mut config = Self::new();
source.load(&mut config)?;
Ok(config)
}
#[inline]
pub fn from_env() -> ConfigResult<Self> {
let source = EnvConfigSource::new();
Self::from_source(&source)
}
#[inline]
pub fn from_env_prefix(prefix: &str) -> ConfigResult<Self> {
let source = EnvConfigSource::with_prefix(prefix);
Self::from_source(&source)
}
#[inline]
pub fn from_env_options(
prefix: &str,
strip_prefix: bool,
convert_underscores: bool,
lowercase_keys: bool,
) -> ConfigResult<Self> {
let source = EnvConfigSource::with_options(
prefix,
strip_prefix,
convert_underscores,
lowercase_keys,
);
Self::from_source(&source)
}
#[inline]
pub fn from_toml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
let source = TomlConfigSource::from_file(path);
Self::from_source(&source)
}
#[inline]
pub fn from_yaml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
let source = YamlConfigSource::from_file(path);
Self::from_source(&source)
}
#[inline]
pub fn from_properties_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
let source = PropertiesConfigSource::from_file(path);
Self::from_source(&source)
}
#[inline]
pub fn from_env_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
let source = EnvFileConfigSource::from_file(path);
Self::from_source(&source)
}
#[inline]
pub fn merge_from_source(&mut self, source: &dyn ConfigSource) -> ConfigResult<()> {
let mut staged = self.clone();
source.load(&mut staged)?;
*self = staged;
Ok(())
}
#[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.description = self.description.clone();
sub.enable_variable_substitution = self.enable_variable_substitution;
sub.max_substitution_depth = self.max_substitution_depth;
sub.read_options = self.read_options.clone();
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.starts_with(&full_prefix) {
let new_key = if strip_prefix {
k[full_prefix.len()..].to_string()
} else {
k.clone()
};
sub.properties.insert(new_key, v.clone());
}
}
Ok(sub)
}
pub fn is_null(&self, name: impl ConfigName) -> bool {
name.with_config_name(|name| {
self.properties
.get(name)
.map(|p| p.is_empty())
.unwrap_or(false)
})
}
pub fn get_optional<T>(&self, name: impl ConfigName) -> ConfigResult<Option<T>>
where
T: FromConfig,
{
<Self as ConfigReader>::get_optional(self, name)
}
pub fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
where
T: FromConfig,
{
<Self as ConfigReader>::get_optional(self, name)
}
pub fn get_optional_string(&self, name: impl ConfigName) -> ConfigResult<Option<String>> {
<Self as ConfigReader>::get_optional_string(self, name)
}
pub fn get_optional_string_list(
&self,
name: impl ConfigName,
) -> ConfigResult<Option<Vec<String>>> {
<Self as ConfigReader>::get_optional_string_list(self, name)
}
pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
where
T: DeserializeOwned,
{
let value = self.deserialize_root_value(prefix)?;
match T::deserialize(ConfigValueDeserializer::new(
value,
prefix.to_string(),
self.read_options(),
)) {
Ok(value) => Ok(value),
Err(error) => Err(error.into_config_error(prefix)),
}
}
fn deserialize_root_value(&self, prefix: &str) -> ConfigResult<JsonValue> {
if prefix.is_empty() {
return self.deserialize_subtree_value(prefix);
}
let exact = self.properties.get(prefix);
let has_children = self.properties.keys().any(|key| is_child_key(key, prefix));
match (exact, has_children) {
(Some(_), true) => Err(ConfigError::KeyConflict {
path: prefix.to_string(),
existing: "exact value".to_string(),
incoming: "nested child keys".to_string(),
}),
(Some(property), false) => self.deserialize_exact_value(prefix, property),
(None, _) => self.deserialize_subtree_value(prefix),
}
}
fn deserialize_exact_value(&self, key: &str, property: &Property) -> ConfigResult<JsonValue> {
if scalar_string_is_missing_for_deserialize(self, self, key, property, self.read_options())?
{
return Ok(JsonValue::Null);
}
let mut value = utils::property_to_json_value(property);
utils::substitute_json_strings_with_fallback(&mut value, self, self)?;
Ok(value)
}
fn deserialize_subtree_value(&self, prefix: &str) -> ConfigResult<JsonValue> {
let sub = self.subconfig(prefix, true)?;
let mut properties = sub.properties.iter().collect::<Vec<_>>();
properties.sort_by_key(|(left_key, _)| *left_key);
let mut map = Map::new();
for (key, prop) in properties {
if scalar_string_is_missing_for_deserialize(&sub, self, key, prop, self.read_options())?
{
continue;
}
let mut json_val = utils::property_to_json_value(prop);
utils::substitute_json_strings_with_fallback(&mut json_val, &sub, self)?;
utils::insert_deserialize_value(&mut map, key, json_val)?;
}
Ok(JsonValue::Object(map))
}
pub fn insert_property(
&mut self,
name: impl ConfigName,
property: Property,
) -> ConfigResult<()> {
name.with_config_name(|name| {
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: impl ConfigName, data_type: DataType) -> ConfigResult<()> {
name.with_config_name(|name| {
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 read_options(&self) -> &ConfigReadOptions {
Config::read_options(self)
}
#[inline]
fn description(&self) -> Option<&str> {
Config::description(self)
}
#[inline]
fn get_property(&self, name: impl ConfigName) -> 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: impl ConfigName) -> bool {
Config::contains(self, name)
}
#[inline]
fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
where
MultiValues: MultiValuesFirstGetter<T>,
{
Config::get_strict(self, name)
}
#[inline]
fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
where
T: FromConfig,
{
Config::get_list(self, name)
}
#[inline]
fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
where
MultiValues: MultiValuesGetter<T>,
{
Config::get_list_strict(self, name)
}
#[inline]
fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
where
T: FromConfig,
{
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: impl ConfigName) -> 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()
}
}