toml_test/
decoded.rs

1use std::io::Read;
2use std::io::Write;
3
4/// Logical representation of any TOML value
5#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6#[serde(untagged)]
7pub enum DecodedValue {
8    Scalar(DecodedScalar),
9    Table(std::collections::HashMap<String, DecodedValue>),
10    Array(Vec<DecodedValue>),
11}
12
13impl DecodedValue {
14    pub fn from_slice(v: &[u8]) -> Result<Self, crate::Error> {
15        serde_json::from_slice(v).map_err(|e| {
16            crate::Error::new(format!(
17                "failed decoding: {}\n```json\n{}\n```",
18                e,
19                String::from_utf8_lossy(v)
20            ))
21        })
22    }
23
24    pub fn to_string_pretty(&self) -> Result<String, crate::Error> {
25        serde_json::to_string_pretty(self).map_err(crate::Error::new)
26    }
27
28    /// See [`Command`][crate::Command]
29    pub fn from_stdin() -> Result<Self, crate::Error> {
30        let mut buf = Vec::new();
31        std::io::stdin()
32            .read_to_end(&mut buf)
33            .map_err(crate::Error::new)?;
34        Self::from_slice(&buf)
35    }
36
37    /// See [`Command`][crate::Command]
38    pub fn into_stdout(&self) -> Result<(), crate::Error> {
39        let s = self.to_string_pretty()?;
40        std::io::stdout()
41            .write_all(s.as_bytes())
42            .map_err(crate::Error::new)
43    }
44}
45
46/// A part of [`DecodedValue`]
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48#[serde(rename_all = "kebab-case")]
49#[serde(tag = "type", content = "value")]
50pub enum DecodedScalar {
51    String(String),
52    Integer(String),
53    Float(String),
54    Bool(String),
55    Datetime(String),
56    DatetimeLocal(String),
57    DateLocal(String),
58    TimeLocal(String),
59}
60
61impl DecodedScalar {
62    pub fn as_str(&self) -> &str {
63        match self {
64            DecodedScalar::String(v)
65            | DecodedScalar::Integer(v)
66            | DecodedScalar::Float(v)
67            | DecodedScalar::Bool(v)
68            | DecodedScalar::Datetime(v)
69            | DecodedScalar::DatetimeLocal(v)
70            | DecodedScalar::DateLocal(v)
71            | DecodedScalar::TimeLocal(v) => v.as_str(),
72        }
73    }
74}
75
76impl<'a> From<&'a str> for DecodedScalar {
77    fn from(other: &'a str) -> Self {
78        DecodedScalar::String(other.to_owned())
79    }
80}
81
82impl<'a> From<&'a String> for DecodedScalar {
83    fn from(other: &'a String) -> Self {
84        DecodedScalar::String(other.clone())
85    }
86}
87
88impl From<String> for DecodedScalar {
89    fn from(other: String) -> Self {
90        DecodedScalar::String(other)
91    }
92}
93
94impl From<i64> for DecodedScalar {
95    fn from(other: i64) -> Self {
96        DecodedScalar::Integer(other.to_string())
97    }
98}
99
100impl From<f64> for DecodedScalar {
101    fn from(other: f64) -> Self {
102        let s = if other.is_nan() {
103            "nan".to_owned()
104        } else if other.is_infinite() && other.is_sign_negative() {
105            "-inf".to_owned()
106        } else if other.is_infinite() && other.is_sign_positive() {
107            "inf".to_owned()
108        } else {
109            let mut buffer = ryu::Buffer::new();
110            let printed = buffer.format(other);
111            printed.to_owned()
112        };
113        DecodedScalar::Float(s)
114    }
115}
116
117impl From<bool> for DecodedScalar {
118    fn from(other: bool) -> Self {
119        DecodedScalar::Bool(other.to_string())
120    }
121}
122
123impl PartialEq for DecodedScalar {
124    fn eq(&self, other: &Self) -> bool {
125        #[allow(clippy::if_same_then_else)]
126        match (self, other) {
127            (DecodedScalar::String(s), DecodedScalar::String(o)) => s == o,
128            (DecodedScalar::Integer(s), DecodedScalar::Integer(o)) => s == o,
129            (DecodedScalar::Float(s), DecodedScalar::Float(o)) => {
130                if s == "inf" && o == "+inf" {
131                    true
132                } else if s == "+inf" && o == "inf" {
133                    true
134                } else if s == "nan" && o == "nan" {
135                    true
136                } else {
137                    let s = s.parse::<f64>().unwrap();
138                    let o = o.parse::<f64>().unwrap();
139                    s == o
140                }
141            }
142            (DecodedScalar::Bool(s), DecodedScalar::Bool(o)) => s == o,
143            (DecodedScalar::Datetime(s), DecodedScalar::Datetime(o)) => {
144                parse_date_time(s) == parse_date_time(o)
145            }
146            (DecodedScalar::DatetimeLocal(s), DecodedScalar::DatetimeLocal(o)) => {
147                parse_date_time_local(s) == parse_date_time_local(o)
148            }
149            (DecodedScalar::DateLocal(s), DecodedScalar::DateLocal(o)) => {
150                parse_date_local(s) == parse_date_local(o)
151            }
152            (DecodedScalar::TimeLocal(s), DecodedScalar::TimeLocal(o)) => {
153                parse_time_local(s) == parse_time_local(o)
154            }
155            (_, _) => false,
156        }
157    }
158}
159
160fn parse_date_time(s: &str) -> chrono::DateTime<chrono::FixedOffset> {
161    match normalize_datetime(s).parse() {
162        Ok(d) => d,
163        Err(err) => panic!("Failed to parse {s:?}: {err}"),
164    }
165}
166
167fn parse_date_time_local(s: &str) -> chrono::NaiveDateTime {
168    match normalize_datetime(s).parse() {
169        Ok(d) => d,
170        Err(err) => panic!("Failed to parse {s:?}: {err}"),
171    }
172}
173
174fn parse_date_local(s: &str) -> chrono::NaiveDate {
175    match s.parse() {
176        Ok(d) => d,
177        Err(err) => panic!("Failed to parse {s:?}: {err}"),
178    }
179}
180
181fn parse_time_local(s: &str) -> chrono::NaiveTime {
182    match s.parse() {
183        Ok(d) => d,
184        Err(err) => panic!("Failed to parse {s:?}: {err}"),
185    }
186}
187
188fn normalize_datetime(s: &str) -> String {
189    s.chars()
190        .map(|c| match c {
191            ' ' => 'T',
192            't' => 'T',
193            'z' => 'Z',
194            _ => c,
195        })
196        .collect()
197}
198
199impl Eq for DecodedScalar {}
200
201#[cfg(test)]
202mod test {
203    use super::*;
204
205    #[test]
206    fn string_equality() {
207        assert_eq!(DecodedScalar::from("foo"), DecodedScalar::from("foo"));
208        assert_ne!(DecodedScalar::from("foo"), DecodedScalar::from("bar"));
209        assert_ne!(DecodedScalar::from("42"), DecodedScalar::from(42));
210        assert_ne!(DecodedScalar::from("true"), DecodedScalar::from(true));
211    }
212
213    #[test]
214    fn integer_equality() {
215        assert_eq!(DecodedScalar::from(42), DecodedScalar::from(42));
216        assert_ne!(DecodedScalar::from(42), DecodedScalar::from(21));
217        assert_ne!(DecodedScalar::from(42), DecodedScalar::from("42"));
218    }
219
220    #[test]
221    fn float_equality() {
222        assert_eq!(DecodedScalar::from(42.0), DecodedScalar::from(42.0));
223        assert_ne!(DecodedScalar::from(42.0), DecodedScalar::from(21.0));
224        assert_ne!(DecodedScalar::from(42.0), DecodedScalar::from("42.0"));
225    }
226
227    #[test]
228    fn nan_equality() {
229        assert_eq!(DecodedScalar::from(f64::NAN), DecodedScalar::from(f64::NAN));
230        assert_eq!(
231            DecodedScalar::from(f64::NAN),
232            DecodedScalar::Float("nan".to_owned())
233        );
234        assert_ne!(DecodedScalar::from(f64::NAN), DecodedScalar::from("nan"));
235    }
236
237    #[test]
238    fn inf_equality() {
239        assert_eq!(
240            DecodedScalar::from(f64::INFINITY),
241            DecodedScalar::from(f64::INFINITY)
242        );
243        assert_ne!(
244            DecodedScalar::from(f64::INFINITY),
245            DecodedScalar::from(f64::NEG_INFINITY)
246        );
247        assert_eq!(
248            DecodedScalar::from(f64::INFINITY),
249            DecodedScalar::Float("inf".to_owned())
250        );
251        assert_eq!(
252            DecodedScalar::from(f64::INFINITY),
253            DecodedScalar::Float("+inf".to_owned())
254        );
255        assert_ne!(
256            DecodedScalar::from(f64::INFINITY),
257            DecodedScalar::from("inf")
258        );
259    }
260
261    #[test]
262    fn float_exp_equality() {
263        assert_eq!(DecodedScalar::from(3.0e14), DecodedScalar::from(3.0e14));
264        assert_eq!(
265            DecodedScalar::from(3.0e14),
266            DecodedScalar::Float("3.0e14".to_owned())
267        );
268    }
269
270    #[test]
271    fn float_binary_equality() {
272        #![allow(clippy::excessive_precision)]
273
274        // These cases are equivalent, just wanting to call out how Rust, at times, encodes the
275        // number in a string.
276        assert_eq!(
277            DecodedScalar::from(3141.5927),
278            DecodedScalar::Float("3141.5927".to_owned())
279        );
280        assert_eq!(
281            DecodedScalar::from(3141.59270000000015),
282            DecodedScalar::Float("3141.5927".to_owned())
283        );
284    }
285
286    #[test]
287    fn neg_inf_equality() {
288        assert_eq!(
289            DecodedScalar::from(f64::NEG_INFINITY),
290            DecodedScalar::from(f64::NEG_INFINITY)
291        );
292        assert_ne!(
293            DecodedScalar::from(f64::NEG_INFINITY),
294            DecodedScalar::from(f64::INFINITY)
295        );
296        assert_eq!(
297            DecodedScalar::from(f64::NEG_INFINITY),
298            DecodedScalar::Float("-inf".to_owned())
299        );
300        assert_ne!(
301            DecodedScalar::from(f64::NEG_INFINITY),
302            DecodedScalar::from("-inf")
303        );
304    }
305
306    #[test]
307    fn bool_equality() {
308        assert_eq!(DecodedScalar::from(true), DecodedScalar::from(true));
309        assert_ne!(DecodedScalar::from(true), DecodedScalar::from(false));
310        assert_ne!(DecodedScalar::from(true), DecodedScalar::from("true"));
311    }
312
313    #[test]
314    fn datetime_equality() {
315        assert_eq!(
316            DecodedScalar::Datetime("1987-07-05 17:45:00Z".to_owned()),
317            DecodedScalar::Datetime("1987-07-05 17:45:00Z".to_owned())
318        );
319        assert_eq!(
320            DecodedScalar::Datetime("1987-07-05T17:45:56.123456Z".to_owned()),
321            DecodedScalar::Datetime("1987-07-05T17:45:56.123456Z".to_owned()),
322        );
323        assert_ne!(
324            DecodedScalar::Datetime("1987-07-05 17:45:00Z".to_owned()),
325            DecodedScalar::Datetime("2000-07-05 17:45:00Z".to_owned())
326        );
327        assert_eq!(
328            DecodedScalar::Datetime("1987-07-05t17:45:00z".to_owned()),
329            DecodedScalar::Datetime("1987-07-05 17:45:00Z".to_owned())
330        );
331        assert_ne!(
332            DecodedScalar::Datetime("1987-07-05 17:45:00Z".to_owned()),
333            DecodedScalar::from("1987-07-05 17:45:00Z")
334        );
335    }
336
337    #[test]
338    fn datetime_local_equality() {
339        assert_eq!(
340            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00".to_owned()),
341            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00".to_owned())
342        );
343        assert_eq!(
344            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00.444".to_owned()),
345            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00.444".to_owned())
346        );
347        assert_ne!(
348            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00".to_owned()),
349            DecodedScalar::DatetimeLocal("2000-07-05 17:45:00".to_owned())
350        );
351        assert_eq!(
352            DecodedScalar::DatetimeLocal("1987-07-05t17:45:00".to_owned()),
353            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00".to_owned())
354        );
355        assert_ne!(
356            DecodedScalar::DatetimeLocal("1987-07-05 17:45:00".to_owned()),
357            DecodedScalar::from("1987-07-05 17:45:00")
358        );
359    }
360
361    #[test]
362    fn date_local_equality() {
363        assert_eq!(
364            DecodedScalar::DateLocal("1987-07-05".to_owned()),
365            DecodedScalar::DateLocal("1987-07-05".to_owned())
366        );
367        assert_ne!(
368            DecodedScalar::DateLocal("1987-07-05".to_owned()),
369            DecodedScalar::DateLocal("2000-07-05".to_owned())
370        );
371        assert_ne!(
372            DecodedScalar::DateLocal("1987-07-05".to_owned()),
373            DecodedScalar::from("1987-07-05")
374        );
375    }
376
377    #[test]
378    fn time_local_equality() {
379        assert_eq!(
380            DecodedScalar::TimeLocal("17:45:00".to_owned()),
381            DecodedScalar::TimeLocal("17:45:00".to_owned())
382        );
383        assert_eq!(
384            DecodedScalar::TimeLocal("17:45:00.444".to_owned()),
385            DecodedScalar::TimeLocal("17:45:00.444".to_owned())
386        );
387        assert_ne!(
388            DecodedScalar::TimeLocal("17:45:00".to_owned()),
389            DecodedScalar::TimeLocal("19:45:00".to_owned())
390        );
391        assert_ne!(
392            DecodedScalar::TimeLocal("17:45:00".to_owned()),
393            DecodedScalar::from("17:45:00")
394        );
395    }
396}