version_number/lib.rs
1#![deny(missing_docs)]
2
3//! # version-number
4//!
5//! ## Synopsis
6//!
7//! A crate to represent and parse two- and three-component version numbers of the form `major.minor`,
8//! and `major.minor.patch` respectively. These version numbers are often seen within the Rust
9//! project manifests.
10//!
11//! ## Semver compatibility
12//!
13//! The version numbers accepted by this crate are a subset of semver version numbers,
14//! with the exception of also allowing two component (shorthand) `major.minor` versions.
15//!
16//! For example, `1.0` and `1.0.0` are both accepted by this library, while the former is
17//! rejected by [`semver`].
18//!
19//! In addition [`Version`] does not accept extra labels such as build parameters, which are
20//! an extension of the [`semver`] version number itself.
21//!
22//! In this crate, we call a two component `major.minor` version number a [`BaseVersion`], and
23//! we call a three component `major.minor.patch` version number a [`FullVersion`].
24//!
25//! [`semver`]: https://semver.org/spec/v2.0.0.html
26//! [`Version`]: crate::Version
27//! [`BaseVersion`]: crate::BaseVersion
28//! [`FullVersion`]: crate::FullVersion
29
30use std::fmt;
31use std::str::FromStr;
32
33use crate::parsers::original;
34
35pub use parsers::{BaseVersionParser, FullVersionParser, ParserError, VersionParser};
36pub use version::{BaseVersion, FullVersion};
37
38/// This crate contains multiple parsers.
39///
40/// In general, it's easiest to use the well tested [`parsers::original::Parser`], which is also used
41/// (currently) by [`Version::parse`].
42pub mod parsers;
43
44mod version;
45
46/// Top level errors for version-numbers.
47#[derive(Debug, thiserror::Error)]
48pub enum Error {
49 /// An error which specifies failure to parse a version number.
50 #[error(transparent)]
51 ParserError(#[from] ParserError),
52}
53
54/// A numbered version which is a two-component `major.minor` version number,
55/// or a three-component `major.minor.patch` version number.
56#[derive(Clone, Debug, Hash, Eq, PartialEq)]
57pub enum Version {
58 /// A two-component `major.minor` version.
59 Base(BaseVersion),
60 /// A three-component `major.minor.patch` version.
61 Full(FullVersion),
62}
63
64impl Version {
65 /// Parse a two- or three-component, `major.minor` or `major.minor.patch` respectively,
66 /// version number from a given input.
67 ///
68 /// Returns a [`Error::ParserError`] if it fails to parse.
69 pub fn parse(input: &str) -> Result<Self, Error> {
70 original::Parser::from(input.as_bytes())
71 .parse()
72 .map_err(|e| Error::from(Into::<ParserError>::into(e)))
73 }
74
75 /// Create a new two-component `major.minor` version number.
76 pub fn new_base_version(major: u64, minor: u64) -> Self {
77 Self::Base(BaseVersion { major, minor })
78 }
79
80 /// Create a new three-component `major.minor.patch` version number.
81 pub fn new_full_version(major: u64, minor: u64, patch: u64) -> Self {
82 Self::Full(FullVersion {
83 major,
84 minor,
85 patch,
86 })
87 }
88
89 /// Returns the `major` version component.
90 ///
91 /// Both the two- and three-component version number variants have a major version.
92 /// This is the leading component.
93 pub fn major(&self) -> u64 {
94 match self {
95 Self::Base(inner) => inner.major,
96 Self::Full(inner) => inner.major,
97 }
98 }
99
100 /// Returns the `minor` version component.
101 ///
102 /// Both the two- and three-component version number variants have a minor version.
103 /// This is the middle component.
104 pub fn minor(&self) -> u64 {
105 match self {
106 Self::Base(inner) => inner.minor,
107 Self::Full(inner) => inner.minor,
108 }
109 }
110
111 /// Returns the `patch` version component, if any.
112 ///
113 /// A three component `major.minor.patch` version will return a `Some(<version>)`,
114 /// while a two component `major.minor` version will return `None` instead.
115 ///
116 /// If it exists, it is the last component.
117 pub fn patch(&self) -> Option<u64> {
118 match self {
119 Self::Base(_) => None,
120 Self::Full(inner) => Some(inner.patch),
121 }
122 }
123
124 /// Check of which variant `self` is.
125 pub fn is(&self, variant: Variant) -> bool {
126 match self {
127 Version::Base(_) => matches!(variant, Variant::Base),
128 Version::Full(_) => matches!(variant, Variant::Full),
129 }
130 }
131
132 /// Map a [`Version`] to `U`.
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// use version_number::{BaseVersion, FullVersion, Version};
138 ///
139 /// // 🧑🔬
140 /// fn invert_version(v: Version) -> Version {
141 /// match v {
142 /// Version::Base(base) => Version::Base(BaseVersion::new(base.minor, base.major)),
143 /// Version::Full(full) => Version::Full(FullVersion::new(full.patch, full.minor, full.major))
144 /// }
145 /// }
146 ///
147 /// let base_example = Version::Base(BaseVersion::new(1, 2));
148 /// let full_example = Version::Full(FullVersion::new(1, 2, 3));
149 ///
150 /// assert_eq!(base_example.map(invert_version), Version::Base(BaseVersion::new(2, 1)));
151 /// assert_eq!(full_example.map(invert_version), Version::Full(FullVersion::new(3, 2, 1)));
152 /// ```
153 #[inline]
154 pub fn map<U, F>(self, fun: F) -> U
155 where
156 F: FnOnce(Self) -> U,
157 {
158 fun(self)
159 }
160
161 /// Map over the `major` version component of the [`Version`].
162 ///
163 /// # Example
164 ///
165 /// ```
166 /// use version_number::{BaseVersion, FullVersion, Version};
167 ///
168 /// let base_example = Version::Base(BaseVersion::new(0, 0));
169 /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
170 ///
171 /// assert_eq!(base_example.map_major(|a| a + 1), Version::Base(BaseVersion::new(1, 0)));
172 /// assert_eq!(full_example.map_major(|a| a + 1), Version::Full(FullVersion::new(1, 0, 0)));
173 /// ```
174 #[inline]
175 pub fn map_major<F>(self, fun: F) -> Self
176 where
177 F: FnOnce(u64) -> u64,
178 {
179 self.map(|v| match v {
180 Self::Base(base) => Version::Base(BaseVersion::new(fun(base.major), base.minor)),
181 Self::Full(full) => {
182 Version::Full(FullVersion::new(fun(full.major), full.minor, full.patch))
183 }
184 })
185 }
186
187 /// Map over the `minor` version component of the [`Version`].
188 ///
189 /// # Example
190 ///
191 /// ```
192 /// use version_number::{BaseVersion, FullVersion, Version};
193 ///
194 /// let base_example = Version::Base(BaseVersion::new(0, 0));
195 /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
196 ///
197 /// assert_eq!(base_example.map_minor(|a| a + 1), Version::Base(BaseVersion::new(0, 1)));
198 /// assert_eq!(full_example.map_minor(|a| a + 1), Version::Full(FullVersion::new(0, 1, 0)));
199 /// ```
200 #[inline]
201 pub fn map_minor<F>(self, fun: F) -> Self
202 where
203 F: FnOnce(u64) -> u64,
204 {
205 self.map(|v| match v {
206 Self::Base(base) => Version::Base(BaseVersion::new(base.major, fun(base.minor))),
207 Self::Full(full) => {
208 Version::Full(FullVersion::new(full.major, fun(full.minor), full.patch))
209 }
210 })
211 }
212
213 /// Map over the `patch` version component of the [`Version`].
214 /// If no `patch` version exists (in case the [`Version`] consists of two components),
215 /// then the original version is returned.
216 ///
217 /// # Example
218 ///
219 /// ```
220 /// use version_number::{BaseVersion, FullVersion, Version};
221 ///
222 /// let base_example = Version::Base(BaseVersion::new(0, 0));
223 /// let full_example = Version::Full(FullVersion::new(0, 0, 0));
224 ///
225 /// assert_eq!(base_example.map_patch(|a| a + 1), Version::Base(BaseVersion::new(0, 0)));
226 /// assert_eq!(full_example.map_patch(|a| a + 1), Version::Full(FullVersion::new(0, 0, 1)));
227 /// ```
228 #[inline]
229 pub fn map_patch<F>(self, fun: F) -> Self
230 where
231 F: FnOnce(u64) -> u64,
232 {
233 self.map(|v| match v {
234 Self::Base(base) => Version::Base(BaseVersion::new(base.major, base.minor)),
235 Self::Full(full) => {
236 Version::Full(FullVersion::new(full.major, full.minor, fun(full.patch)))
237 }
238 })
239 }
240}
241
242impl FromStr for Version {
243 type Err = Error;
244
245 fn from_str(input: &str) -> Result<Self, Error> {
246 original::Parser::from_slice(input.as_bytes())
247 .parse()
248 .map_err(|e| Error::from(Into::<ParserError>::into(e)))
249 }
250}
251
252impl fmt::Display for Version {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 match self {
255 Self::Base(inner) => fmt::Display::fmt(&inner, f),
256 Self::Full(inner) => fmt::Display::fmt(&inner, f),
257 }
258 }
259}
260
261impl From<(u64, u64)> for Version {
262 fn from(tuple: (u64, u64)) -> Self {
263 Self::Base(BaseVersion::from(tuple))
264 }
265}
266
267impl From<(u64, u64, u64)> for Version {
268 fn from(tuple: (u64, u64, u64)) -> Self {
269 Self::Full(FullVersion::from(tuple))
270 }
271}
272
273/// Type used to indicate which variant of a [`Version`] is used.
274/// The options are [`Base`] for [`Version::Base`], and [`Full`] for [`Version::Full`].
275///
276/// [`Version`]: crate::Version
277/// [`Base`]: crate::Variant::Base
278/// [`Version::Base`]: crate::Version::Base
279/// [`Full`]: crate::Variant::Full
280/// [`Version::Full`]: crate::Version::Full
281#[derive(Copy, Clone, Debug)]
282pub enum Variant {
283 /// Indicates a [`Version::Base`] is used.
284 ///
285 /// [`Version::Base`]: crate::Version::Base
286 Base,
287 /// Indicates a [`Version::Full`] is used.
288 ///
289 /// [`Version::Full`]: crate::Version::Full
290 Full,
291}
292
293#[cfg(test)]
294mod tests {
295 use crate::{BaseVersion, FullVersion, Variant, Version};
296
297 #[test]
298 fn is_base_variant() {
299 let version = Version::Base(BaseVersion::new(0, 0));
300
301 assert!(version.is(Variant::Base));
302 assert!(!version.is(Variant::Full));
303 }
304
305 #[test]
306 fn is_full_variant() {
307 let version = Version::Full(FullVersion::new(0, 0, 0));
308
309 assert!(version.is(Variant::Full));
310 assert!(!version.is(Variant::Base));
311 }
312
313 #[test]
314 fn map() {
315 let version = Version::Base(BaseVersion::new(0, 0));
316
317 let mapped = version.map(|v| match v {
318 Version::Base(base) => Version::Full(FullVersion::new(base.major, base.minor, 999)),
319 v => v,
320 });
321
322 assert_eq!(mapped, Version::Full(FullVersion::new(0, 0, 999)));
323 }
324
325 #[yare::parameterized(
326 base = { Version::Base(BaseVersion::new(0, 0)) },
327 full = { Version::Full(FullVersion::new(0, 0, 0)) },
328 )]
329 fn map_major(version: Version) {
330 let mapped = version.map_major(|_v| 999);
331
332 assert_eq!(mapped.major(), 999);
333 }
334
335 #[yare::parameterized(
336 base = { Version::Base(BaseVersion::new(0, 0)) },
337 full = { Version::Full(FullVersion::new(0, 0, 0)) },
338 )]
339 fn map_minor(version: Version) {
340 let mapped = version.map_minor(|_v| 999);
341
342 assert_eq!(mapped.minor(), 999);
343 }
344
345 #[test]
346 fn map_patch_base() {
347 let version = Version::Base(BaseVersion::new(0, 0));
348 let mapped = version.map_patch(|_v| 999);
349
350 assert!(mapped.patch().is_none());
351 }
352
353 #[test]
354 fn map_patch_full() {
355 let version = Version::Full(FullVersion::new(0, 0, 0));
356 let mapped = version.map_patch(|_v| 999);
357
358 assert_eq!(mapped.patch().unwrap(), 999);
359 }
360}