use std::fmt::{self, Debug, Display};
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct SemanticVersion {
major: u32,
minor: u32,
patch: u32,
}
#[cfg(feature = "serde")]
impl serde::Serialize for SemanticVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{}", self))
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SemanticVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl SemanticVersion {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn zero() -> Self {
Self::new(0, 0, 0)
}
pub fn one() -> Self {
Self::new(1, 0, 0)
}
pub fn two() -> Self {
Self::new(2, 0, 0)
}
}
impl From<(u32, u32, u32)> for SemanticVersion {
fn from(tuple: (u32, u32, u32)) -> Self {
let (major, minor, patch) = tuple;
Self::new(major, minor, patch)
}
}
impl From<&(u32, u32, u32)> for SemanticVersion {
fn from(tuple: &(u32, u32, u32)) -> Self {
let (major, minor, patch) = *tuple;
Self::new(major, minor, patch)
}
}
impl From<&SemanticVersion> for SemanticVersion {
fn from(v: &SemanticVersion) -> Self {
*v
}
}
impl From<SemanticVersion> for (u32, u32, u32) {
fn from(v: SemanticVersion) -> Self {
(v.major, v.minor, v.patch)
}
}
impl SemanticVersion {
pub fn bump_patch(self) -> Self {
Self::new(self.major, self.minor, self.patch + 1)
}
pub fn bump_minor(self) -> Self {
Self::new(self.major, self.minor + 1, 0)
}
pub fn bump_major(self) -> Self {
Self::new(self.major + 1, 0, 0)
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum VersionParseError {
#[error("version {full_version} must contain 3 numbers separated by dot")]
NotThreeParts {
full_version: String,
},
#[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")]
ParseIntError {
full_version: String,
version_part: String,
parse_error: String,
},
}
impl FromStr for SemanticVersion {
type Err = VersionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parse_u32 = |part: &str| {
part.parse::<u32>().map_err(|e| Self::Err::ParseIntError {
full_version: s.to_string(),
version_part: part.to_string(),
parse_error: e.to_string(),
})
};
let mut parts = s.split('.');
match (parts.next(), parts.next(), parts.next(), parts.next()) {
(Some(major), Some(minor), Some(patch), None) => {
let major = parse_u32(major)?;
let minor = parse_u32(minor)?;
let patch = parse_u32(patch)?;
Ok(Self {
major,
minor,
patch,
})
}
_ => Err(Self::Err::NotThreeParts {
full_version: s.to_string(),
}),
}
}
}
#[test]
fn from_str_for_semantic_version() {
let parse = |str: &str| str.parse::<SemanticVersion>();
assert!(parse(
&SemanticVersion {
major: 0,
minor: 1,
patch: 0
}
.to_string()
)
.is_ok());
assert!(parse("1.2.3").is_ok());
assert_eq!(
parse("1.abc.3"),
Err(VersionParseError::ParseIntError {
full_version: "1.abc.3".to_owned(),
version_part: "abc".to_owned(),
parse_error: "invalid digit found in string".to_owned(),
})
);
assert_eq!(
parse("1.2.-3"),
Err(VersionParseError::ParseIntError {
full_version: "1.2.-3".to_owned(),
version_part: "-3".to_owned(),
parse_error: "invalid digit found in string".to_owned(),
})
);
assert_eq!(
parse("1.2.9876543210"),
Err(VersionParseError::ParseIntError {
full_version: "1.2.9876543210".to_owned(),
version_part: "9876543210".to_owned(),
parse_error: "number too large to fit in target type".to_owned(),
})
);
assert_eq!(
parse("1.2"),
Err(VersionParseError::NotThreeParts {
full_version: "1.2".to_owned(),
})
);
assert_eq!(
parse("1.2.3."),
Err(VersionParseError::NotThreeParts {
full_version: "1.2.3.".to_owned(),
})
);
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}