Skip to main content

diode_base/
config.rs

1use std::collections::BTreeMap;
2use std::path::Path;
3
4use diode::{Extract, StdError};
5use serde::{Deserialize, Serialize, de::DeserializeOwned};
6
7#[derive(Default, Serialize, Deserialize)]
8pub struct Config {
9    #[serde(flatten)]
10    pub(crate) configs: BTreeMap<String, serde_json::Value>,
11}
12
13pub trait ConfigSection: DeserializeOwned {
14    fn key() -> &'static str;
15}
16
17impl Config {
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    pub fn get<T>(&self, name: impl AsRef<str>) -> Result<T, StdError>
23    where
24        T: DeserializeOwned,
25    {
26        Ok(serde_json::from_value(
27            self.configs
28                .get(name.as_ref())
29                .cloned()
30                .unwrap_or(serde_json::Value::Null),
31        )?)
32    }
33
34    pub fn set<T>(&mut self, name: impl Into<String>, value: T) -> Result<(), StdError>
35    where
36        T: Serialize,
37    {
38        self.configs
39            .insert(name.into(), serde_json::to_value(value)?);
40        Ok(())
41    }
42
43    pub fn with<T>(mut self, name: impl Into<String>, value: T) -> Self
44    where
45        T: Serialize,
46    {
47        self.configs
48            .insert(name.into(), serde_json::to_value(value).unwrap());
49        self
50    }
51
52    pub fn merge_from(&mut self, other: Self) -> Result<(), StdError> {
53        for (key, value) in other.configs {
54            let entry = self.configs.entry(key);
55            merge_json_from(entry.or_insert(serde_json::Value::Null), value)?;
56        }
57        Ok(())
58    }
59
60    pub fn parse<T>(text: T) -> Result<Self, StdError>
61    where
62        T: AsRef<str>,
63    {
64        Ok(serde_json::from_str(text.as_ref())?)
65    }
66
67    pub async fn parse_file(path: impl AsRef<Path>) -> Result<Self, StdError> {
68        let text = tokio::fs::read_to_string(path).await?;
69        Self::parse(text)
70    }
71
72    /// Check if the config is empty
73    pub fn is_empty(&self) -> bool {
74        self.configs.is_empty()
75    }
76
77    /// Get the number of config entries
78    pub fn len(&self) -> usize {
79        self.configs.len()
80    }
81}
82
83impl<T> Extract<T> for Config
84where
85    T: ConfigSection,
86{
87    fn extract(ctx: &diode::AppContext) -> Result<T, diode::AppError> {
88        Ok(ctx
89            .get_component_ref::<Config>()
90            .unwrap()
91            .get::<T>(T::key())
92            .unwrap())
93    }
94}
95
96fn merge_json_from(lhs: &mut serde_json::Value, rhs: serde_json::Value) -> Result<(), StdError> {
97    match lhs {
98        serde_json::Value::Object(l) => match rhs {
99            serde_json::Value::Object(r) => {
100                for (key, value) in r {
101                    let entry = l.entry(key);
102                    merge_json_from(entry.or_insert(serde_json::Value::Null), value)?;
103                }
104            }
105            _ => *lhs = rhs,
106        },
107        serde_json::Value::Array(l) => match rhs {
108            serde_json::Value::Array(r) => {
109                l.extend(r);
110            }
111            _ => *lhs = rhs,
112        },
113        _ => *lhs = rhs,
114    }
115    Ok(())
116}