#![deny(missing_docs)]
use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
use crate::parsers::original;
pub use parsers::{BaseVersionParser, FullVersionParser, ParserError, VersionParser};
#[cfg(feature = "semver")]
pub use version::FromSemverError;
pub use version::{BaseVersion, FullVersion};
pub mod parsers;
#[cfg(feature = "serde")]
mod serde_impl;
mod version;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
ParserError(#[from] ParserError),
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub enum Version {
Base(BaseVersion),
Full(FullVersion),
}
impl Version {
pub fn parse(input: &str) -> Result<Self, Error> {
original::Parser::from(input.as_bytes())
.parse()
.map_err(|e| Error::from(Into::<ParserError>::into(e)))
}
pub const fn new_base_version(major: u64, minor: u64) -> Self {
Self::Base(BaseVersion { major, minor })
}
pub const fn new_full_version(major: u64, minor: u64, patch: u64) -> Self {
Self::Full(FullVersion {
major,
minor,
patch,
})
}
pub fn major(&self) -> u64 {
match self {
Self::Base(inner) => inner.major,
Self::Full(inner) => inner.major,
}
}
pub fn minor(&self) -> u64 {
match self {
Self::Base(inner) => inner.minor,
Self::Full(inner) => inner.minor,
}
}
pub fn patch(&self) -> Option<u64> {
match self {
Self::Base(_) => None,
Self::Full(inner) => Some(inner.patch),
}
}
pub fn to_full_version_lossy(&self) -> FullVersion {
match self {
Self::Base(inner) => inner.to_full_version_lossy(),
Self::Full(inner) => *inner,
}
}
pub fn is_compatible_with(&self, other: &FullVersion) -> Ordering {
match self {
Self::Base(inner) => inner.cmp(&other.to_base_version_lossy()),
Self::Full(inner) => inner.cmp(other),
}
}
pub fn matches(&self, other: &FullVersion) -> bool {
self.is_compatible_with(other) == Ordering::Equal
}
pub fn is(&self, variant: Variant) -> bool {
match self {
Version::Base(_) => matches!(variant, Variant::Base),
Version::Full(_) => matches!(variant, Variant::Full),
}
}
#[inline]
pub fn map<U, F>(self, fun: F) -> U
where
F: FnOnce(Self) -> U,
{
fun(self)
}
#[inline]
pub fn map_major<F>(self, fun: F) -> Self
where
F: FnOnce(u64) -> u64,
{
self.map(|v| match v {
Self::Base(base) => Version::Base(BaseVersion::new(fun(base.major), base.minor)),
Self::Full(full) => {
Version::Full(FullVersion::new(fun(full.major), full.minor, full.patch))
}
})
}
#[inline]
pub fn map_minor<F>(self, fun: F) -> Self
where
F: FnOnce(u64) -> u64,
{
self.map(|v| match v {
Self::Base(base) => Version::Base(BaseVersion::new(base.major, fun(base.minor))),
Self::Full(full) => {
Version::Full(FullVersion::new(full.major, fun(full.minor), full.patch))
}
})
}
#[inline]
pub fn map_patch<F>(self, fun: F) -> Self
where
F: FnOnce(u64) -> u64,
{
self.map(|v| match v {
Self::Base(base) => Version::Base(BaseVersion::new(base.major, base.minor)),
Self::Full(full) => {
Version::Full(FullVersion::new(full.major, full.minor, fun(full.patch)))
}
})
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Error> {
original::Parser::from_slice(input.as_bytes())
.parse()
.map_err(|e| Error::from(Into::<ParserError>::into(e)))
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Base(inner) => fmt::Display::fmt(&inner, f),
Self::Full(inner) => fmt::Display::fmt(&inner, f),
}
}
}
impl From<BaseVersion> for Version {
fn from(version: BaseVersion) -> Self {
Self::Base(version)
}
}
impl From<FullVersion> for Version {
fn from(version: FullVersion) -> Self {
Self::Full(version)
}
}
impl From<(u64, u64)> for Version {
fn from(tuple: (u64, u64)) -> Self {
Self::Base(BaseVersion::from(tuple))
}
}
impl From<(u64, u64, u64)> for Version {
fn from(tuple: (u64, u64, u64)) -> Self {
Self::Full(FullVersion::from(tuple))
}
}
#[derive(Copy, Clone, Debug)]
pub enum Variant {
Base,
Full,
}
#[cfg(test)]
mod tests {
use crate::{BaseVersion, FullVersion, Variant, Version};
use std::cmp::Ordering;
#[yare::parameterized(
base = { Version::new_base_version(1, 2), FullVersion::new(1, 2, 0) },
full = { Version::new_full_version(1, 2, 3), FullVersion::new(1, 2, 3) },
)]
fn to_full_version_lossy(version: Version, expected: FullVersion) {
assert_eq!(version.to_full_version_lossy(), expected);
}
#[yare::parameterized(
base_eq_patch_zero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 0), Ordering::Equal },
base_eq_patch_nonzero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 99), Ordering::Equal },
base_lt_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 57, 0), Ordering::Less },
base_lt_major = { Version::new_base_version(1, 56), FullVersion::new(2, 0, 0), Ordering::Less },
base_gt_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 55, 99), Ordering::Greater },
base_gt_major = { Version::new_base_version(2, 0), FullVersion::new(1, 99, 99), Ordering::Greater },
full_eq = { Version::new_full_version(1, 56, 1), FullVersion::new(1, 56, 1), Ordering::Equal },
full_lt_patch = { Version::new_full_version(1, 56, 0), FullVersion::new(1, 56, 1), Ordering::Less },
full_lt_minor = { Version::new_full_version(1, 56, 9), FullVersion::new(1, 57, 0), Ordering::Less },
full_lt_major = { Version::new_full_version(1, 99, 9), FullVersion::new(2, 0, 0), Ordering::Less },
full_gt_patch = { Version::new_full_version(1, 56, 1), FullVersion::new(1, 56, 0), Ordering::Greater },
full_gt_minor = { Version::new_full_version(1, 57, 0), FullVersion::new(1, 56, 9), Ordering::Greater },
full_gt_major = { Version::new_full_version(2, 0, 0), FullVersion::new(1, 99, 9), Ordering::Greater },
)]
fn is_compatible_with(version: Version, other: FullVersion, expected: Ordering) {
assert_eq!(version.is_compatible_with(&other), expected);
}
#[yare::parameterized(
base_patch_zero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 0), true },
base_patch_nonzero = { Version::new_base_version(1, 56), FullVersion::new(1, 56, 3), true },
base_other_minor = { Version::new_base_version(1, 56), FullVersion::new(1, 55, 0), false },
base_other_major = { Version::new_base_version(1, 56), FullVersion::new(2, 56, 0), false },
full_same = { Version::new_full_version(1, 56, 3), FullVersion::new(1, 56, 3), true },
full_other_patch = { Version::new_full_version(1, 56, 0), FullVersion::new(1, 56, 3), false },
)]
fn matches(version: Version, other: FullVersion, expected: bool) {
assert_eq!(version.matches(&other), expected);
}
#[test]
fn from_base_version() {
let version = Version::from(BaseVersion::new(1, 2));
assert_eq!(version, Version::Base(BaseVersion::new(1, 2)));
}
#[test]
fn from_full_version() {
let version = Version::from(FullVersion::new(1, 2, 3));
assert_eq!(version, Version::Full(FullVersion::new(1, 2, 3)));
}
#[test]
fn const_constructors() {
const BASE: Version = Version::new_base_version(1, 2);
const FULL: Version = Version::new_full_version(1, 2, 3);
assert_eq!(BASE, Version::Base(BaseVersion::new(1, 2)));
assert_eq!(FULL, Version::Full(FullVersion::new(1, 2, 3)));
}
#[test]
fn is_base_variant() {
let version = Version::Base(BaseVersion::new(0, 0));
assert!(version.is(Variant::Base));
assert!(!version.is(Variant::Full));
}
#[test]
fn is_full_variant() {
let version = Version::Full(FullVersion::new(0, 0, 0));
assert!(version.is(Variant::Full));
assert!(!version.is(Variant::Base));
}
#[test]
fn map() {
let version = Version::Base(BaseVersion::new(0, 0));
let mapped = version.map(|v| match v {
Version::Base(base) => Version::Full(FullVersion::new(base.major, base.minor, 999)),
v => v,
});
assert_eq!(mapped, Version::Full(FullVersion::new(0, 0, 999)));
}
#[yare::parameterized(
base = { Version::Base(BaseVersion::new(0, 0)) },
full = { Version::Full(FullVersion::new(0, 0, 0)) },
)]
fn map_major(version: Version) {
let mapped = version.map_major(|_v| 999);
assert_eq!(mapped.major(), 999);
}
#[yare::parameterized(
base = { Version::Base(BaseVersion::new(0, 0)) },
full = { Version::Full(FullVersion::new(0, 0, 0)) },
)]
fn map_minor(version: Version) {
let mapped = version.map_minor(|_v| 999);
assert_eq!(mapped.minor(), 999);
}
#[test]
fn map_patch_base() {
let version = Version::Base(BaseVersion::new(0, 0));
let mapped = version.map_patch(|_v| 999);
assert!(mapped.patch().is_none());
}
#[test]
fn map_patch_full() {
let version = Version::Full(FullVersion::new(0, 0, 0));
let mapped = version.map_patch(|_v| 999);
assert_eq!(mapped.patch().unwrap(), 999);
}
}