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 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}