winget_types/installer/
platform.rs

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