1use std::cmp::Ordering;
49use std::error::Error;
50use std::fmt::{Display, Formatter, Result as FmtResult};
51use std::slice::Iter;
52use std::str::FromStr;
53use std::vec::IntoIter as VecIntoIter;
54
55use anyhow::Error as AnyError;
56use serde_derive::{Deserialize, Serialize};
57
58use crate::expr::parser;
59
60#[derive(Debug)]
62#[non_exhaustive]
63pub enum ParseError {
64 ParseFailure(String, AnyError),
66
67 ParseLeftovers(String, usize),
69}
70
71impl Display for ParseError {
72 #[inline]
73 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
74 match *self {
75 Self::ParseFailure(ref value, _) => {
76 write!(f, "Could not parse '{value}' as a version string")
77 }
78 Self::ParseLeftovers(ref value, ref count) => write!(
79 f,
80 "Could not parse '{value}' as a version string: {count} bytes left over"
81 ),
82 }
83 }
84}
85
86impl Error for ParseError {
87 #[inline]
88 fn source(&self) -> Option<&(dyn Error + 'static)> {
89 match *self {
90 Self::ParseFailure(_, ref err) => Some(err.as_ref()),
91 Self::ParseLeftovers(_, _) => None,
92 }
93 }
94}
95
96#[derive(Debug, Clone, Eq, PartialEq)]
98#[non_exhaustive]
99#[expect(
100 clippy::module_name_repetitions,
101 reason = "sensible name for the struct"
102)]
103pub struct VersionComponent {
104 pub num: Option<u32>,
106 pub rest: String,
108}
109
110fn compare_single(left: &VersionComponent, right: &VersionComponent) -> Ordering {
112 left.num.map_or_else(
113 || {
114 if right.num.is_some() {
115 Ordering::Less
116 } else {
117 left.rest.cmp(&right.rest)
118 }
119 },
120 |ver_left| {
121 right.num.map_or(Ordering::Greater, |ver_right| {
122 let res = ver_left.cmp(&ver_right);
123 if res == Ordering::Equal {
124 left.rest.cmp(&right.rest)
125 } else {
126 res
127 }
128 })
129 },
130 )
131}
132
133fn compare_components(left: &[VersionComponent], right: &[VersionComponent]) -> Ordering {
135 left.split_first().map_or_else(
136 || {
137 right.first().map_or(Ordering::Equal, |ver_right| {
138 if ver_right.num.is_some() {
139 Ordering::Less
140 } else {
141 Ordering::Greater
142 }
143 })
144 },
145 |(comp_left, rest_left)| {
146 right.split_first().map_or_else(
147 || {
148 if comp_left.num.is_some() {
149 Ordering::Greater
150 } else {
151 Ordering::Less
152 }
153 },
154 |(comp_right, rest_right)| {
155 let res = compare_single(comp_left, comp_right);
156 if res == Ordering::Equal {
157 compare_components(rest_left, rest_right)
158 } else {
159 res
160 }
161 },
162 )
163 },
164 )
165}
166
167#[derive(Debug, Clone, Deserialize, Serialize)]
169#[serde(transparent)]
170pub struct Version {
171 value: String,
173 #[serde(skip)]
175 components: Vec<VersionComponent>,
176}
177
178impl Version {
179 #[inline]
181 #[must_use]
182 pub const fn new(value: String, components: Vec<VersionComponent>) -> Self {
183 Self { value, components }
184 }
185
186 #[inline]
188 pub fn iter(&self) -> Iter<'_, VersionComponent> {
189 self.components.iter()
190 }
191}
192
193impl FromStr for Version {
194 type Err = ParseError;
195
196 #[inline]
197 fn from_str(value: &str) -> Result<Self, Self::Err> {
198 parser::parse_version(value)
199 }
200}
201
202impl AsRef<str> for Version {
203 #[inline]
204 fn as_ref(&self) -> &str {
205 &self.value
206 }
207}
208
209impl Display for Version {
210 #[inline]
211 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
212 write!(f, "{}", self.as_ref())
213 }
214}
215
216impl PartialEq for Version {
217 #[inline]
218 fn eq(&self, other: &Self) -> bool {
219 self.cmp(other) == Ordering::Equal
220 }
221}
222
223impl Eq for Version {}
224
225impl PartialOrd for Version {
226 #[inline]
227 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
228 Some(self.cmp(other))
229 }
230}
231
232impl Ord for Version {
233 #[inline]
234 fn cmp(&self, other: &Self) -> Ordering {
235 compare_components(&self.components, &other.components)
236 }
237}
238
239impl IntoIterator for Version {
240 type Item = VersionComponent;
241 type IntoIter = VecIntoIter<Self::Item>;
242
243 #[inline]
244 fn into_iter(self) -> Self::IntoIter {
245 self.components.into_iter()
246 }
247}
248
249impl<'data> IntoIterator for &'data Version {
250 type Item = &'data VersionComponent;
251 type IntoIter = Iter<'data, VersionComponent>;
252
253 #[inline]
254 fn into_iter(self) -> Self::IntoIter {
255 self.components.iter()
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 #![expect(clippy::panic_in_result_fn, reason = "this is a test suite")]
262
263 use std::error::Error;
264
265 #[test]
266 fn num_only() -> Result<(), Box<dyn Error>> {
267 let expected: [super::VersionComponent; 1] = [super::VersionComponent {
268 num: Some(616),
269 rest: String::new(),
270 }];
271 let ver: super::Version = "616".parse()?;
272
273 let components = ver.into_iter().collect::<Vec<_>>();
274 assert_eq!(&expected[..], &*components);
275 Ok(())
276 }
277
278 #[test]
279 fn rest_only() -> Result<(), Box<dyn Error>> {
280 let expected: [super::VersionComponent; 1] = [super::VersionComponent {
281 num: None,
282 rest: "whee".to_owned(),
283 }];
284 let ver: super::Version = "whee".parse()?;
285
286 let components = ver.into_iter().collect::<Vec<_>>();
287 assert_eq!(&expected[..], &*components);
288 Ok(())
289 }
290
291 #[test]
292 fn both() -> Result<(), Box<dyn Error>> {
293 let expected: [super::VersionComponent; 1] = [super::VersionComponent {
294 num: Some(29),
295 rest: "palms".to_owned(),
296 }];
297 let ver: super::Version = "29palms".parse()?;
298
299 let components = ver.into_iter().collect::<Vec<_>>();
300 assert_eq!(&expected[..], &*components);
301 Ok(())
302 }
303
304 #[test]
305 fn three() -> Result<(), Box<dyn Error>> {
306 let expected: [super::VersionComponent; 3] = [
307 super::VersionComponent {
308 num: Some(1),
309 rest: String::new(),
310 },
311 super::VersionComponent {
312 num: Some(5),
313 rest: "a".to_owned(),
314 },
315 super::VersionComponent {
316 num: None,
317 rest: "beta3".to_owned(),
318 },
319 ];
320 let ver: super::Version = "1.5a.beta3".parse()?;
322
323 let components = ver.into_iter().collect::<Vec<_>>();
324 assert_eq!(&expected[..], &*components);
325 Ok(())
326 }
327}