arduino_report_size_deltas/reports/
mod.rs

1//! A module for API related to parsing of JSON data from CI artifacts.
2//! Additionally, there's a convenient [`parse_json()`] function for parsing of older
3//! JSON formats produced by the [arduino/compile-sketches] action.
4//!
5//! [arduino/compile-sketches]: https://github.com/arduino/compile-sketches
6use crate::error::JsonError;
7use std::{fs, path::Path};
8pub mod structs;
9use structs::{Report, ReportOld};
10
11/// Deserialize a JSON file at the given `path` into a [`Report`].
12///
13/// This will automatically try to parsing old JSON formats when
14/// parsing the newer format fails syntactically.
15pub fn parse_json<P: AsRef<Path>>(path: P) -> Result<Report, JsonError> {
16    let asset = fs::read_to_string(path)?;
17    match serde_json::from_str::<Report>(&asset) {
18        Ok(report) => Ok(report),
19        Err(e) => {
20            if e.is_data() {
21                // if parsing the new format fails (for typing reasons),
22                // then try the old format and convert it.
23                match serde_json::from_str::<ReportOld>(&asset) {
24                    Ok(report) => Ok(report.into()),
25                    Err(e_old) => {
26                        eprintln!("Parsing old format failed: {e_old}");
27                        Err(JsonError::Serde(e))
28                    }
29                }
30            } else {
31                Err(JsonError::Serde(e))
32            }
33        }
34    }
35}
36
37#[cfg(test)]
38mod test {
39    use std::io::Write;
40
41    use super::{JsonError, parse_json};
42    use tempfile::NamedTempFile;
43
44    /// Test parsing of JSON report in newer format
45    #[test]
46    fn parse_new() {
47        for entry in std::fs::read_dir("tests/size-deltas-reports-new").unwrap() {
48            let path = entry.unwrap().path();
49            if path.extension().unwrap().to_string_lossy() == "json" {
50                println!("Parsing {path:?}");
51                let report = parse_json(&path).unwrap();
52                assert!(!report.boards.is_empty());
53                assert!(report.is_valid());
54            } else {
55                println!("Skipped parsing non-JSON file: {}", path.to_string_lossy());
56            }
57        }
58    }
59
60    /// Test parsing of JSON report in newer format
61    #[test]
62    fn parse_old() {
63        for entry in std::fs::read_dir("tests/size-deltas-reports-old").unwrap() {
64            let path = entry.unwrap().path();
65            println!("Parsing {path:?}");
66            let report = parse_json(path).unwrap();
67            assert!(!report.boards.is_empty());
68            assert!(!report.is_valid());
69        }
70    }
71
72    #[test]
73    fn absent_file() {
74        let result = parse_json("not-a-file.json");
75        assert!(result.is_err_and(|e| matches!(e, JsonError::FileReadFail(_))));
76    }
77
78    #[test]
79    fn bad_json() {
80        let bad_asset = NamedTempFile::new().unwrap();
81        let result = parse_json(&bad_asset);
82        assert!(result.is_err_and(|e| matches!(e, JsonError::Serde(_))));
83    }
84
85    #[test]
86    fn bad_report() {
87        let mut bad_asset = NamedTempFile::new().unwrap();
88        bad_asset.write_all("{}".as_bytes()).unwrap();
89        let result = parse_json(&bad_asset);
90        assert!(result.is_err_and(|e| matches!(e, JsonError::Serde(_))));
91    }
92}