arduino_report_size_deltas/reports/
structs.rs

1//! A module that declares the data structures used for parsing JSON data.
2//!
3//! [arduino/compile-sketches]: https://github.com/arduino/compile-sketches
4//! [arduino/report-size-deltas]: https://github.com/arduino/report-size-deltas
5//!
6//! All these structures were crafted from observations in
7//!
8//! - python source code (including tests) for
9//!   [arduino/report-size-deltas] and [arduino/compile-sketches]
10//! - actual artifacts produced via [arduino/compile-sketches].
11//!
12//! There doesn't seem to be a documented schema for the JSON data being parsed.'
13//! All python code producing the JSON data is partially typed, so it hard to
14//! discern a proper schema.
15use serde::Deserialize;
16
17/// The root structure that describes a report about compilation.
18#[derive(Debug, Deserialize, Default)]
19pub struct Report {
20    /// The boards targeted when compiling sketches.
21    pub boards: Vec<Board>,
22    /// The SHA hash of the commit from which compilation was performed.
23    pub commit_hash: String,
24    /// The URL of the commit referenced by the [`Report::commit_hash`].
25    pub commit_url: String,
26}
27
28impl Report {
29    /// Ensure all needed data is present.
30    ///
31    /// [`parse_artifacts()`][fn@crate::parse_artifacts] supports parsing of
32    /// old/outdated JSON formats previously produced by the `arduino/compile-sketches`
33    /// action. Use this function to ensure enough data is present to form a [`Report`].
34    pub fn is_valid(&self) -> bool {
35        if self.boards.is_empty() {
36            return false;
37        }
38        for board in &self.boards {
39            if board.sizes.is_none() || board.sizes.as_ref().is_some_and(|v| v.is_empty()) {
40                return false;
41            }
42            // unwrap() yields Some value because of the check above
43            for size in board.sizes.as_ref().unwrap() {
44                if !size.has_maximum() {
45                    return false;
46                }
47            }
48        }
49        true
50    }
51}
52
53/// A intermediate structure used to translate olf JSON formats into the newer format.
54#[derive(Debug, Deserialize)]
55pub(super) struct ReportOld {
56    pub board: String,
57    pub commit_hash: String,
58    pub commit_url: String,
59    pub sketches: Vec<Sketch>,
60    pub sizes: Option<Vec<BoardSize>>,
61}
62
63impl From<ReportOld> for Report {
64    /// Convert a [`ReportOld`] instance into a [`Report`] instance.
65    fn from(value: ReportOld) -> Self {
66        let board = Board {
67            board: value.board,
68            sketches: value.sketches,
69            sizes: value.sizes,
70        };
71        Self {
72            boards: vec![board],
73            commit_hash: value.commit_hash,
74            commit_url: value.commit_url,
75        }
76    }
77}
78
79/// A data structure to describe the target [`Board::board`] and compilation context.
80///
81/// Includes it's  ([`Board::sizes`]), and which [`Board::sketches`] were compiled.
82#[derive(Debug, Deserialize, Default)]
83pub struct Board {
84    /// The board's "Fully Qualified Board Name" (FQBN).
85    ///
86    /// A board-specific ID used by Arduino CLI tool.
87    pub board: String,
88
89    /// The list of compiled [`Sketch`]es.
90    pub sketches: Vec<Sketch>,
91
92    /// The board's maximum capacity of memory and flash.
93    pub sizes: Option<Vec<BoardSize>>,
94}
95
96/// A data structure used to describe a compiled sketch.
97#[derive(Debug, Deserialize, Default)]
98pub struct Sketch {
99    /// The relative path to the sketch compiled.
100    ///
101    /// Often relative to the project's root directory.
102    pub name: String,
103
104    /// Was sketch successfully compiled?
105    pub compilation_success: bool,
106
107    /// The compile size of the sketch.
108    ///
109    /// This [`Vec`] typically includes details about
110    /// [`SketchSizeKind::Flash`] and [`SketchSizeKind::Ram`].
111    pub sizes: Vec<SketchSizeKind>,
112
113    /// The number of compilation warnings (if any).
114    ///
115    /// This information is only included in the report artifacts when the
116    /// `enable-warnings-report` option is enabled for `arduino/compile-sketches`.
117    pub warnings: Option<SketchWarnings>,
118}
119
120/// The number of warnings about a particular sketch's compilation.
121#[derive(Debug, Deserialize, Default)]
122pub struct SketchWarnings {
123    /// The current number of warnings from latest compilation.
124    pub current: AbsCount,
125
126    /// The previous number of warnings from latest compilation.
127    pub previous: AbsCount,
128
129    /// The change in the number of warnings from [`SketchWarnings::previous`] to [`SketchWarnings::current`].
130    pub delta: AbsCount,
131}
132
133/// An absolute count used for the values of [`SketchWarnings`].
134#[derive(Debug, Deserialize, Default)]
135pub struct AbsCount {
136    /// The absolute 32-bit integer value.
137    ///
138    /// "Absolute" as in "not relative", meaning this value can be negative.
139    pub absolute: i32,
140}
141
142/// A data structure to describe a compilation's size.
143///
144/// Used for [`SketchSizeKind::Ram`] and [`SketchSizeKind::Flash`].
145#[derive(Debug, Deserialize, Default)]
146pub struct SketchSize {
147    /// The maximum size of something.
148    ///
149    /// Only present for compatibility with older JSON formats.
150    /// This is not actually used in the generated report comment.
151    /// Instead, maximum values are stored in [`Board::sizes`].
152    pub maximum: Option<SizeValue<u64>>,
153
154    /// The current compilation size.
155    pub current: SketchDeltaSize,
156
157    /// The previous compilation size.
158    ///
159    /// Can be [`None`] if no previous compilation was performed.
160    pub previous: Option<SketchDeltaSize>,
161
162    /// The change in compilation size from [SketchSize::previous] to [`SketchSize::current`].
163    ///
164    /// Can be [`None`] if no previous compilation was performed.
165    pub delta: Option<SketchDeltaSize>,
166}
167
168impl SketchSize {
169    /// A convenience function to get [`SketchSize::delta`].
170    ///
171    /// Falls back to [`SketchSize::current`] when [`SketchSize::delta`] is [`None`].
172    pub fn get_delta(&self) -> &SketchDeltaSize {
173        self.delta.as_ref().unwrap_or(&self.current)
174    }
175}
176
177/// An enumeration of possible compilation size kinds.
178#[derive(Debug, Deserialize)]
179#[serde(tag = "name")]
180pub enum SketchSizeKind {
181    /// The compilation size of "Ram for global variables".
182    #[serde(rename(deserialize = "RAM for global variables"))]
183    Ram {
184        #[serde(flatten)]
185        size: SketchSize,
186    },
187
188    /// The compilation size of flash memory.
189    #[serde(rename(deserialize = "flash"))]
190    Flash {
191        #[serde(flatten)]
192        size: SketchSize,
193    },
194}
195
196impl Default for SketchSizeKind {
197    fn default() -> Self {
198        Self::Flash {
199            size: Default::default(),
200        }
201    }
202}
203
204/// An enumeration of the possible values used to describe a compilation's size.
205#[derive(Debug, Deserialize)]
206#[serde(untagged)]
207pub enum SizeValue<T> {
208    /// Represents a "Not Applicable" (N/A) value.
209    NotApplicable(String),
210
211    /// Represents a known value.
212    Known(T),
213}
214
215impl<T> Default for SizeValue<T> {
216    fn default() -> Self {
217        SizeValue::NotApplicable(String::from("N/A"))
218    }
219}
220
221/// A data structure to describe fields in [`SketchSize`].
222#[derive(Debug, Deserialize, Default)]
223pub struct SketchDeltaSize {
224    /// The absolute compilation size value.
225    ///
226    /// "Absolute" as in "not relative", meaning this 64-bit integer can be negative.
227    pub absolute: SizeValue<i64>,
228
229    /// The relative compilation size.
230    ///
231    /// Often relative to a previous compilation size.
232    /// This can be [`None`]if no previous compilation was preformed.
233    pub relative: Option<SizeValue<f32>>,
234}
235
236/// An enumeration of a [`Board::sizes`].
237#[derive(Debug, Deserialize)]
238#[serde(tag = "name")]
239pub enum BoardSize {
240    /// The maximum size of "RAM for global variables".
241    #[serde(rename(deserialize = "RAM for global variables"))]
242    Ram { maximum: Option<SizeValue<u64>> },
243    /// The maximum size of flash memory.
244    #[serde(rename(deserialize = "flash"))]
245    Flash { maximum: Option<SizeValue<u64>> },
246}
247
248impl Default for BoardSize {
249    fn default() -> Self {
250        BoardSize::Flash {
251            maximum: Default::default(),
252        }
253    }
254}
255
256impl BoardSize {
257    /// A convenience function to ensure the board's maximum sizes are defined.
258    ///
259    /// Primarily used by [`Report::is_valid()`].
260    pub fn has_maximum(&self) -> bool {
261        match self {
262            BoardSize::Ram { maximum } => maximum.is_some(),
263            BoardSize::Flash { maximum } => maximum.is_some(),
264        }
265    }
266}
267
268#[cfg(test)]
269mod test {
270    use crate::report_structs::BoardSize;
271
272    use super::{Report, SketchSize, SketchSizeKind};
273
274    #[test]
275    fn no_boards() {
276        let report = Report {
277            boards: vec![],
278            commit_hash: Default::default(),
279            commit_url: Default::default(),
280        };
281        assert!(!report.is_valid());
282    }
283
284    #[test]
285    fn default_enum() {
286        let _size_default = SketchSize::default();
287        assert!(matches!(
288            SketchSizeKind::default(),
289            SketchSizeKind::Flash {
290                size: _size_default
291            }
292        ));
293
294        assert!(matches!(
295            BoardSize::default(),
296            BoardSize::Flash { maximum: None }
297        ));
298    }
299}