Skip to main content

cmsis_pdsc_parser/
pdsc.rs

1//! PDSC pack-level types (description, releases, devices, etc.)
2
3use serde::{Deserialize, Serialize};
4
5use crate::family::Family;
6
7#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
8/// Represents the [PDSC Descrpition](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_package_description.html)
9/// element.
10pub struct Description {
11    /// File path, file name, and file extension with an overview of documentation in markdown format
12    pub overview: Option<String>,
13
14    /// The description body containing a brief markdown desrciption
15    #[serde(rename = "#content")]
16    pub content: Option<String>,
17}
18
19#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
20/// Represents the [PDSC ECCN](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_ECCN.html) element
21pub struct Eccn {
22    #[serde(rename = "ECCN-EU")]
23    pub eu: String,
24    #[serde(rename = "ECCN-US")]
25    pub us: String,
26}
27
28#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
29/// Representation of the [PDSC LicenseSets](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_licenseSets_pg.html#element_licenseSets)
30/// element
31pub struct LicenseSets {
32    #[serde(rename = "licenseSet")]
33    pub license_set: Vec<LicenseSet>,
34}
35
36#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
37/// Represents the [PDSC LicenseSetsType](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_licenseSets_pg.html#element_licenseSets)
38pub struct LicenseSet {
39    /// License set identifier string, must be uniuqe in the PDSC file
40    pub id: String,
41    /// If set to true this license set is associated with all content, not explicitly referencing another license set
42    pub default: Option<bool>,
43    /// If set to true this license set is required to be accepted by the user before installation starts.
44    pub gating: Option<bool>,
45    /// Description of the license file refereneces
46    pub license: Vec<License>,
47}
48
49#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
50/// Represents the [PDSC License](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_licenseSets_pg.html#element_licensefile)
51/// element.
52///
53/// Contains a description of an individual license file
54pub struct License {
55    /// License filename with pack base directory relative path
56    pub name: String,
57
58    /// Display string used by tools to represent the license
59    pub title: String,
60
61    /// Machine readable licence ID string according to the [SPDX License List](https://spdx.org/licenses/)
62    pub spdx: Option<String>,
63
64    /// Public web link to the license text
65    pub url: Option<String>,
66}
67
68#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
69/// Represents the [PDSC Dominate](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_dominate.html) element
70pub struct Dominate {
71    /// Descriptive text that explains the reason for dominate
72    pub info: String,
73}
74
75#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
76/// Represents the [PDSC Repository](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_repository.html) element
77pub struct Repository {
78    #[serde(rename = "type")]
79    pub repository_type: Option<String>,
80
81    #[serde(rename = "#content")]
82    pub url: String,
83}
84
85#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
86/// Represents the [PDSC Releases](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_releases.html) element
87pub struct Releases {
88    pub release: Vec<Release>,
89}
90
91#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
92/// Represents the [PDSC Release](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_releases.html#element_release) element
93pub struct Release {
94    /// Release version string
95    pub version: String,
96    /// Release date in xs:date format (e.g. `2023-01-15`)
97    pub date: Option<String>,
98    /// VCS tag for this release
99    pub tag: Option<String>,
100    /// URL of the release archive
101    pub url: Option<String>,
102    /// Date this release was deprecated, in xs:date format (e.g. `2023-01-15`); tools should warn users when the pack is installed
103    pub deprecated: Option<String>,
104    /// Pack name (`Vendor.Name`) that replaces this deprecated release
105    pub replacement: Option<String>,
106    #[serde(rename = "#content")]
107    pub content: String,
108}
109
110#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
111/// Represents the [PDSC ChangelogsType](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_changelogs_pg.html#element_changelogs) element
112pub struct Changelogs {
113    pub changelog: Vec<Changelog>,
114}
115
116#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
117/// Represents the [PDSC Changelog](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_changelogs_pg.html#element_changelog) element
118pub struct Changelog {
119    /// Changelog identifier string, must be uniuqe within the PDSC file
120    pub id: String,
121
122    /// A path relative to the PDSC file and the filename of the changelog file
123    pub name: String,
124
125    /// If `true` this changelog is associated with all APIs and components that do not explicitly reference another changelog; xs:boolean (`"true"` / `"false"`)
126    pub default: Option<bool>,
127
128    /// File type of the changelog file; `text/plain;charset=UTF-8` is assumed if not specified
129    #[serde(rename = "type")]
130    pub changelog_type: Option<String>,
131}
132
133#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
134/// Represents the [PDSC Keywords](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_keywords.html) element
135pub struct Keywords {
136    /// The vector of keywords
137    #[serde(rename = "keyword")]
138    pub keywords: Vec<String>,
139}
140
141#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
142/// Represents the [PDSC Environments](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_components_pg.html#element_component_environments) element
143pub struct Environments {
144    /// The vector of environments
145    #[serde(rename = "environment")]
146    pub environments: Vec<Environment>,
147}
148
149#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
150/// Represents the [PDSC Environment](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#element_environment) element
151pub struct Environment {
152    pub name: String,
153    #[serde(rename = "Pname")]
154    pub processor_name: Option<String>,
155    // TODO: Handle the `anyAttribute` children
156}
157
158/// Represents [PDSC Devices](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_devices_pg.html)
159#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)]
160pub struct Devices<'a> {
161    /// Device family definitions (1..*)
162    #[serde(rename = "family", default)]
163    #[serde(borrow)]
164    pub families: Vec<Family<'a>>,
165}
166
167#[cfg(test)]
168mod tests {
169    use std::default;
170
171    use crate::pdsc::{
172        Changelogs, Devices, Eccn, License, LicenseSet, Release, Releases, Repository,
173    };
174    #[test]
175    fn parse_eccn() {
176        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
177<ECCN>
178    <ECCN-EU>NEC</ECCN-EU>
179    <ECCN-US>5D992.c</ECCN-US>
180</ECCN>"#;
181
182        let eccn: Eccn = serde_roxmltree::from_str(xml_str).unwrap();
183
184        assert_eq!(eccn.eu, "NEC".to_string());
185        assert_eq!(eccn.us, "5D992.c".to_string());
186    }
187
188    #[test]
189    /// Test that ECCN parsing fails if either the EU or US field is missing.
190    ///
191    /// As per the Open [CMSIS Pack specification](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/element_ECCN.html)
192    /// if the ECCN field is present it must contain both the US and EU ECCN entries.
193    fn parse_eccn_missing_field() {
194        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
195<ECCN>
196    <ECCN-US>5D992.c</ECCN-US>
197</ECCN>"#;
198
199        let eccn: Result<Eccn, _> = serde_roxmltree::from_str(xml_str);
200
201        assert!(eccn.is_err());
202
203        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
204<ECCN>
205    <ECCN-EU>NEC</ECCN-EU>
206</ECCN>"#;
207
208        let eccn: Result<Eccn, _> = serde_roxmltree::from_str(xml_str);
209
210        assert!(eccn.is_err());
211    }
212
213    #[test]
214    fn parse_license_set() {
215        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
216<licenseSet id="all" default="true" gating="false">
217    <license name="LICENSE.txt"
218        title="Apache License, Version 2.0"
219        spdx="Apache-2.0"
220        url="https://www.apache.org/licenses/LICENSE-2.0"/>
221</licenseSet>"#;
222
223        let license_set: LicenseSet = serde_roxmltree::from_str(xml_str).unwrap();
224
225        assert_eq!(
226            license_set,
227            LicenseSet {
228                id: "all".to_string(),
229                default: Some(true),
230                gating: Some(false),
231                license: vec![License {
232                    name: "LICENSE.txt".to_string(),
233                    title: "Apache License, Version 2.0".to_string(),
234                    spdx: Some("Apache-2.0".to_string()),
235                    url: Some("https://www.apache.org/licenses/LICENSE-2.0".to_string())
236                }]
237            }
238        );
239    }
240
241    #[test]
242    fn parse_repository() {
243        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
244<repository type="git">https://github.com/ARM-software/CMSIS-Driver.git</repository>"#;
245
246        let repo: Repository = serde_roxmltree::from_str(xml_str).unwrap();
247
248        assert_eq!(repo.repository_type, Some("git".to_string()));
249        assert_eq!(
250            repo.url,
251            "https://github.com/ARM-software/CMSIS-Driver.git".to_string()
252        );
253    }
254
255    #[test]
256    fn parse_releases() {
257        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
258<releases>
259  <release version="1.1.1" date="2020-05-12">Fixed a problem with the feature xyz.</release>
260  <release version="1.1.0" date="2020-03-13">Introduces a new feature xyz.</release>
261  <release version="1.0.0" date="2020-02-23">First published version.</release>
262</releases>
263"#;
264
265        let releases: Releases = serde_roxmltree::from_str(xml_str).unwrap();
266
267        assert_eq!(
268            releases,
269            Releases {
270                release: vec![
271                    Release {
272                        version: "1.1.1".to_string(),
273                        date: Some("2020-05-12".to_string()),
274                        content: "Fixed a problem with the feature xyz.".to_string(),
275                        ..default::Default::default()
276                    },
277                    Release {
278                        version: "1.1.0".to_string(),
279                        date: Some("2020-03-13".to_string()),
280                        content: "Introduces a new feature xyz.".to_string(),
281                        ..default::Default::default()
282                    },
283                    Release {
284                        version: "1.0.0".to_string(),
285                        date: Some("2020-02-23".to_string()),
286                        content: "First published version.".to_string(),
287                        ..default::Default::default()
288                    },
289                ]
290            }
291        );
292    }
293
294    #[test]
295    fn parse_releases_public_repo() {
296        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
297<releases>
298    <release version="2.1.0" tag="2.1.0" url="https://github.com/ARM-software/CMSIS-Driver/archive/2.1.0.zip">
299      Added LAN9220 Ethernet MAC+PHY driver.
300    </release>
301    <release version="2.0.0" tag="2.0.0" url="https://github.com/ARM-software/CMSIS-Driver/archive/2.0.0.zip">
302      First published version.
303    </release>
304</releases>"#;
305
306        let releases: Releases = serde_roxmltree::from_str(xml_str).unwrap();
307
308        assert_eq!(
309            releases,
310            Releases {
311                release: vec![
312                    Release {
313                        version: "2.1.0".to_string(),
314                        tag: Some("2.1.0".to_string()),
315                        url: Some(
316                            "https://github.com/ARM-software/CMSIS-Driver/archive/2.1.0.zip"
317                                .to_string()
318                        ),
319                        content: "\n      Added LAN9220 Ethernet MAC+PHY driver.\n    ".to_string(),
320                        ..default::Default::default()
321                    },
322                    Release {
323                        version: "2.0.0".to_string(),
324                        tag: Some("2.0.0".to_string()),
325                        url: Some(
326                            "https://github.com/ARM-software/CMSIS-Driver/archive/2.0.0.zip"
327                                .to_string()
328                        ),
329                        content: "\n      First published version.\n    ".to_string(),
330                        ..default::Default::default()
331                    },
332                ]
333            }
334        );
335    }
336
337    #[test]
338    fn parse_releases_deprecated() {
339        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
340<releases>
341  <release version="1.0.1" date="2020-04-18" deprecated="2020-04-18" replacement="Vendor.pack_name">
342  </release>
343  <release version="1.0.0" date="2020-03-24">Initial version.
344  </release>
345</releases>"#;
346
347        let releases: Releases = serde_roxmltree::from_str(xml_str).unwrap();
348
349        assert_eq!(
350            releases,
351            Releases {
352                release: vec![
353                    Release {
354                        version: "1.0.1".to_string(),
355                        date: Some("2020-04-18".to_string()),
356                        deprecated: Some("2020-04-18".to_string()),
357                        replacement: Some("Vendor.pack_name".to_string()),
358                        content: "\n  ".to_string(),
359                        ..default::Default::default()
360                    },
361                    Release {
362                        version: "1.0.0".to_string(),
363                        date: Some("2020-03-24".to_string()),
364                        content: "Initial version.\n  ".to_string(),
365                        ..default::Default::default()
366                    },
367                ]
368            }
369        );
370    }
371
372    #[test]
373    fn parse_devices_multiple_families() {
374        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
375<devices>
376    <family Dfamily="FamilyA" Dvendor="ARM:82">
377        <debugvars configfile="a.dbgconf" version="1.0.0"></debugvars>
378        <sequences/>
379    </family>
380    <family Dfamily="FamilyB" Dvendor="ARM:82">
381        <debugvars configfile="b.dbgconf" version="2.0.0"></debugvars>
382        <sequences/>
383    </family>
384</devices>"#;
385        // Devices borrows from the Document so the document must outlive the parsed value
386        let document = roxmltree::Document::parse(xml_str).unwrap();
387        let devices: Devices = serde_roxmltree::from_doc(&document).unwrap();
388        assert_eq!(devices.families.len(), 2);
389        assert_eq!(devices.families[0].device_family, "FamilyA");
390        assert_eq!(devices.families[1].device_family, "FamilyB");
391    }
392
393    #[test]
394    fn parse_changelog_default_bool() {
395        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
396<changelogs>
397    <changelog id="all" name="CHANGELOG.md" default="true"/>
398    <changelog id="other" name="OTHER.md"/>
399</changelogs>"#;
400
401        let changelogs: Changelogs = serde_roxmltree::from_str(xml_str).unwrap();
402        assert_eq!(changelogs.changelog.len(), 2);
403        assert_eq!(changelogs.changelog[0].id, "all");
404        assert_eq!(changelogs.changelog[0].name, "CHANGELOG.md");
405        assert_eq!(changelogs.changelog[0].default, Some(true));
406        assert_eq!(changelogs.changelog[1].id, "other");
407        assert_eq!(changelogs.changelog[1].default, None);
408    }
409}