use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt, str::FromStr};
use thiserror::Error;
#[derive(Error, Debug)]
pub struct ParseError {
#[from]
source: semver::Error,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.source)
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct Version {
pub major: u64,
pub minor: u64,
pub patch: u64,
}
impl Version {
pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn parse(version: &str) -> Result<Self, ParseError> {
semver::Version::parse(version)
.map(|ref version| version.into())
.map_err(|source| ParseError { source })
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl From<&semver::Version> for Version {
fn from(version: &semver::Version) -> Self {
Self {
major: version.major,
minor: version.minor,
patch: version.patch,
}
}
}
impl From<&Version> for semver::Version {
fn from(version: &Version) -> Self {
semver::Version::new(version.major, version.minor, version.patch)
}
}
impl<T> From<(T, T, T)> for Version
where
T: Into<u64>,
{
fn from((major, minor, patch): (T, T, T)) -> Self {
Self {
major: major.into(),
minor: minor.into(),
patch: patch.into(),
}
}
}
impl FromStr for Version {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
semver::Version::parse(s)
.map(|ref version| version.into())
.map_err(|source| ParseError { source })
}
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Version::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.major > other.major {
Some(Ordering::Greater)
} else if self.major < other.major {
Some(Ordering::Less)
} else if self.minor > other.minor {
Some(Ordering::Greater)
} else if self.minor < other.minor {
Some(Ordering::Less)
} else if self.patch > other.patch {
Some(Ordering::Greater)
} else if self.patch < other.patch {
Some(Ordering::Less)
} else {
Some(Ordering::Equal)
}
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
if self.major > other.major {
Ordering::Greater
} else if self.major < other.major {
Ordering::Less
} else if self.minor > other.minor {
Ordering::Greater
} else if self.minor < other.minor {
Ordering::Less
} else if self.patch > other.patch {
Ordering::Greater
} else if self.patch < other.patch {
Ordering::Less
} else {
Ordering::Equal
}
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct VersionReq {
inner: semver::VersionReq,
}
impl VersionReq {
pub fn parse(text: &str) -> Result<VersionReq, ParseError> {
Ok(VersionReq {
inner: semver::VersionReq::parse(text)?,
})
}
pub fn matches(&self, version: &Version) -> bool {
self.inner.matches(&semver::Version::from(version))
}
}
impl FromStr for VersionReq {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(VersionReq {
inner: semver::VersionReq::from_str(s)?,
})
}
}
impl ToString for VersionReq {
fn to_string(&self) -> String {
self.inner.to_string()
}
}
impl Serialize for VersionReq {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.inner.to_string())
}
}
impl<'de> Deserialize<'de> for VersionReq {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
VersionReq::parse(&s).map_err(serde::de::Error::custom)
}
}
#[test]
fn version() -> anyhow::Result<()> {
let v1 = Version::parse("1.0.0")?;
let v2 = Version::parse("2.0.0")?;
let v3 = Version::parse("3.0.0")?;
assert!(v2 > v1);
assert!(v2 < v3);
let v1_1 = Version::parse("1.1.0")?;
assert!(v1_1 > v1);
let v1_1_1 = Version::parse("1.1.1")?;
assert!(v1_1_1 > v1_1);
Ok(())
}