1use core::str::FromStr;
2use serde::{Deserialize, Serialize};
3
4use super::engine::EngineKind;
5use crate::shared::DeploymentKind;
6use crate::shared::ModelError;
7use crate::shared::validation::{Validate, ensure_hash, ensure_http_url};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Architecture {
13 X64,
15 X86,
17 Arm64,
19 Any,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum InstallerType {
27 Msi,
29 Msix,
31 Appx,
33 Exe,
35 Inno,
37 Nullsoft,
39 Wix,
41 Burn,
43 Pwa,
45 Font,
47 Portable,
49 Zip,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Installer {
56 pub url: String,
58 pub hash: String,
60 pub architecture: Architecture,
62 pub kind: InstallerType,
64}
65
66impl Installer {
67 pub fn validate(&self) -> Result<(), ModelError> {
69 ensure_http_url("installer.url", &self.url)?;
70 ensure_hash("installer.hash", &self.hash)
71 }
72}
73
74impl Validate for Installer {
75 fn validate(&self) -> Result<(), ModelError> {
76 Installer::validate(self)
77 }
78}
79
80impl Architecture {
81 pub fn as_str(self) -> &'static str {
83 match self {
84 Self::X64 => "x64",
85 Self::X86 => "x86",
86 Self::Arm64 => "arm64",
87 Self::Any => "",
88 }
89 }
90
91 pub fn current() -> Self {
93 match std::env::consts::ARCH {
94 "x86_64" => Self::X64,
95 "x86" => Self::X86,
96 "aarch64" => Self::Arm64,
97 _ => Self::Any,
98 }
99 }
100}
101
102impl FromStr for Architecture {
103 type Err = ModelError;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 match s.trim().to_ascii_lowercase().as_str() {
107 "x64" => Ok(Self::X64),
108 "x86" => Ok(Self::X86),
109 "arm64" => Ok(Self::Arm64),
110 "" => Ok(Self::Any),
111 other => Err(ModelError::invalid_enum_value("installer.arch", other)),
112 }
113 }
114}
115
116impl InstallerType {
117 pub fn as_str(self) -> &'static str {
119 match self {
120 Self::Msi => "msi",
121 Self::Msix => "msix",
122 Self::Appx => "appx",
123 Self::Exe => "exe",
124 Self::Inno => "inno",
125 Self::Nullsoft => "nullsoft",
126 Self::Wix => "wix",
127 Self::Burn => "burn",
128 Self::Pwa => "pwa",
129 Self::Font => "font",
130 Self::Portable => "portable",
131 Self::Zip => "zip",
132 }
133 }
134
135 pub fn deployment_kind(self) -> DeploymentKind {
137 self.into()
138 }
139
140 pub fn is_windows_package(self) -> bool {
142 matches!(self, Self::Msix | Self::Appx)
143 }
144
145 pub fn is_msi_family(self) -> bool {
147 matches!(self, Self::Msi | Self::Wix)
148 }
149
150 pub fn is_native_exe_family(self) -> bool {
152 matches!(self, Self::Exe | Self::Inno | Self::Nullsoft | Self::Burn)
153 }
154
155 pub fn is_font_family(self) -> bool {
157 matches!(self, Self::Font)
158 }
159
160 pub fn is_special_case(self) -> bool {
162 matches!(self, Self::Pwa)
163 }
164
165 pub fn is_archive(self) -> bool {
167 matches!(self, Self::Zip)
168 }
169}
170
171impl FromStr for InstallerType {
172 type Err = ModelError;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 match s.trim().to_ascii_lowercase().as_str() {
176 "msi" => Ok(Self::Msi),
177 "msix" => Ok(Self::Msix),
178 "appx" => Ok(Self::Appx),
179 "exe" => Ok(Self::Exe),
180 "inno" => Ok(Self::Inno),
181 "nullsoft" => Ok(Self::Nullsoft),
182 "wix" => Ok(Self::Wix),
183 "burn" => Ok(Self::Burn),
184 "pwa" => Ok(Self::Pwa),
185 "font" => Ok(Self::Font),
186 "portable" => Ok(Self::Portable),
187 "zip" => Ok(Self::Zip),
188 other => Err(ModelError::invalid_enum_value("installer.kind", other)),
189 }
190 }
191}
192
193impl core::fmt::Display for Architecture {
194 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
195 f.write_str(self.as_str())
196 }
197}
198
199impl From<Architecture> for String {
200 fn from(value: Architecture) -> Self {
201 value.to_string()
202 }
203}
204
205impl core::fmt::Display for InstallerType {
206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207 f.write_str(self.as_str())
208 }
209}
210
211impl From<InstallerType> for String {
212 fn from(value: InstallerType) -> Self {
213 value.to_string()
214 }
215}
216
217impl From<EngineKind> for InstallerType {
218 fn from(value: EngineKind) -> Self {
219 match value {
220 EngineKind::Msix => Self::Msix,
221 EngineKind::Zip => Self::Zip,
222 EngineKind::Portable => Self::Portable,
223 EngineKind::Msi => Self::Msi,
224 EngineKind::NativeExe => Self::Exe,
225 EngineKind::Font => Self::Font,
226 }
227 }
228}
229
230impl From<InstallerType> for DeploymentKind {
231 fn from(value: InstallerType) -> Self {
232 match value {
233 InstallerType::Portable | InstallerType::Zip => Self::Portable,
234 _ => Self::Installed,
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use crate::shared::DeploymentKind;
242
243 use super::InstallerType;
244 use core::str::FromStr;
245
246 #[test]
247 fn installer_type_parses_official_winget_values() {
248 assert_eq!(
249 InstallerType::from_str("appx").expect("appx"),
250 InstallerType::Appx
251 );
252 assert_eq!(
253 InstallerType::from_str("inno").expect("inno"),
254 InstallerType::Inno
255 );
256 assert_eq!(
257 InstallerType::from_str("nullsoft").expect("nullsoft"),
258 InstallerType::Nullsoft
259 );
260 assert_eq!(
261 InstallerType::from_str("wix").expect("wix"),
262 InstallerType::Wix
263 );
264 assert_eq!(
265 InstallerType::from_str("burn").expect("burn"),
266 InstallerType::Burn
267 );
268 assert_eq!(
269 InstallerType::from_str("pwa").expect("pwa"),
270 InstallerType::Pwa
271 );
272 assert_eq!(
273 InstallerType::from_str("font").expect("font"),
274 InstallerType::Font
275 );
276 }
277
278 #[test]
279 fn installer_type_classifies_deployment_kind() {
280 assert_eq!(
281 InstallerType::Portable.deployment_kind(),
282 DeploymentKind::Portable
283 );
284 assert_eq!(
285 InstallerType::Zip.deployment_kind(),
286 DeploymentKind::Portable
287 );
288 assert_eq!(
289 InstallerType::Msi.deployment_kind(),
290 DeploymentKind::Installed
291 );
292 assert_eq!(
293 InstallerType::Exe.deployment_kind(),
294 DeploymentKind::Installed
295 );
296 assert_eq!(
297 InstallerType::Inno.deployment_kind(),
298 DeploymentKind::Installed
299 );
300 assert_eq!(
301 InstallerType::Nullsoft.deployment_kind(),
302 DeploymentKind::Installed
303 );
304 assert_eq!(
305 InstallerType::Burn.deployment_kind(),
306 DeploymentKind::Installed
307 );
308 assert!(InstallerType::Msix.is_windows_package());
309 assert!(InstallerType::Wix.is_msi_family());
310 assert!(InstallerType::Exe.is_native_exe_family());
311 assert!(InstallerType::Inno.is_native_exe_family());
312 assert!(InstallerType::Nullsoft.is_native_exe_family());
313 assert!(InstallerType::Burn.is_native_exe_family());
314 assert!(InstallerType::Font.is_font_family());
315 assert!(!InstallerType::Font.is_native_exe_family());
316 assert!(!InstallerType::Font.is_special_case());
317 assert!(InstallerType::Pwa.is_special_case());
318 }
319}