use std::{
error::Error,
fmt::{Display, Formatter},
};
use config::{Config, ValueKind};
pub trait ConfigPath {
fn main_key_prefix() -> &'static str;
fn overload_key_prefix(config: &Config) -> Result<Option<String>, ConfigurationError>;
fn merge_subconfig(config: &Config, defaults: config::Value) -> Result<Config, ConfigurationError> {
match Self::overload_key_prefix(config)? {
Some(key) => {
let overload: config::Value = config.get(key.as_str()).unwrap_or_default();
let mut config = Config::builder()
.set_default(Self::main_key_prefix(), defaults)?
.add_source(config.clone());
if !matches!(overload.kind, ValueKind::Nil) {
config = config.set_override(Self::main_key_prefix(), overload)?;
}
let config = config.build()?;
Ok(config)
},
None => {
let config = Config::builder()
.set_default(Self::main_key_prefix(), defaults)?
.add_source(config.clone())
.build()?;
Ok(config)
},
}
}
}
pub trait SubConfigPath {
fn main_key_prefix() -> &'static str;
fn subconfig_key() -> String {
let main = <Self as SubConfigPath>::main_key_prefix();
format!("{main}.override_from")
}
}
impl<C: SubConfigPath> ConfigPath for C {
fn main_key_prefix() -> &'static str {
<Self as SubConfigPath>::main_key_prefix()
}
fn overload_key_prefix(config: &Config) -> Result<Option<String>, ConfigurationError> {
let subconfig_key = Self::subconfig_key();
let network_val: Option<String> = config
.get_string(subconfig_key.as_str())
.ok()
.map(|network| format!("{}.{}", network, Self::main_key_prefix()));
Ok(network_val)
}
}
pub trait ConfigLoader: ConfigPath + Sized {
fn load_from(config: &Config) -> Result<Self, ConfigurationError>;
}
pub trait DefaultConfigLoader: ConfigPath + Sized {
fn load_from(config: &Config) -> Result<Self, ConfigurationError>;
}
impl<C> DefaultConfigLoader for C
where C: ConfigPath + Default + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>
{
fn load_from(config: &Config) -> Result<Self, ConfigurationError> {
let default = <Self as Default>::default();
let buf = serde_json::to_value(&default)?;
let value: config::Value = serde_json::from_value(buf)?;
let merger = Self::merge_subconfig(config, value)?;
let final_value: config::Value = merger.get(Self::main_key_prefix())?;
final_value
.try_deserialize()
.map_err(|ce| ConfigurationError::new(Self::main_key_prefix(), None, ce.to_string()))
}
}
#[derive(Debug)]
pub struct ConfigurationError {
field: String,
value: Option<String>,
message: String,
}
impl ConfigurationError {
pub fn new<F: Into<String>, M: Into<String>>(field: F, value: Option<String>, msg: M) -> Self {
ConfigurationError {
field: field.into(),
value,
message: msg.into(),
}
}
}
impl Display for ConfigurationError {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
match &self.value {
Some(v) => write!(f, "Invalid value `{}` for {}: {}", v, self.field, self.message),
None => write!(f, "Invalid value for `{}`: {}", self.field, self.message),
}
}
}
impl Error for ConfigurationError {}
impl From<config::ConfigError> for ConfigurationError {
fn from(err: config::ConfigError) -> Self {
use config::ConfigError;
match err {
ConfigError::FileParse { uri, cause } if uri.is_some() => Self {
field: uri.unwrap(),
value: None,
message: cause.to_string(),
},
ConfigError::Type {
ref unexpected,
ref key,
..
} => Self {
field: format!("{key:?}"),
value: Some(unexpected.to_string()),
message: err.to_string(),
},
ConfigError::NotFound(key) => Self {
field: key,
value: None,
message: "required key not found".to_string(),
},
x => Self::new("", None, x.to_string().as_str()),
}
}
}
impl From<serde_json::error::Error> for ConfigurationError {
fn from(err: serde_json::error::Error) -> Self {
Self {
field: "".to_string(),
value: None,
message: err.to_string(),
}
}
}
#[cfg(test)]
mod test {
use config::ConfigError;
#[test]
fn configuration_error() {
let e = ConfigurationError::new("test", None, "is a string");
assert_eq!(e.to_string(), "Invalid value for `test`: is a string");
let frozen_e = ConfigurationError::from(ConfigError::Frozen);
assert_eq!(frozen_e.to_string(), "Invalid value for ``: configuration is frozen");
}
use serde::{Deserialize, Serialize};
use super::*;
#[allow(dead_code)]
#[derive(Serialize, Deserialize)]
struct SubTari {
monero: String,
}
impl Default for SubTari {
fn default() -> Self {
Self {
monero: "isprivate".into(),
}
}
}
#[allow(dead_code)]
#[derive(Default, Serialize, Deserialize)]
struct SuperTari {
#[serde(flatten)]
pub within: SubTari,
pub over: SubTari,
#[serde(default = "serde_default_string")]
bitcoin: String,
}
#[allow(dead_code)]
fn serde_default_string() -> String {
"ispublic".into()
}
impl SubConfigPath for SuperTari {
fn main_key_prefix() -> &'static str {
"crypto"
}
}
#[derive(Serialize, Deserialize)]
struct OneConfig {
param1: String,
#[serde(default = "param2_serde_default")]
param2: String,
}
impl Default for OneConfig {
fn default() -> Self {
Self {
param1: "param1".into(),
param2: "param2".into(),
}
}
}
fn param2_serde_default() -> String {
"alwaysset".into()
}
impl ConfigPath for OneConfig {
fn main_key_prefix() -> &'static str {
"one"
}
fn overload_key_prefix(_: &Config) -> Result<Option<String>, ConfigurationError> {
Ok(None)
}
}
#[test]
fn config_loaders() -> anyhow::Result<()> {
let config = Config::default();
let one = <OneConfig as DefaultConfigLoader>::load_from(&config)?;
assert_eq!(one.param1, OneConfig::default().param1);
assert_eq!(one.param2, OneConfig::default().param2);
let config = Config::builder()
.add_source(config)
.set_override("one.param1", "can load from main section")
.unwrap()
.build()
.unwrap();
let one = <OneConfig as DefaultConfigLoader>::load_from(&config)?;
assert_eq!(one.param1, "can load from main section");
assert_eq!(one.param2, "param2");
Ok(())
}
}