Skip to main content

confex_core/
value.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3
4#[derive(Debug, Clone, PartialEq, Default)]
5pub enum ConfigValue {
6    #[default]
7    Null,
8    Bool(bool),
9    Integer(i64),
10    Float(f64),
11    String(String),
12    Array(Vec<ConfigValue>),
13    Table(BTreeMap<String, ConfigValue>),
14}
15
16impl ConfigValue {
17    #[must_use]
18    pub fn as_table(&self) -> Option<&BTreeMap<String, ConfigValue>> {
19        match self {
20            Self::Table(t) => Some(t),
21            _ => None,
22        }
23    }
24
25    #[must_use]
26    pub fn as_table_mut(&mut self) -> Option<&mut BTreeMap<String, ConfigValue>> {
27        match self {
28            Self::Table(t) => Some(t),
29            _ => None,
30        }
31    }
32
33    #[must_use]
34    pub fn is_null(&self) -> bool {
35        matches!(self, Self::Null)
36    }
37}
38
39impl From<bool> for ConfigValue {
40    fn from(v: bool) -> Self {
41        Self::Bool(v)
42    }
43}
44
45impl From<i64> for ConfigValue {
46    fn from(v: i64) -> Self {
47        Self::Integer(v)
48    }
49}
50
51impl From<f64> for ConfigValue {
52    fn from(v: f64) -> Self {
53        Self::Float(v)
54    }
55}
56
57impl From<String> for ConfigValue {
58    fn from(v: String) -> Self {
59        Self::String(v)
60    }
61}
62
63impl From<&str> for ConfigValue {
64    fn from(v: &str) -> Self {
65        Self::String(v.to_owned())
66    }
67}
68
69impl FromStr for ConfigValue {
70    type Err = std::convert::Infallible;
71
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        if s.eq_ignore_ascii_case("true") {
74            return Ok(Self::Bool(true));
75        }
76        if s.eq_ignore_ascii_case("false") {
77            return Ok(Self::Bool(false));
78        }
79        if let Ok(i) = s.parse::<i64>() {
80            return Ok(Self::Integer(i));
81        }
82        if let Ok(f) = s.parse::<f64>() {
83            return Ok(Self::Float(f));
84        }
85        Ok(Self::String(s.to_owned()))
86    }
87}
88
89#[cfg(feature = "json")]
90impl From<serde_json::Value> for ConfigValue {
91    fn from(v: serde_json::Value) -> Self {
92        match v {
93            serde_json::Value::Null => Self::Null,
94            serde_json::Value::Bool(b) => Self::Bool(b),
95            serde_json::Value::Number(n) => n
96                .as_i64()
97                .map(Self::Integer)
98                .or_else(|| n.as_f64().map(Self::Float))
99                .unwrap_or(Self::Null),
100            serde_json::Value::String(s) => Self::String(s),
101            serde_json::Value::Array(arr) => Self::Array(arr.into_iter().map(Self::from).collect()),
102            serde_json::Value::Object(map) => {
103                Self::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
104            }
105        }
106    }
107}
108
109#[cfg(feature = "json")]
110impl From<ConfigValue> for serde_json::Value {
111    fn from(v: ConfigValue) -> Self {
112        match v {
113            ConfigValue::Null => Self::Null,
114            ConfigValue::Bool(b) => Self::Bool(b),
115            ConfigValue::Integer(i) => Self::Number(i.into()),
116            ConfigValue::Float(f) => {
117                serde_json::Number::from_f64(f).map_or(Self::Null, Self::Number)
118            }
119            ConfigValue::String(s) => Self::String(s),
120            ConfigValue::Array(arr) => Self::Array(arr.into_iter().map(Into::into).collect()),
121            ConfigValue::Table(map) => {
122                Self::Object(map.into_iter().map(|(k, v)| (k, v.into())).collect())
123            }
124        }
125    }
126}
127
128#[cfg(feature = "toml")]
129impl From<toml::Value> for ConfigValue {
130    fn from(v: toml::Value) -> Self {
131        match v {
132            toml::Value::Boolean(b) => ConfigValue::Bool(b),
133            toml::Value::Integer(i) => ConfigValue::Integer(i),
134            toml::Value::Float(f) => ConfigValue::Float(f),
135            toml::Value::String(s) => ConfigValue::String(s),
136            toml::Value::Array(arr) => {
137                ConfigValue::Array(arr.into_iter().map(Self::from).collect())
138            }
139            toml::Value::Table(map) => {
140                ConfigValue::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
141            }
142            toml::Value::Datetime(dt) => ConfigValue::String(dt.to_string()),
143        }
144    }
145}
146
147#[cfg(feature = "toml")]
148impl From<ConfigValue> for toml::Value {
149    fn from(v: ConfigValue) -> Self {
150        match v {
151            // TOML has no null; map to empty string as lossy fallback
152            ConfigValue::Null => toml::Value::Table(toml::map::Map::default()),
153            ConfigValue::Bool(b) => toml::Value::Boolean(b),
154            ConfigValue::Integer(i) => toml::Value::Integer(i),
155            ConfigValue::Float(f) => toml::Value::Float(f),
156            ConfigValue::String(s) => toml::Value::String(s),
157            ConfigValue::Array(arr) => {
158                toml::Value::Array(arr.into_iter().map(Self::from).collect())
159            }
160            ConfigValue::Table(map) => {
161                toml::Value::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
162            }
163        }
164    }
165}