winget_types/shared/
package_version.rs1use core::{fmt, str::FromStr};
2
3use thiserror::Error;
4
5use super::{DISALLOWED_CHARACTERS, version::Version};
6
7#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "serde", serde(try_from = "Version"))]
10#[repr(transparent)]
11pub struct PackageVersion(Version);
12
13#[derive(Error, Debug, Eq, PartialEq)]
14pub enum PackageVersionError {
15 #[error("Package version contains invalid character {_0:?}")]
16 InvalidCharacter(char),
17 #[error("Package version cannot be empty")]
18 Empty,
19 #[error(
20 "Package version cannot be more than {} characters long",
21 PackageVersion::MAX_CHAR_LENGTH
22 )]
23 TooLong,
24}
25
26impl PackageVersion {
27 const MAX_CHAR_LENGTH: usize = 128;
28
29 pub fn new<T: AsRef<str>>(version: T) -> Result<Self, PackageVersionError> {
51 let version = version.as_ref();
52
53 if version.is_empty() {
54 return Err(PackageVersionError::Empty);
55 }
56
57 let char_count = version.chars().try_fold(0, |char_count, char| {
58 if DISALLOWED_CHARACTERS.contains(&char) || char.is_control() {
59 return Err(PackageVersionError::InvalidCharacter(char));
60 }
61
62 Ok(char_count + 1)
63 })?;
64
65 if char_count > Self::MAX_CHAR_LENGTH {
66 return Err(PackageVersionError::TooLong);
67 }
68
69 Ok(Self(Version::new(version)))
70 }
71
72 #[inline]
80 pub unsafe fn new_unchecked<T: AsRef<str>>(version: T) -> Self {
81 Self(Version::new(version))
82 }
83
84 #[must_use]
100 #[inline]
101 pub fn is_latest(&self) -> bool {
102 self.0.is_latest()
103 }
104
105 #[must_use]
107 #[inline]
108 pub fn as_str(&self) -> &str {
109 self.0.as_str()
110 }
111
112 #[must_use]
114 #[inline]
115 pub const fn inner(&self) -> &Version {
116 &self.0
117 }
118
119 #[inline]
137 pub fn closest<'iter, I>(&self, versions: I) -> Option<&'iter Self>
138 where
139 I: IntoIterator<Item = &'iter Self>,
140 {
141 self.0.closest(versions)
142 }
143}
144
145impl fmt::Display for PackageVersion {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 self.0.fmt(f)
148 }
149}
150
151impl FromStr for PackageVersion {
152 type Err = PackageVersionError;
153
154 fn from_str(s: &str) -> Result<Self, Self::Err> {
155 Self::new(s)
156 }
157}
158
159impl<'ver> From<&'ver PackageVersion> for &'ver Version {
160 #[inline]
161 fn from(value: &'ver PackageVersion) -> Self {
162 &value.0
163 }
164}
165
166impl TryFrom<Version> for PackageVersion {
167 type Error = PackageVersionError;
168
169 #[inline]
170 fn try_from(value: Version) -> Result<Self, Self::Error> {
171 Self::new(value)
172 }
173}
174
175impl PartialEq<Version> for PackageVersion {
176 fn eq(&self, other: &Version) -> bool {
177 self.0.eq(other)
178 }
179}
180
181impl PartialEq<PackageVersion> for Version {
182 fn eq(&self, other: &PackageVersion) -> bool {
183 self.eq(&other.0)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use alloc::format;
190
191 use super::{DISALLOWED_CHARACTERS, PackageVersion, PackageVersionError};
192
193 #[test]
194 fn empty_package_version() {
195 assert_eq!(PackageVersion::new(""), Err(PackageVersionError::Empty));
196 }
197
198 #[test]
199 fn disallowed_characters_in_package_version() {
200 for char in DISALLOWED_CHARACTERS {
201 assert_eq!(
202 format!("1.2{char}3").parse::<PackageVersion>(),
203 Err(PackageVersionError::InvalidCharacter(char))
204 )
205 }
206 }
207
208 #[test]
209 fn control_characters_in_package_version() {
210 assert_eq!(
211 "1.2\03".parse::<PackageVersion>(),
212 Err(PackageVersionError::InvalidCharacter('\0'))
213 );
214 }
215
216 #[test]
217 fn unicode_package_version_max_length() {
218 let version = "🦀".repeat(PackageVersion::MAX_CHAR_LENGTH);
219
220 assert!(version.len() > PackageVersion::MAX_CHAR_LENGTH);
222 assert!(version.encode_utf16().count() > PackageVersion::MAX_CHAR_LENGTH);
223 assert_eq!(version.chars().count(), PackageVersion::MAX_CHAR_LENGTH);
224 assert!(PackageVersion::new(version).is_ok());
225 }
226
227 #[test]
228 fn package_version_too_long() {
229 let version = "🦀".repeat(PackageVersion::MAX_CHAR_LENGTH + 1);
230
231 assert_eq!(
232 version.parse::<PackageVersion>(),
233 Err(PackageVersionError::TooLong)
234 );
235 }
236}