arch_pkg_text/value/
version.rs

1use super::{
2    Epoch, Release, UpstreamVersion, ValidUpstreamVersion, ValidateUpstreamVersionError, Version,
3};
4use core::num::ParseIntError;
5use derive_more::{Display, Error};
6
7/// Result of [`Version::parse`].
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct ParsedVersion<'a> {
10    epoch: Option<u64>,
11    upstream: ValidUpstreamVersion<'a>,
12    release: u64,
13}
14
15impl<'a> ParsedVersion<'a> {
16    /// Construct a parsed version.
17    pub fn new(epoch: Option<u64>, upstream: ValidUpstreamVersion<'a>, release: u64) -> Self {
18        ParsedVersion {
19            epoch,
20            upstream,
21            release,
22        }
23    }
24
25    /// Extract the epoch, upstream version, and release respectively.
26    pub fn components(&self) -> (Option<u64>, ValidUpstreamVersion<'a>, u64) {
27        let ParsedVersion {
28            epoch,
29            upstream,
30            release,
31        } = *self;
32        (epoch, upstream, release)
33    }
34}
35
36/// Error type of [`Version::components`].
37#[derive(Debug, Display, Clone, Copy, Error)]
38pub enum SplitVersionError {
39    #[display("Release suffix not found")]
40    MissingRelease,
41}
42
43/// Error type of [`Version::parse`].
44#[derive(Debug, Display, Clone, Error)]
45pub enum ParseVersionError<'a> {
46    #[display("Failed to split components: {_0}")]
47    InvalidComponents(SplitVersionError),
48    #[display("Invalid epoch: {_0}")]
49    InvalidEpoch(ParseIntError),
50    #[display("Invalid upstream version: {_0}")]
51    InvalidUpstream(#[error(not(source))] ValidateUpstreamVersionError<'a>),
52    #[display("Invalid release: {_0}")]
53    InvalidRelease(ParseIntError),
54}
55
56/// Result of [`Version::components`].
57type Components<'a> = (Option<Epoch<'a>>, UpstreamVersion<'a>, Release<'a>);
58
59impl<'a> Version<'a> {
60    /// Exact the epoch, upstream version, and release respectively as containers of raw strings.
61    ///
62    /// This function only splits the internal string into its components, it does not validate the components.
63    /// Use [`Version::parse`] to actually validate them.
64    ///
65    /// A valid version usually contains an epoch, an upstream version, and a release suffix:
66    ///
67    /// ```
68    /// # use arch_pkg_text::value::{Epoch, Version};
69    /// # use pretty_assertions::assert_eq;
70    /// let (epoch, upstream, release) = Version("2:0.1.2_rc.1-1").components().unwrap();
71    /// let epoch = epoch.as_ref().map(Epoch::as_str);
72    /// let upstream = upstream.as_str();
73    /// let release = release.as_str();
74    /// assert_eq!((epoch, upstream, release), (Some("2"), "0.1.2_rc.1", "1"));
75    /// ```
76    ///
77    /// Epoch is optional:
78    ///
79    /// ```
80    /// # use arch_pkg_text::value::{Epoch, Version};
81    /// # use pretty_assertions::assert_eq;
82    /// let (epoch, upstream, release) = Version("0.1.2_rc.1-1").components().unwrap();
83    /// let epoch = epoch.as_ref().map(Epoch::as_str);
84    /// let upstream = upstream.as_str();
85    /// let release = release.as_str();
86    /// assert_eq!((epoch, upstream, release), (None, "0.1.2_rc.1", "1"));
87    /// ```
88    ///
89    /// Release is mandatory:
90    ///
91    /// ```
92    /// # use arch_pkg_text::value::{SplitVersionError, Version};
93    /// let result = Version("2:0.1.2_rc.1").components();
94    /// # dbg!(&result);
95    /// assert!(matches!(result, Err(SplitVersionError::MissingRelease)));
96    /// ```
97    pub fn components(&self) -> Result<Components<'a>, SplitVersionError> {
98        let (epoch, rest) = match self.split_once(':') {
99            Some((epoch, rest)) => (Some(epoch), rest),
100            None => (None, self.as_str()),
101        };
102        let (upstream, release) = rest
103            .rsplit_once('-')
104            .ok_or(SplitVersionError::MissingRelease)?;
105        Ok((
106            epoch.map(Epoch),
107            UpstreamVersion(upstream),
108            Release(release),
109        ))
110    }
111
112    /// Parse and validate the version.
113    ///
114    /// A valid version usually contains an epoch, an upstream version, and a release suffix:
115    ///
116    /// ```
117    /// # use arch_pkg_text::value::Version;
118    /// # use pretty_assertions::assert_eq;
119    /// let (epoch, upstream, release) = Version("2:0.1.2_rc.1-1").parse().unwrap().components();
120    /// assert_eq!(
121    ///     (epoch, upstream.as_str(), release),
122    ///     (Some(2), "0.1.2_rc.1", 1)
123    /// );
124    /// ```
125    ///
126    /// Epoch is optional:
127    ///
128    /// ```
129    /// # use arch_pkg_text::value::Version;
130    /// # use pretty_assertions::assert_eq;
131    /// let (epoch, upstream, release) = Version("0.1.2_rc.1-1").parse().unwrap().components();
132    /// assert_eq!(
133    ///     (epoch, upstream.as_str(), release),
134    ///     (None, "0.1.2_rc.1", 1)
135    /// );
136    /// ```
137    ///
138    /// Release is mandatory:
139    ///
140    /// ```
141    /// # use arch_pkg_text::value::{ParseVersionError, SplitVersionError, Version};
142    /// let result = Version("2:0.1.2_rc.1").parse();
143    /// # dbg!(&result);
144    /// assert!(matches!(
145    ///     result,
146    ///     Err(ParseVersionError::InvalidComponents(SplitVersionError::MissingRelease))),
147    /// );
148    /// ```
149    ///
150    /// Epoch must be a valid integer:
151    ///
152    /// ```
153    /// # use arch_pkg_text::value::{ParseVersionError, Version};
154    /// let result = Version("2.1:0.1.2_rc.1-1").parse();
155    /// # dbg!(&result);
156    /// assert!(matches!(
157    ///     result,
158    ///     Err(ParseVersionError::InvalidEpoch(_))),
159    /// );
160    /// ```
161    ///
162    /// Upstream version must be valid:
163    ///
164    /// ```
165    /// # use arch_pkg_text::value::{ParseVersionError, Version};
166    /// let result = Version("2:0.1.2-rc.1-1").parse();
167    /// # dbg!(&result);
168    /// assert!(matches!(
169    ///     result,
170    ///     Err(ParseVersionError::InvalidUpstream(_))),
171    /// );
172    /// ```
173    ///
174    /// Release must be a valid integer:
175    ///
176    /// ```
177    /// # use arch_pkg_text::value::{ParseVersionError, Version};
178    /// let result = Version("2:0.1.2_rc.1-a").parse();
179    /// # dbg!(&result);
180    /// assert!(matches!(
181    ///     result,
182    ///     Err(ParseVersionError::InvalidRelease(_))),
183    /// );
184    /// ```
185    pub fn parse(&self) -> Result<ParsedVersion<'a>, ParseVersionError<'a>> {
186        let (epoch, upstream, release) = self
187            .components()
188            .map_err(ParseVersionError::InvalidComponents)?;
189        let epoch = epoch
190            .as_ref()
191            .map(Epoch::parse)
192            .transpose()
193            .map_err(ParseVersionError::InvalidEpoch)?;
194        let upstream = upstream
195            .validate()
196            .map_err(ParseVersionError::InvalidUpstream)?;
197        let release = release.parse().map_err(ParseVersionError::InvalidRelease)?;
198        Ok(ParsedVersion::new(epoch, upstream, release))
199    }
200}
201
202impl<'a> TryFrom<Version<'a>> for ParsedVersion<'a> {
203    type Error = ParseVersionError<'a>;
204    fn try_from(version: Version<'a>) -> Result<Self, Self::Error> {
205        version.parse()
206    }
207}