use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use sea_orm::Value;
use thiserror::Error;
use utoipa::ToSchema;
#[derive(Debug, Eq, Clone, serde::Serialize, ToSchema)]
#[schema(value_type = String)]
pub struct Version(String);
impl<'de> serde::Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
semver::Version::parse(&s)
.map(|sv| Version(sv.to_string()))
.map_err(|e| serde::de::Error::custom(e.to_string()))
}
}
#[derive(Debug, Eq, PartialEq, Error)]
pub enum VersionError {
#[error("Invalid SemVer")]
InvalidSemVer,
}
impl Version {
pub fn from_unchecked_str(version: &str) -> Self {
Self(version.to_string())
}
pub fn into_inner(self) -> String {
self.0
}
}
impl From<Version> for Value {
fn from(value: Version) -> Self {
Value::String(Some(value.0))
}
}
impl From<&Version> for Value {
fn from(value: &Version) -> Self {
Value::String(Some(value.0.clone()))
}
}
impl TryFrom<&str> for Version {
type Error = VersionError;
fn try_from(version: &str) -> Result<Self, Self::Error> {
Version::try_from(&version.to_string())
}
}
impl TryFrom<&String> for Version {
type Error = VersionError;
fn try_from(version: &String) -> Result<Self, Self::Error> {
match semver::Version::parse(version) {
Ok(sv) => Ok(Version(sv.to_string())),
Err(_) => Err(VersionError::InvalidSemVer),
}
}
}
impl AsRef<Version> for Version {
fn as_ref(&self) -> &Version {
self
}
}
impl Deref for Version {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for Version {
fn default() -> Self {
Version(semver::Version::new(0, 0, 0).to_string())
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
let sv1 = semver::Version::parse(&self.to_string()).unwrap();
let sv2 = semver::Version::parse(&other.to_string()).unwrap();
sv1.cmp(&sv2)
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
let sv1 = semver::Version::parse(&self.to_string()).unwrap();
let sv2 = semver::Version::parse(&other.to_string()).unwrap();
sv1 == sv2
}
}
impl Hash for Version {
fn hash<H: Hasher>(&self, state: &mut H) {
let sv = semver::Version::parse(&self.to_string()).unwrap();
sv.hash(state);
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_package_versions() {
assert_eq!(Version::try_from("0.0.0").unwrap().to_string(), "0.0.0");
assert_eq!(Version::try_from("1.0.1").unwrap().to_string(), "1.0.1");
assert_eq!(
Version::try_from("23.123.343").unwrap().to_string(),
"23.123.343"
);
assert_eq!(
Version::try_from("2.43.3-alpha34").unwrap().to_string(),
"2.43.3-alpha34"
);
assert_eq!(
Version::try_from("0.1.1-45rdfsd-45").unwrap().to_string(),
"0.1.1-45rdfsd-45"
);
}
#[test]
fn invalid_package_versions() {
assert_eq!(
Version::try_from("a.1.2").unwrap_err(),
VersionError::InvalidSemVer
);
assert_eq!(
Version::try_from("002.23.1").unwrap_err(),
VersionError::InvalidSemVer
);
assert_eq!(
Version::try_from("3.2fg.3").unwrap_err(),
VersionError::InvalidSemVer
);
assert_eq!(
Version::try_from("5.3.2.3").unwrap_err(),
VersionError::InvalidSemVer
);
}
}