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}