use crate::{DataError, Validate, ValidationError};
use core::fmt;
use semver::{Version, VersionReq};
use serde::{Deserialize, Deserializer, de};
use serde_json::Value;
use serde_with::SerializeDisplay;
use std::str::FromStr;
#[derive(Debug, PartialEq, SerializeDisplay)]
pub struct MyVersion(Version);
impl MyVersion {
pub fn major(&self) -> u64 {
self.0.major
}
pub fn minor(&self) -> u64 {
self.0.minor
}
pub fn patch(&self) -> u64 {
self.0.patch
}
fn is_excluded(&self) -> bool {
self.0.major == 1 && self.0.minor == 1
}
}
impl FromStr for MyVersion {
type Err = DataError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.trim().split('.').collect();
let padded = match parts.len() {
1 => format!("{}.0.0", parts[0]),
2 => format!("{}.{}.0", parts[0], parts[1]),
_ => s.to_string(),
};
let sv = Version::parse(&padded)?;
Ok(MyVersion(sv))
}
}
impl From<f64> for MyVersion {
fn from(float_value: f64) -> Self {
let major = float_value.trunc() as u64;
let minor = (float_value.fract() * 1000.0).round() as u64;
MyVersion(Version::new(major, minor, 0))
}
}
impl<'de> Deserialize<'de> for MyVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
match value {
Value::String(s) => MyVersion::from_str(&s).map_err(de::Error::custom),
Value::Number(num) => {
if let Some(z_float) = num.as_f64() {
Ok(MyVersion::from(z_float))
} else {
Err(de::Error::custom("Invalid number format"))
}
}
_ => Err(de::Error::custom("Expected string | number")),
}
}
}
impl fmt::Display for MyVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Validate for MyVersion {
fn validate(&self) -> Vec<super::ValidationError> {
let mut vec = vec![];
let range = VersionReq::parse(">=1.0.0, <=2.0.0").unwrap();
if range.matches(&self.0) && !self.is_excluded() {
} else {
vec.push(ValidationError::ConstraintViolation(
format!("Version '{self}' is invalid or not allowed").into(),
))
}
vec
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing_test::traced_test;
#[test]
fn test_no_patch() {
let v = MyVersion::from_str("1.0").unwrap();
assert_eq!(v.major(), 1);
assert_eq!(v.minor(), 0);
assert_eq!(v.patch(), 0);
let v: MyVersion = serde_json::from_str("1.0").unwrap();
assert_eq!(v.major(), 1);
assert_eq!(v.minor(), 0);
assert_eq!(v.patch(), 0);
}
#[traced_test]
#[test]
fn test_invalid() {
assert!(!MyVersion::from_str("0.9.9").unwrap().is_valid());
assert!(!MyVersion::from_str("2.0.1-beta").unwrap().is_valid());
assert!(!MyVersion::from_str("1.1.0").unwrap().is_valid());
}
#[traced_test]
#[test]
fn test_valid() {
assert!(MyVersion::from_str("1.0").unwrap().is_valid());
assert!(MyVersion::from_str("1.0.3").unwrap().is_valid());
assert!(MyVersion::from_str("2.0.0").unwrap().is_valid());
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Foo {
ver: Option<MyVersion>,
}
#[traced_test]
#[test]
fn test_serde() {
const F1: &str = r#"{"ver":"1.0"}"#;
const F2: &str = r#"{"ver":"1.0.3"}"#;
const F3: &str = r#"{"ver":"2.0.0"}"#;
let f: Foo = serde_json::from_str(F1).unwrap();
assert_eq!(f.ver, Some(MyVersion(Version::new(1, 0, 0))));
let f: Foo = serde_json::from_str(F2).unwrap();
assert_eq!(f.ver, Some(MyVersion(Version::new(1, 0, 3))));
let f: Foo = serde_json::from_str(F3).unwrap();
assert_eq!(f.ver, Some(MyVersion(Version::new(2, 0, 0))));
}
}