winbrew_models\package/
model.rs1use core::str::FromStr;
10use serde::{Deserialize, Serialize};
11
12use crate::install::Installer;
13use crate::shared::validation::{Validate, ensure_non_empty};
14use crate::shared::{ModelError, Version};
15
16use super::dependency::Dependency;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum PackageSource {
22 Winget,
24 Scoop,
26 Chocolatey,
28 Winbrew,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum PackageKind {
36 Catalog,
38 Installed,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Package {
45 pub id: String,
47 pub name: String,
49 pub version: Version,
51 pub source: PackageSource,
53 pub kind: PackageKind,
55 pub description: Option<String>,
57 pub homepage: Option<String>,
59 pub license: Option<String>,
61 pub publisher: Option<String>,
63 pub installers: Vec<Installer>,
65 pub dependencies: Vec<Dependency>,
67}
68
69impl Package {
70 pub fn validate(&self) -> Result<(), ModelError> {
72 ensure_non_empty("package.id", &self.id)?;
73 ensure_non_empty("package.name", &self.name)?;
74 self.version.validate()?;
75
76 for installer in &self.installers {
77 installer.validate()?;
78 }
79
80 for dependency in &self.dependencies {
81 dependency.validate()?;
82 }
83
84 Ok(())
85 }
86}
87
88impl PackageSource {
89 pub fn as_str(self) -> &'static str {
90 match self {
91 Self::Winget => "winget",
92 Self::Scoop => "scoop",
93 Self::Chocolatey => "chocolatey",
94 Self::Winbrew => "winbrew",
95 }
96 }
97}
98
99impl FromStr for PackageSource {
100 type Err = ModelError;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 match s.trim().to_ascii_lowercase().as_str() {
104 "winget" => Ok(Self::Winget),
105 "scoop" => Ok(Self::Scoop),
106 "chocolatey" => Ok(Self::Chocolatey),
107 "winbrew" => Ok(Self::Winbrew),
108 other => Err(ModelError::invalid_enum_value("package.source", other)),
109 }
110 }
111}
112
113impl core::fmt::Display for PackageSource {
114 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115 f.write_str(self.as_str())
116 }
117}
118
119impl From<PackageSource> for String {
120 fn from(value: PackageSource) -> Self {
121 value.to_string()
122 }
123}
124
125impl AsRef<str> for PackageSource {
126 fn as_ref(&self) -> &str {
127 self.as_str()
128 }
129}
130
131impl PackageKind {
132 pub fn as_str(self) -> &'static str {
133 match self {
134 Self::Catalog => "catalog",
135 Self::Installed => "installed",
136 }
137 }
138}
139
140impl FromStr for PackageKind {
141 type Err = ModelError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 match s.trim().to_ascii_lowercase().as_str() {
145 "catalog" => Ok(Self::Catalog),
146 "installed" => Ok(Self::Installed),
147 other => Err(ModelError::invalid_enum_value("package.kind", other)),
148 }
149 }
150}
151
152impl core::fmt::Display for PackageKind {
153 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154 f.write_str(self.as_str())
155 }
156}
157
158impl From<PackageKind> for String {
159 fn from(value: PackageKind) -> Self {
160 value.to_string()
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::{Package, PackageKind, PackageSource};
167 use crate::install::{Architecture, Installer, InstallerType};
168 use crate::shared::Version;
169 use core::str::FromStr;
170
171 #[test]
172 fn validates_package() {
173 let package = Package {
174 id: "winget/Contoso.App".to_string(),
175 name: "Contoso App".to_string(),
176 version: Version::parse("1.2.3").expect("version should parse"),
177 source: PackageSource::Winget,
178 kind: PackageKind::Catalog,
179 description: None,
180 homepage: None,
181 license: None,
182 publisher: None,
183 installers: vec![Installer {
184 url: "https://example.test/app.exe".to_string(),
185 hash: "sha256:deadbeef".to_string(),
186 architecture: Architecture::X64,
187 kind: InstallerType::Exe,
188 }],
189 dependencies: vec![],
190 };
191
192 assert!(package.validate().is_ok());
193 }
194
195 #[test]
196 fn parses_package_source_and_kind() {
197 assert_eq!(
198 PackageSource::from_str("winget").unwrap(),
199 PackageSource::Winget
200 );
201 assert_eq!(
202 PackageSource::from_str("scoop").unwrap(),
203 PackageSource::Scoop
204 );
205 assert_eq!(
206 PackageSource::from_str("chocolatey").unwrap(),
207 PackageSource::Chocolatey
208 );
209 assert_eq!(
210 PackageSource::from_str("winbrew").unwrap(),
211 PackageSource::Winbrew
212 );
213 assert_eq!(
214 PackageKind::from_str("catalog").unwrap(),
215 PackageKind::Catalog
216 );
217 assert_eq!(
218 PackageKind::from_str("installed").unwrap(),
219 PackageKind::Installed
220 );
221 }
222}