miden_package_registry/
version_requirement.rs1use core::fmt;
2
3use miden_assembly_syntax::debuginfo::Span;
4#[cfg(feature = "arbitrary")]
5use miden_core::utils::hash_string_to_word;
6#[cfg(feature = "arbitrary")]
7use proptest::prelude::*;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use super::*;
12use crate::Word;
13
14#[derive(Debug, Clone)]
16#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
17pub enum VersionRequirement {
18 Semantic(Span<VersionReq>),
26 Digest(Span<Word>),
35 Exact(Version),
37}
38
39impl VersionRequirement {
40 pub fn is_semantic_version(&self) -> bool {
42 matches!(self, Self::Semantic(_))
43 }
44
45 pub fn is_digest(&self) -> bool {
47 matches!(self, Self::Digest(_))
48 }
49
50 pub fn is_exact(&self) -> bool {
52 matches!(self, Self::Exact(_))
53 }
54}
55
56impl Eq for VersionRequirement {}
57
58impl PartialEq for VersionRequirement {
59 fn eq(&self, other: &Self) -> bool {
60 match (self, other) {
61 (Self::Exact(l), Self::Exact(r)) => l == r,
62 (Self::Digest(l), Self::Digest(r)) => l.into_inner() == r.into_inner(),
63 (Self::Semantic(l), Self::Semantic(r)) => l == r,
64 (Self::Semantic(_) | Self::Exact(_), Self::Digest(_))
65 | (Self::Semantic(_), Self::Exact(_))
66 | (Self::Digest(_), Self::Semantic(_) | Self::Exact(_))
67 | (Self::Exact(_), Self::Semantic(_)) => false,
68 }
69 }
70}
71
72impl fmt::Display for VersionRequirement {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::Semantic(v) => fmt::Display::fmt(v, f),
76 Self::Digest(word) => fmt::Display::fmt(word, f),
77 Self::Exact(version) => {
78 assert!(
79 version.digest.is_some(),
80 "exact requirements must include an artifact digest"
81 );
82 write!(f, "{version}")
83 },
84 }
85 }
86}
87
88impl From<VersionReq> for VersionRequirement {
89 fn from(version: VersionReq) -> Self {
90 Self::Semantic(Span::unknown(version))
91 }
92}
93
94impl From<Word> for VersionRequirement {
95 fn from(digest: Word) -> Self {
96 Self::Digest(Span::unknown(digest))
97 }
98}
99
100impl From<Version> for VersionRequirement {
101 fn from(value: Version) -> Self {
102 if value.digest.is_none() {
103 Self::Semantic(Span::unknown(format!("={}", value.version).parse().unwrap()))
104 } else {
105 Self::Exact(value)
106 }
107 }
108}
109
110#[cfg(feature = "serde")]
111impl Serialize for VersionRequirement {
112 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
113 where
114 S: serde::Serializer,
115 {
116 use alloc::string::ToString;
117 serializer.serialize_str(&self.to_string())
118 }
119}
120
121#[cfg(feature = "serde")]
122impl<'de> Deserialize<'de> for VersionRequirement {
123 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124 where
125 D: serde::Deserializer<'de>,
126 {
127 use core::str::FromStr;
128
129 let value = <String as Deserialize>::deserialize(deserializer)?;
130
131 if value == "*" {
132 return Ok(Self::from(VersionReq::STAR));
133 }
134
135 if let Some((version, digest)) = value.split_once('#') {
136 let version = version.parse::<SemVer>().map_err(serde::de::Error::custom)?;
137 let digest = Word::parse(digest).map_err(serde::de::Error::custom)?;
138 return Ok(Self::Exact(Version::new(version, digest)));
139 }
140
141 if let Ok(digest) = Word::parse(&value) {
142 return Ok(Self::from(digest));
143 }
144
145 let requirement = VersionReq::from_str(&value).map_err(serde::de::Error::custom)?;
146 Ok(Self::from(requirement))
147 }
148}
149
150#[cfg(feature = "arbitrary")]
151impl Arbitrary for VersionRequirement {
152 type Parameters = ();
153 type Strategy = BoxedStrategy<Self>;
154
155 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
156 let semantic =
157 (0u64..=4, 0u64..=8, 0u64..=16, 0u8..=2).prop_map(|(major, minor, patch, kind)| {
158 let req = match kind {
159 0 => format!("^{major}.{minor}.{patch}"),
160 1 => format!("~{major}.{minor}.{patch}"),
161 _ => format!("={major}.{minor}.{patch}"),
162 }
163 .parse::<VersionReq>()
164 .expect("generated version requirements are valid");
165
166 Self::Semantic(Span::unknown(req))
167 });
168
169 let digest =
170 proptest::collection::vec(proptest::char::range('a', 'z'), 1..16).prop_map(|chars| {
171 let material = chars.into_iter().collect::<String>();
172 let digest = hash_string_to_word(material.as_str());
173 Self::Digest(Span::unknown(digest))
174 });
175
176 let exact = any::<Version>()
177 .prop_filter("exact requirements must include a digest", |version| {
178 version.digest.is_some()
179 })
180 .prop_map(Self::Exact);
181
182 proptest::prop_oneof![Just(Self::from(VersionReq::STAR)), semantic, digest, exact,].boxed()
183 }
184}