use std::cmp::Ordering;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::util::WILDCARD;
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Clone, Hash, Serialize, Deserialize)]
enum VersionPart {
Number(u32),
Text(String),
}
#[derive(Debug, Clone)]
pub struct VersionSpec(Vec<VersionPart>);
impl Serialize for VersionSpec {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for VersionSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let version_str = String::deserialize(deserializer)?;
Ok(VersionSpec::new(&version_str))
}
}
impl VersionSpec {
pub fn new(version_str: &str) -> Self {
let parts = version_str
.split('.')
.map(|part| {
if let Ok(number) = part.parse::<u32>() {
VersionPart::Number(number)
} else {
VersionPart::Text(part.to_string())
}
})
.collect();
VersionSpec(parts)
}
pub(crate) fn is_compatible(&self, other: &Self) -> bool {
if other < self {
return false;
}
let mut ub = self.0.clone(); let ub_len = ub.len();
let mut numeric_count = 0;
let numeric_total = self
.0
.iter()
.filter(|part| matches!(part, VersionPart::Number(_)))
.count();
if numeric_total == 1 {
return self == other;
}
for i in 0..ub_len {
if let VersionPart::Number(n) = ub[i] {
numeric_count += 1;
if numeric_count == numeric_total - 1 {
ub[i] = VersionPart::Number(n + 1);
ub.truncate(i + 1); break;
}
}
}
other < &VersionSpec(ub)
}
pub(crate) fn is_arbitrary_equal(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
pub(crate) fn is_caret(&self, other: &Self) -> bool {
if other < self {
return false;
}
let mut ub = self.0.clone(); let ub_len = ub.len();
let mut numeric_count = 0;
for i in 0..ub_len {
if let VersionPart::Number(n) = ub[i] {
numeric_count += 1;
if n != 0 || (numeric_count == ub_len) {
ub[i] = VersionPart::Number(n + 1);
ub.truncate(i + 1); break;
}
}
}
other < &VersionSpec(ub)
}
pub(crate) fn is_tilde(&self, other: &Self) -> bool {
if other < self {
return false;
}
let mut ub = self.0.clone(); let ub_len = ub.len();
let mut numeric_count = 0;
for i in 0..ub_len {
if let VersionPart::Number(n) = ub[i] {
numeric_count += 1;
if numeric_count == 2 || (numeric_count == 1 && ub_len == 1) {
ub[i] = VersionPart::Number(n + 1);
ub.truncate(i + 1); break;
}
}
}
other < &VersionSpec(ub)
}
pub(crate) fn has_wildcard(&self) -> bool {
self.0.iter().rev().any(|part| match part {
VersionPart::Number(_) => false,
VersionPart::Text(text) => text == WILDCARD,
})
}
}
impl fmt::Display for VersionSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let version_string = self
.0
.iter()
.map(|part| match part {
VersionPart::Number(num) => num.to_string(),
VersionPart::Text(text) => text.clone(),
})
.collect::<Vec<_>>()
.join(".");
write!(f, "{version_string}")
}
}
impl Hash for VersionSpec {
fn hash<H: Hasher>(&self, state: &mut H) {
for part in &self.0 {
part.hash(state);
}
}
}
impl Ord for VersionSpec {
fn cmp(&self, other: &Self) -> Ordering {
let max_len = self.0.len().max(other.0.len());
for i in 0..max_len {
let self_part = self.0.get(i).unwrap_or(&VersionPart::Number(0));
let other_part = other.0.get(i).unwrap_or(&VersionPart::Number(0));
let ordering = match (self_part, other_part) {
(VersionPart::Number(a), VersionPart::Number(b)) => a.cmp(b),
(VersionPart::Text(a), VersionPart::Text(b)) => {
if a == WILDCARD || b == WILDCARD {
Ordering::Equal
} else {
a.cmp(b)
}
}
(VersionPart::Number(_), VersionPart::Text(b)) => {
if b == WILDCARD {
Ordering::Equal
} else {
Ordering::Greater }
}
(VersionPart::Text(a), VersionPart::Number(_)) => {
if a == WILDCARD {
Ordering::Equal
} else {
Ordering::Less
}
}
};
if ordering != Ordering::Equal {
return ordering; }
}
Ordering::Equal
}
}
impl PartialOrd for VersionSpec {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for VersionSpec {
fn eq(&self, other: &Self) -> bool {
let max_len = self.0.len().max(other.0.len());
for i in 0..max_len {
let self_part = self.0.get(i).unwrap_or(&VersionPart::Number(0));
let other_part = other.0.get(i).unwrap_or(&VersionPart::Number(0));
match (self_part, other_part) {
(VersionPart::Text(a), VersionPart::Text(b))
if a == WILDCARD || b == WILDCARD =>
{
continue
}
(VersionPart::Text(a), VersionPart::Number(_)) if a == WILDCARD => {
continue
}
(VersionPart::Number(_), VersionPart::Text(b)) if b == WILDCARD => {
continue
}
(VersionPart::Number(a), VersionPart::Number(b)) if a != b => {
return false
}
(VersionPart::Text(a), VersionPart::Text(b)) if a != b => return false,
(VersionPart::Number(_), VersionPart::Text(_)) => return false,
(VersionPart::Text(_), VersionPart::Number(_)) => return false,
_ => {} }
}
true
}
}
impl Eq for VersionSpec {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_spec_a() {
assert_eq!(VersionSpec::new("2.2"), VersionSpec::new("2.2"));
assert_eq!(VersionSpec::new("2.*"), VersionSpec::new("2.2"));
assert_eq!(VersionSpec::new("2.2"), VersionSpec::new("2.*"));
}
#[test]
fn test_version_spec_b() {
assert_eq!(VersionSpec::new("2.*.1"), VersionSpec::new("2.2.1"));
assert_ne!(VersionSpec::new("2.*.1"), VersionSpec::new("2.2.2"));
}
#[test]
fn test_version_spec_c() {
assert!(VersionSpec::new("2.*") <= VersionSpec::new("2.2.1"));
assert!(VersionSpec::new("2.2") <= VersionSpec::new("2.*"));
}
#[test]
fn test_version_spec_d() {
assert!(VersionSpec::new("2.1") != VersionSpec::new("2.2"));
assert!(VersionSpec::new("2.2") == VersionSpec::new("2.2"));
assert!(VersionSpec::new("2.2.0") == VersionSpec::new("2.2"));
}
#[test]
fn test_version_spec_e() {
assert!(VersionSpec::new("1.7.1") > VersionSpec::new("1.7"));
assert!(VersionSpec::new("1.7.1") < VersionSpec::new("1.8"));
assert!(VersionSpec::new("1.7.0.post1") <= VersionSpec::new("1.7"));
assert!(VersionSpec::new("1.7.1") > VersionSpec::new("1.7.post1"));
}
#[test]
fn test_version_is_compatible_a() {
assert!(VersionSpec::new("2.2").is_compatible(&VersionSpec::new("2.2")),);
assert!(!VersionSpec::new("2.2").is_compatible(&VersionSpec::new("3.2")),);
assert!(VersionSpec::new("2.2").is_compatible(&VersionSpec::new("2.2.3.9")),);
}
#[test]
fn test_version_is_compatible_b() {
assert!(VersionSpec::new("2.2-2").is_arbitrary_equal(&VersionSpec::new("2.2-2")),);
assert!(
VersionSpec::new("foobar").is_arbitrary_equal(&VersionSpec::new("foobar")),
);
assert!(
!VersionSpec::new("foobar").is_arbitrary_equal(&VersionSpec::new("foobars")),
);
assert!(!VersionSpec::new("1.0")
.is_arbitrary_equal(&VersionSpec::new("1.0+downstream1")),);
}
#[test]
fn test_version_is_compatible_c() {
assert!(!VersionSpec::new("2.2.1").is_compatible(&VersionSpec::new("2.2.0")),);
assert!(VersionSpec::new("2.2.1").is_compatible(&VersionSpec::new("2.2.9")),);
assert!(!VersionSpec::new("2.2.1").is_compatible(&VersionSpec::new("2.3")),);
}
#[test]
fn test_version_is_compatible_d() {
assert!(!VersionSpec::new("1.4.5.0").is_compatible(&VersionSpec::new("2.2.0")),);
assert!(VersionSpec::new("1.4.5.0").is_compatible(&VersionSpec::new("1.4.5.9")),);
assert!(!VersionSpec::new("1.4.5.0").is_compatible(&VersionSpec::new("1.4.6.0")),);
}
#[test]
fn test_version_is_compatible_e() {
assert!(VersionSpec::new("2").is_compatible(&VersionSpec::new("2.0.0.0")),);
assert!(!VersionSpec::new("2").is_compatible(&VersionSpec::new("2.1")),);
assert!(!VersionSpec::new("2").is_compatible(&VersionSpec::new("3")),);
}
#[test]
fn test_version_spec_json_a() {
let vs1 = VersionSpec::new("2.2.3rc2");
let json = serde_json::to_string(&vs1).unwrap();
assert_eq!(json, "\"2.2.3rc2\"");
let vs2: VersionSpec = serde_json::from_str(&json).unwrap();
assert_eq!(vs2, VersionSpec::new("2.2.3rc2"));
}
#[test]
fn test_version_spec_tilde_a() {
assert!(VersionSpec::new("1.7.1").is_tilde(&VersionSpec::new("1.7.2")),);
assert!(!VersionSpec::new("1.7.1").is_tilde(&VersionSpec::new("1.7")),);
assert!(!VersionSpec::new("1.7.1").is_tilde(&VersionSpec::new("1.8")),);
assert!(!VersionSpec::new("1.7.1").is_tilde(&VersionSpec::new("2")),);
assert!(!VersionSpec::new("1.7.1").is_tilde(&VersionSpec::new("0.8")),);
}
#[test]
fn test_version_spec_tilde_b() {
assert!(VersionSpec::new("1.2").is_tilde(&VersionSpec::new("1.2.1")),);
assert!(VersionSpec::new("1.2").is_tilde(&VersionSpec::new("1.2.9.1")),);
assert!(!VersionSpec::new("1.2").is_tilde(&VersionSpec::new("1.8")),);
assert!(!VersionSpec::new("1.2").is_tilde(&VersionSpec::new("2")),);
assert!(!VersionSpec::new("1.2").is_tilde(&VersionSpec::new("1.3")),);
}
#[test]
fn test_version_spec_tilde_c() {
assert!(VersionSpec::new("2").is_tilde(&VersionSpec::new("2.1")),);
assert!(VersionSpec::new("2").is_tilde(&VersionSpec::new("2.9.1")),);
assert!(!VersionSpec::new("2").is_tilde(&VersionSpec::new("1.8")),);
assert!(!VersionSpec::new("2").is_tilde(&VersionSpec::new("3")),);
assert!(!VersionSpec::new("2").is_tilde(&VersionSpec::new("4")),);
}
#[test]
fn test_version_spec_caret_a() {
assert!(VersionSpec::new("1.7.1").is_caret(&VersionSpec::new("1.7.2")),);
assert!(VersionSpec::new("1.7.1").is_caret(&VersionSpec::new("1.20")),);
assert!(!VersionSpec::new("1.7.1").is_caret(&VersionSpec::new("1.6")),);
assert!(!VersionSpec::new("1.7.1").is_caret(&VersionSpec::new("2")),);
assert!(!VersionSpec::new("1.7.1").is_caret(&VersionSpec::new("0.8")),);
}
#[test]
fn test_version_spec_caret_b() {
assert!(VersionSpec::new("1").is_caret(&VersionSpec::new("1.7.2")),);
assert!(VersionSpec::new("1").is_caret(&VersionSpec::new("1.0.1")),);
assert!(VersionSpec::new("1").is_caret(&VersionSpec::new("1.6")),);
assert!(!VersionSpec::new("1").is_caret(&VersionSpec::new("2")),);
assert!(!VersionSpec::new("1").is_caret(&VersionSpec::new("0.8")),);
}
#[test]
fn test_version_spec_caret_c() {
assert!(!VersionSpec::new("0").is_caret(&VersionSpec::new("1.7.2")),);
assert!(!VersionSpec::new("0").is_caret(&VersionSpec::new("1.0.1")),);
assert!(VersionSpec::new("0").is_caret(&VersionSpec::new("0.6")),);
assert!(VersionSpec::new("0").is_caret(&VersionSpec::new("0.1.2")),);
assert!(VersionSpec::new("0").is_caret(&VersionSpec::new("0.8")),);
}
#[test]
fn test_version_spec_caret_d() {
assert!(!VersionSpec::new("0.0.3").is_caret(&VersionSpec::new("1.7.2")),);
assert!(!VersionSpec::new("0.0.3").is_caret(&VersionSpec::new("0.0.2")),);
assert!(!VersionSpec::new("0.0.3").is_caret(&VersionSpec::new("0.0.4")),);
assert!(VersionSpec::new("0.0.3").is_caret(&VersionSpec::new("0.0.3.1")),);
assert!(VersionSpec::new("0.0.3").is_caret(&VersionSpec::new("0.0.3.9")),);
}
#[test]
fn test_version_spec_caret_e() {
assert!(VersionSpec::new("0.0").is_caret(&VersionSpec::new("0.0.2")),);
assert!(VersionSpec::new("0.0").is_caret(&VersionSpec::new("0.0.2.5")),);
assert!(!VersionSpec::new("0.0").is_caret(&VersionSpec::new("0.1.0")),);
assert!(!VersionSpec::new("0.0").is_caret(&VersionSpec::new("1")),);
}
#[test]
fn test_version_spec_has_wildcard_a() {
assert!(VersionSpec::new("1.7.*").has_wildcard());
assert!(!VersionSpec::new("1.7.3").has_wildcard());
}
}