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}