use core::fmt;
use miden_assembly_syntax::debuginfo::Span;
#[cfg(feature = "arbitrary")]
use miden_core::utils::hash_string_to_word;
#[cfg(feature = "arbitrary")]
use proptest::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::*;
use crate::Word;
#[derive(Debug, Clone)]
#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
pub enum VersionRequirement {
Semantic(Span<VersionReq>),
Digest(Span<Word>),
Exact(Version),
}
impl VersionRequirement {
pub fn is_semantic_version(&self) -> bool {
matches!(self, Self::Semantic(_))
}
pub fn is_digest(&self) -> bool {
matches!(self, Self::Digest(_))
}
pub fn is_exact(&self) -> bool {
matches!(self, Self::Exact(_))
}
}
impl Eq for VersionRequirement {}
impl PartialEq for VersionRequirement {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Exact(l), Self::Exact(r)) => l == r,
(Self::Digest(l), Self::Digest(r)) => l.into_inner() == r.into_inner(),
(Self::Semantic(l), Self::Semantic(r)) => l == r,
(Self::Semantic(_) | Self::Exact(_), Self::Digest(_))
| (Self::Semantic(_), Self::Exact(_))
| (Self::Digest(_), Self::Semantic(_) | Self::Exact(_))
| (Self::Exact(_), Self::Semantic(_)) => false,
}
}
}
impl fmt::Display for VersionRequirement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Semantic(v) => fmt::Display::fmt(v, f),
Self::Digest(word) => fmt::Display::fmt(word, f),
Self::Exact(version) => {
assert!(
version.digest.is_some(),
"exact requirements must include an artifact digest"
);
write!(f, "{version}")
},
}
}
}
impl From<VersionReq> for VersionRequirement {
fn from(version: VersionReq) -> Self {
Self::Semantic(Span::unknown(version))
}
}
impl From<Word> for VersionRequirement {
fn from(digest: Word) -> Self {
Self::Digest(Span::unknown(digest))
}
}
impl From<Version> for VersionRequirement {
fn from(value: Version) -> Self {
if value.digest.is_none() {
Self::Semantic(Span::unknown(format!("={}", value.version).parse().unwrap()))
} else {
Self::Exact(value)
}
}
}
#[cfg(feature = "serde")]
impl Serialize for VersionRequirement {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use alloc::string::ToString;
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for VersionRequirement {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use core::str::FromStr;
let value = <String as Deserialize>::deserialize(deserializer)?;
if value == "*" {
return Ok(Self::from(VersionReq::STAR));
}
if let Some((version, digest)) = value.split_once('#') {
let version = version.parse::<SemVer>().map_err(serde::de::Error::custom)?;
let digest = Word::parse(digest).map_err(serde::de::Error::custom)?;
return Ok(Self::Exact(Version::new(version, digest)));
}
if let Ok(digest) = Word::parse(&value) {
return Ok(Self::from(digest));
}
let requirement = VersionReq::from_str(&value).map_err(serde::de::Error::custom)?;
Ok(Self::from(requirement))
}
}
#[cfg(feature = "arbitrary")]
impl Arbitrary for VersionRequirement {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let semantic =
(0u64..=4, 0u64..=8, 0u64..=16, 0u8..=2).prop_map(|(major, minor, patch, kind)| {
let req = match kind {
0 => format!("^{major}.{minor}.{patch}"),
1 => format!("~{major}.{minor}.{patch}"),
_ => format!("={major}.{minor}.{patch}"),
}
.parse::<VersionReq>()
.expect("generated version requirements are valid");
Self::Semantic(Span::unknown(req))
});
let digest =
proptest::collection::vec(proptest::char::range('a', 'z'), 1..16).prop_map(|chars| {
let material = chars.into_iter().collect::<String>();
let digest = hash_string_to_word(material.as_str());
Self::Digest(Span::unknown(digest))
});
let exact = any::<Version>()
.prop_filter("exact requirements must include a digest", |version| {
version.digest.is_some()
})
.prop_map(Self::Exact);
proptest::prop_oneof![Just(Self::from(VersionReq::STAR)), semantic, digest, exact,].boxed()
}
}