1pub trait ConfigObject<V>: Sized + IntoIterator<Item = (String, V)> {
5 fn new() -> Self;
7 fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a V)>
9 where
10 V: 'a;
11 fn get(&self, key: &str) -> Option<&V>;
13 fn get_mut(&mut self, key: &str) -> Option<&mut V>;
15 fn insert(&mut self, key: String, value: V);
17}
18
19pub trait ConfigValue: Clone {
24 type Object: ConfigObject<Self>;
26 fn kind(&self) -> &'static str;
28 fn as_object(&self) -> Option<&Self::Object>;
30 fn as_object_mut(&mut self) -> Option<&mut Self::Object>;
32 fn into_object(self) -> Result<Self::Object, Self>;
34 fn as_array(&self) -> Option<&[Self]>;
36 fn as_str(&self) -> Option<&str>;
38 fn object(object: Self::Object) -> Self;
40 fn array(items: Vec<Self>) -> Self;
42 fn string(text: String) -> Self;
44 fn stringify(&self) -> Option<String>;
47}
48
49impl ConfigObject<serde_json::Value> for serde_json::Map<String, serde_json::Value> {
50 fn new() -> Self {
51 Self::new()
52 }
53 fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a serde_json::Value)>
54 where
55 serde_json::Value: 'a,
56 {
57 self.iter()
58 }
59 fn get(&self, key: &str) -> Option<&serde_json::Value> {
60 self.get(key)
61 }
62 fn get_mut(&mut self, key: &str) -> Option<&mut serde_json::Value> {
63 self.get_mut(key)
64 }
65 fn insert(&mut self, key: String, value: serde_json::Value) {
66 self.insert(key, value);
67 }
68}
69
70impl ConfigObject<toml::Value> for toml::Table {
71 fn new() -> Self {
72 Self::new()
73 }
74 fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a toml::Value)>
75 where
76 toml::Value: 'a,
77 {
78 self.iter()
79 }
80 fn get(&self, key: &str) -> Option<&toml::Value> {
81 self.get(key)
82 }
83 fn get_mut(&mut self, key: &str) -> Option<&mut toml::Value> {
84 self.get_mut(key)
85 }
86 fn insert(&mut self, key: String, value: toml::Value) {
87 self.insert(key, value);
88 }
89}
90
91impl ConfigValue for serde_json::Value {
92 type Object = serde_json::Map<String, Self>;
93 fn kind(&self) -> &'static str {
94 match self {
95 Self::Null => "null",
96 Self::Bool(_) => "bool",
97 Self::Number(_) => "number",
98 Self::String(_) => "string",
99 Self::Array(_) => "array",
100 Self::Object(_) => "object",
101 }
102 }
103 fn as_object(&self) -> Option<&<Self as ConfigValue>::Object> {
104 self.as_object()
105 }
106 fn as_object_mut(&mut self) -> Option<&mut <Self as ConfigValue>::Object> {
107 self.as_object_mut()
108 }
109 fn into_object(self) -> Result<<Self as ConfigValue>::Object, Self> {
110 match self {
111 Self::Object(map) => Ok(map),
112 other => Err(other),
113 }
114 }
115 fn as_array(&self) -> Option<&[Self]> {
116 self.as_array().map(Vec::as_slice)
117 }
118 fn as_str(&self) -> Option<&str> {
119 self.as_str()
120 }
121 fn object(object: <Self as ConfigValue>::Object) -> Self {
122 Self::Object(object)
123 }
124 fn array(items: Vec<Self>) -> Self {
125 Self::Array(items)
126 }
127 fn string(text: String) -> Self {
128 Self::String(text)
129 }
130 fn stringify(&self) -> Option<String> {
131 match self {
132 Self::Bool(b) => Some(b.to_string()),
133 Self::Number(n) => Some(if n.is_i64() || n.is_u64() {
134 n.to_string()
135 } else {
136 n.as_f64().map(float).unwrap_or_else(|| n.to_string())
137 }),
138 Self::String(s) => Some(s.clone()),
139 Self::Null | Self::Array(_) | Self::Object(_) => None,
140 }
141 }
142}
143
144impl ConfigValue for toml::Value {
145 type Object = toml::Table;
146 fn kind(&self) -> &'static str {
147 match self {
148 Self::String(_) => "string",
149 Self::Integer(_) | Self::Float(_) => "number",
150 Self::Boolean(_) => "bool",
151 Self::Datetime(_) => "datetime",
152 Self::Array(_) => "array",
153 Self::Table(_) => "object",
154 }
155 }
156 fn as_object(&self) -> Option<&Self::Object> {
157 self.as_table()
158 }
159 fn as_object_mut(&mut self) -> Option<&mut Self::Object> {
160 self.as_table_mut()
161 }
162 fn into_object(self) -> Result<Self::Object, Self> {
163 match self {
164 Self::Table(map) => Ok(map),
165 other => Err(other),
166 }
167 }
168 fn as_array(&self) -> Option<&[Self]> {
169 self.as_array().map(Vec::as_slice)
170 }
171 fn as_str(&self) -> Option<&str> {
172 self.as_str()
173 }
174 fn object(object: Self::Object) -> Self {
175 Self::Table(object)
176 }
177 fn array(items: Vec<Self>) -> Self {
178 Self::Array(items)
179 }
180 fn string(text: String) -> Self {
181 Self::String(text)
182 }
183 fn stringify(&self) -> Option<String> {
184 match self {
185 Self::String(s) => Some(s.clone()),
186 Self::Integer(i) => Some(i.to_string()),
187 Self::Float(f) => Some(float(*f)),
188 Self::Boolean(b) => Some(b.to_string()),
189 Self::Datetime(dt) => Some(dt.to_string()),
190 Self::Array(_) | Self::Table(_) => None,
191 }
192 }
193}
194
195fn float(f: f64) -> String {
196 if f.is_finite() {
197 format!("{f:?}")
198 } else if f.is_nan() {
199 "nan".into()
200 } else if f.is_sign_negative() {
201 "-inf".into()
202 } else {
203 "inf".into()
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::ConfigFormat;
211
212 fn rendering<V: ConfigFormat>() {
213 for (source, expected) in [
214 ("true", "true"),
215 ("false", "false"),
216 ("1", "1"),
217 ("1.0", "1.0"),
218 ("-0.0", "-0.0"),
219 ("1e300", "1e300"),
220 ("1e-7", "1e-7"),
221 ] {
222 assert_eq!(
223 V::parse_inline(source.into()).stringify().as_deref(),
224 Some(expected)
225 );
226 }
227 assert_eq!(
228 V::string("plain".into()).stringify().as_deref(),
229 Some("plain")
230 );
231 assert_eq!(V::array(Vec::new()).stringify(), None);
232 assert_eq!(V::object(V::Object::new()).stringify(), None);
233 }
234
235 #[test]
236 fn embedded_float_spelling_is_independent_of_serialization() {
237 let json = serde_json::json!({"v": 1e20});
238 let toml = toml::Value::parse_document("v = 1e20").unwrap();
239 assert_eq!(json["v"].stringify().as_deref(), Some("1e20"));
240 assert_eq!(toml["v"].stringify().as_deref(), Some("1e20"));
241 assert_eq!(crate::format::emit(json, false).unwrap(), "{\"v\":1e+20}\n");
242 assert_eq!(
243 crate::format::emit(toml, false).unwrap(),
244 "v = 100000000000000000000.0\n"
245 );
246 }
247
248 #[test]
249 fn native_scalar_rendering_preserves_existing_spellings() {
250 rendering::<serde_json::Value>();
251 rendering::<toml::Value>();
252 assert_eq!(serde_json::Value::Null.stringify(), None);
253 assert_eq!(
254 serde_json::Value::from(u64::MAX).stringify().as_deref(),
255 Some("18446744073709551615")
256 );
257 for spelling in [
258 "inf",
259 "-inf",
260 "nan",
261 "1979-05-27T07:32:00Z",
262 "1979-05-27",
263 "07:32:00",
264 ] {
265 assert_eq!(
266 toml::Value::parse_inline(spelling.into())
267 .stringify()
268 .as_deref(),
269 Some(spelling)
270 );
271 }
272 }
273}