1#![deny(missing_docs)]
2
3use std::cmp::Ordering;
31use std::fmt;
32use std::str::FromStr;
33
34use crate::parsers::original;
35
36pub use parsers::{BaseVersionParser, FullVersionParser, ParserError, VersionParser};
37#[cfg(feature = "semver")]
38pub use version::FromSemverError;
39pub use version::{BaseVersion, FullVersion};
40
41pub mod parsers;
46
47#[cfg(feature = "serde")]
48mod serde_impl;
49mod version;
50
51#[derive(Debug, thiserror::Error)]
53pub enum Error {
54 #[error(transparent)]
56 ParserError(#[from] ParserError),
57}
58
59#[derive(Clone, Debug, Hash, Eq, PartialEq)]
62pub enum Version {
63 Base(BaseVersion),
65 Full(FullVersion),
67}
68
69impl Version {
70 pub fn parse(input: &str) -> Result<Self, Error> {
75 original::Parser::from(input.as_bytes())
76 .parse()
77 .map_err(|e| Error::from(Into::<ParserError>::into(e)))
78 }
79
80 pub const fn new_base_version(major: u64, minor: u64) -> Self {
82 Self::Base(BaseVersion { major, minor })
83 }
84
85 pub const fn new_full_version(major: u64, minor: u64, patch: u64) -> Self {
87 Self::Full(FullVersion {
88 major,
89 minor,
90 patch,
91 })
92 }
93
94 pub fn major(&self) -> u64 {
99 match self {
100 Self::Base(inner) => inner.major,
101 Self::Full(inner) => inner.major,
102 }
103 }
104
105 pub fn minor(&self) -> u64 {
110 match self {
111 Self::Base(inner) => inner.minor,
112 Self::Full(inner) => inner.minor,
113 }
114 }
115
116 pub fn patch(&self) -> Option<u64> {
123 match self {
124 Self::Base(_) => None,
125 Self::Full(inner) => Some(inner.patch),
126 }
127 }
128
129 pub fn to_full_version_lossy(&self) -> FullVersion {
133 match self {
134 Self::Base(inner) => inner.to_full_version_lossy(),
135 Self::Full(inner) => *inner,
136 }
137 }
138
139 pub fn is_compatible_with(&self, other: &FullVersion) -> Ordering {
160 match self {
161 Self::Base(inner) => inner.cmp(&other.to_base_version_lossy()),
162 Self::Full(inner) => inner.cmp(other),
163 }
164 }
165
166 pub fn matches(&self, other: &FullVersion) -> bool {
182 self.is_compatible_with(other) == Ordering::Equal
183 }
184
185 pub fn is(&self, variant: Variant) -> bool {
187 match self {
188 Version::Base(_) => matches!(variant, Variant::Base),
189 Version::Full(_) => matches!(variant, Variant::Full),
190 }
191 }
192
193 #[inline]
215 pub fn map<U, F>(self, fun: F) -> U
216 where
217 F: FnOnce(Self) -> U,
218 {
219 fun(self)
220 }
221
222 #[inline]
236 pub fn map_major<F>(self, fun: F) -> Self
237 where
238 F: FnOnce(u64) -> u64,
239 {
240 self.map(|v| match v {
241 Self::Base(base) => Version::Base(BaseVersion::new(fun(base.major), base.minor)),
242 Self::Full(full) => {
243 Version::Full(FullVersion::new(fun(full.major), full.minor, full.patch))
244 }
245 })
246 }
247
248 #[inline]
262 pub fn map_minor<F>(self, fun: F) -> Self
263 where
264 F: FnOnce(u64) -> u64,
265 {
266 self.map(|v| match v {
267 Self::Base(base) => Version::Base(BaseVersion::new(base.major, fun(base.minor))),
268 Self::Full(full) => {
269 Version::Full(FullVersion::new(full.major, fun(full.minor), full.patch))
270 }
271 })
272 }
273
274 #[inline]
290 pub fn map_patch<F>(self, fun: F) -> Self
291 where
292 F: FnOnce(u64) -> u64,
293 {
294 self.map(|v| match v {
295 Self::Base(base) => Version::Base(BaseVersion::new(base.major, base.minor)),
296 Self::Full(full) => {
297 Version::Full(FullVersion::new(full.major, full.minor, fun(full.patch)))
298 }
299 })
300 }
301}
302
303impl FromStr for Version {
304 type Err = Error;
305
306 fn from_str(input: &str) -> Result<Self, Error> {
307 original::Parser::from_slice(input.as_bytes())
308 .parse()
309 .map_err(|e| Error::from(Into::<ParserError>::into(e)))
310 }
311}
312
313impl fmt::Display for Version {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 match self {
316 Self::Base(inner) => fmt::Display::fmt(&inner, f),
317 Self::Full(inner) => fmt::Display::fmt(&inner, f),
318 }
319 }
320}
321
322impl From<BaseVersion> for Version {
323 fn from(version: BaseVersion) -> Self {
324 Self::Base(version)
325 }
326}
327
328impl From<FullVersion> for Version {
329 fn from(version: FullVersion) -> Self {
330 Self::Full(version)
331 }
332}
333
334impl From<(u64, u64)> for Version {
335 fn from(tuple: (u64, u64)) -> Self {
336 Self::Base(BaseVersion::from(tuple))
337 }
338}
339
340impl From<(u64, u64, u64)> for Version {
341 fn from(tuple: (u64, u64, u64)) -> Self {
342 Self::Full(FullVersion::from(tuple))
343 }
344}
345
346#[derive(Copy, Clone, Debug)]
355pub enum Variant {
356 Base,
360 Full,
364}
365
366#[cfg(test)]
367mod tests {
368 use crate::{BaseVersion, FullVersion, Variant, Version};
369 use std::cmp::Ordering;
370
371 #[yare::parameterized(
372 base = { Version::new_base_version(1, 2), FullVersion::new(1, 2, 0) },
373 full = { Version::new_full_version(1, 2, 3), FullVersion::new(1, 2, 3) },
374 )]
375 fn to_full_version_lossy(version: Version, expected: FullVersion) {
376 assert_eq!(version.to_full_version_lossy(), expected);
377 }
378
379 #[yare::parameterized(
380 base_eq_patch_zero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 0), Ordering::Equal },
381 base_eq_patch_nonzero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 99), Ordering::Equal },
382 base_lt_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 57, 0), Ordering::Less },
383 base_lt_major = { Version::new_base_version(1, 56), FullVersion::new(2, 0, 0), Ordering::Less },
384 base_gt_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 55, 99), Ordering::Greater },
385 base_gt_major = { Version::new_base_version(2, 0), FullVersion::new(1, 99, 99), Ordering::Greater },
386 full_eq = { Version::new_full_version(1, 56, 1), FullVersion::new(1, 56, 1), Ordering::Equal },
387 full_lt_patch = { Version::new_full_version(1, 56, 0), FullVersion::new(1, 56, 1), Ordering::Less },
388 full_lt_minor = { Version::new_full_version(1, 56, 9), FullVersion::new(1, 57, 0), Ordering::Less },
389 full_lt_major = { Version::new_full_version(1, 99, 9), FullVersion::new(2, 0, 0), Ordering::Less },
390 full_gt_patch = { Version::new_full_version(1, 56, 1), FullVersion::new(1, 56, 0), Ordering::Greater },
391 full_gt_minor = { Version::new_full_version(1, 57, 0), FullVersion::new(1, 56, 9), Ordering::Greater },
392 full_gt_major = { Version::new_full_version(2, 0, 0), FullVersion::new(1, 99, 9), Ordering::Greater },
393 )]
394 fn is_compatible_with(version: Version, other: FullVersion, expected: Ordering) {
395 assert_eq!(version.is_compatible_with(&other), expected);
396 }
397
398 #[yare::parameterized(
399 base_patch_zero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 0), true },
400 base_patch_nonzero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 3), true },
401 base_other_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 55, 0), false },
402 base_other_major = { Version::new_base_version(1, 56), FullVersion::new(2, 56, 0), false },
403 full_same = { Version::new_full_version(1, 56, 3), FullVersion::new(1, 56, 3), true },
404 full_other_patch = { Version::new_full_version(1, 56, 0), FullVersion::new(1, 56, 3), false },
405 )]
406 fn matches(version: Version, other: FullVersion, expected: bool) {
407 assert_eq!(version.matches(&other), expected);
408 }
409
410 #[test]
411 fn from_base_version() {
412 let version = Version::from(BaseVersion::new(1, 2));
413
414 assert_eq!(version, Version::Base(BaseVersion::new(1, 2)));
415 }
416
417 #[test]
418 fn from_full_version() {
419 let version = Version::from(FullVersion::new(1, 2, 3));
420
421 assert_eq!(version, Version::Full(FullVersion::new(1, 2, 3)));
422 }
423
424 #[test]
425 fn const_constructors() {
426 const BASE: Version = Version::new_base_version(1, 2);
427 const FULL: Version = Version::new_full_version(1, 2, 3);
428
429 assert_eq!(BASE, Version::Base(BaseVersion::new(1, 2)));
430 assert_eq!(FULL, Version::Full(FullVersion::new(1, 2, 3)));
431 }
432
433 #[test]
434 fn is_base_variant() {
435 let version = Version::Base(BaseVersion::new(0, 0));
436
437 assert!(version.is(Variant::Base));
438 assert!(!version.is(Variant::Full));
439 }
440
441 #[test]
442 fn is_full_variant() {
443 let version = Version::Full(FullVersion::new(0, 0, 0));
444
445 assert!(version.is(Variant::Full));
446 assert!(!version.is(Variant::Base));
447 }
448
449 #[test]
450 fn map() {
451 let version = Version::Base(BaseVersion::new(0, 0));
452
453 let mapped = version.map(|v| match v {
454 Version::Base(base) => Version::Full(FullVersion::new(base.major, base.minor, 999)),
455 v => v,
456 });
457
458 assert_eq!(mapped, Version::Full(FullVersion::new(0, 0, 999)));
459 }
460
461 #[yare::parameterized(
462 base = { Version::Base(BaseVersion::new(0, 0)) },
463 full = { Version::Full(FullVersion::new(0, 0, 0)) },
464 )]
465 fn map_major(version: Version) {
466 let mapped = version.map_major(|_v| 999);
467
468 assert_eq!(mapped.major(), 999);
469 }
470
471 #[yare::parameterized(
472 base = { Version::Base(BaseVersion::new(0, 0)) },
473 full = { Version::Full(FullVersion::new(0, 0, 0)) },
474 )]
475 fn map_minor(version: Version) {
476 let mapped = version.map_minor(|_v| 999);
477
478 assert_eq!(mapped.minor(), 999);
479 }
480
481 #[test]
482 fn map_patch_base() {
483 let version = Version::Base(BaseVersion::new(0, 0));
484 let mapped = version.map_patch(|_v| 999);
485
486 assert!(mapped.patch().is_none());
487 }
488
489 #[test]
490 fn map_patch_full() {
491 let version = Version::Full(FullVersion::new(0, 0, 0));
492 let mapped = version.map_patch(|_v| 999);
493
494 assert_eq!(mapped.patch().unwrap(), 999);
495 }
496}