winget_types/installer/
platform.rs

1use std::{
2    fmt,
3    fmt::{Display, Formatter},
4    str::FromStr,
5};
6
7use bitflags::bitflags;
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{SeqAccess, Visitor},
11    ser::SerializeSeq,
12};
13use thiserror::Error;
14
15bitflags! {
16    /// A list of installer supported operating systems internally represented as bit flags
17    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
18    pub struct Platform: u8 {
19        const WINDOWS_DESKTOP = 1;
20        const WINDOWS_UNIVERSAL = 1 << 1;
21    }
22}
23
24const WINDOWS_DESKTOP: &str = "Windows.Desktop";
25const WINDOWS_UNIVERSAL: &str = "Windows.Universal";
26
27impl Display for Platform {
28    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29        match *self {
30            Self::WINDOWS_DESKTOP => f.write_str(WINDOWS_DESKTOP),
31            Self::WINDOWS_UNIVERSAL => f.write_str(WINDOWS_UNIVERSAL),
32            _ => bitflags::parser::to_writer(self, f),
33        }
34    }
35}
36
37#[derive(Error, Debug, Eq, PartialEq)]
38#[error("Platform did not match either `{WINDOWS_DESKTOP}` or `{WINDOWS_UNIVERSAL}`")]
39pub struct PlatformParseError;
40
41impl FromStr for Platform {
42    type Err = PlatformParseError;
43
44    fn from_str(s: &str) -> Result<Self, Self::Err> {
45        match s {
46            WINDOWS_DESKTOP => Ok(Self::WINDOWS_DESKTOP),
47            WINDOWS_UNIVERSAL => Ok(Self::WINDOWS_UNIVERSAL),
48            _ => Err(PlatformParseError),
49        }
50    }
51}
52
53impl Serialize for Platform {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: Serializer,
57    {
58        let mut seq = serializer.serialize_seq(Some(self.iter().count()))?;
59        for platform in self.iter() {
60            match platform {
61                Self::WINDOWS_DESKTOP => seq.serialize_element(WINDOWS_DESKTOP)?,
62                Self::WINDOWS_UNIVERSAL => seq.serialize_element(WINDOWS_UNIVERSAL)?,
63                _ => {}
64            }
65        }
66        seq.end()
67    }
68}
69
70impl<'de> Deserialize<'de> for Platform {
71    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72    where
73        D: Deserializer<'de>,
74    {
75        struct PlatformVisitor;
76
77        impl<'de> Visitor<'de> for PlatformVisitor {
78            type Value = Platform;
79
80            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
81                formatter.write_str("a sequence of platform strings")
82            }
83
84            fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
85            where
86                V: SeqAccess<'de>,
87            {
88                let mut platform = Platform::empty();
89
90                while let Some(value) = seq.next_element::<&str>()? {
91                    match value {
92                        WINDOWS_DESKTOP => platform |= Platform::WINDOWS_DESKTOP,
93                        WINDOWS_UNIVERSAL => platform |= Platform::WINDOWS_UNIVERSAL,
94                        _ => {
95                            return Err(serde::de::Error::unknown_variant(
96                                value,
97                                &[WINDOWS_DESKTOP, WINDOWS_UNIVERSAL],
98                            ));
99                        }
100                    }
101                }
102
103                Ok(platform)
104            }
105        }
106
107        deserializer.deserialize_seq(PlatformVisitor)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use indoc::indoc;
114    use rstest::rstest;
115
116    use crate::installer::{Platform, platform::PlatformParseError};
117
118    #[rstest]
119    #[case(
120        Platform::all(),
121        indoc! {"
122            - Windows.Desktop
123            - Windows.Universal
124        "}
125    )]
126    #[case(
127        Platform::empty(),
128        indoc! {"
129            []
130        "}
131    )]
132    #[case(
133        Platform::WINDOWS_DESKTOP,
134        indoc! {"
135            - Windows.Desktop
136        "}
137    )]
138    #[case(
139        Platform::WINDOWS_UNIVERSAL,
140        indoc! {"
141            - Windows.Universal
142        "}
143    )]
144    fn serialize_platform(#[case] platform: Platform, #[case] expected: &str) {
145        assert_eq!(serde_yaml::to_string(&platform).unwrap(), expected);
146    }
147
148    #[rstest]
149    #[case(
150        indoc! {"
151            - Windows.Desktop
152            - Windows.Universal
153        "},
154        Platform::all(),
155    )]
156    #[case(
157        indoc! {"
158            []
159        "},
160        Platform::empty()
161    )]
162    #[case(
163        indoc! {"
164            - Windows.Desktop
165        "},
166        Platform::WINDOWS_DESKTOP
167    )]
168    #[case(
169        indoc! {"
170            - Windows.Universal
171        "},
172        Platform::WINDOWS_UNIVERSAL
173    )]
174    fn deserialize_platform(#[case] input: &str, #[case] expected: Platform) {
175        assert_eq!(serde_yaml::from_str::<Platform>(input).unwrap(), expected);
176    }
177
178    #[test]
179    fn platform_serialize_ordered() {
180        let input = indoc! {"
181            - Windows.Universal
182            - Windows.Desktop
183        "};
184
185        let deserialized = serde_yaml::from_str::<Platform>(input).unwrap();
186
187        assert_eq!(
188            serde_yaml::to_string(&deserialized).unwrap(),
189            indoc! {"
190                - Windows.Desktop
191                - Windows.Universal
192            "}
193        );
194    }
195
196    #[test]
197    fn from_str() {
198        assert!("Windows.Desktop".parse::<Platform>().is_ok());
199        assert!("Windows.Universal".parse::<Platform>().is_ok());
200        assert_eq!(
201            "WindowsDesktop".parse::<Platform>().err().unwrap(),
202            PlatformParseError
203        );
204    }
205}