miden_package_registry/
version.rs1use core::{borrow::Borrow, fmt, str::FromStr};
2
3pub use miden_assembly_syntax::semver::{Error as SemVerError, Version as SemVer};
4use miden_core::Word;
5#[cfg(feature = "arbitrary")]
6use proptest::prelude::*;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10use super::VersionRequirement;
11
12#[derive(Debug, thiserror::Error)]
14pub enum InvalidVersionError {
15 #[error("invalid digest: {0}")]
16 Digest(&'static str),
17 #[error("invalid semantic version: {0}")]
18 Version(SemVerError),
19}
20
21#[cfg(feature = "arbitrary")]
22impl Arbitrary for InvalidVersionError {
23 type Parameters = ();
24 type Strategy = BoxedStrategy<Self>;
25
26 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
27 any::<bool>()
28 .prop_map(|use_digest| {
29 if use_digest {
30 Self::Digest("invalid digest")
31 } else {
32 Self::Version("not-a-version".parse::<SemVer>().unwrap_err())
33 }
34 })
35 .boxed()
36 }
37}
38
39#[derive(Debug, Clone, Eq, PartialEq)]
55pub struct Version {
56 pub version: SemVer,
60 pub digest: Option<Word>,
65}
66
67#[cfg(feature = "serde")]
68impl Serialize for Version {
69 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70 where
71 S: serde::Serializer,
72 {
73 use alloc::string::ToString;
74
75 serializer.serialize_str(&self.to_string())
76 }
77}
78
79#[cfg(feature = "serde")]
80impl<'de> Deserialize<'de> for Version {
81 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82 where
83 D: serde::Deserializer<'de>,
84 {
85 let value = <alloc::string::String as Deserialize>::deserialize(deserializer)?;
86 value.parse().map_err(serde::de::Error::custom)
87 }
88}
89
90impl Version {
91 pub fn new(version: SemVer, digest: Word) -> Self {
93 Self { version, digest: Some(digest) }
94 }
95
96 pub fn without_digest(&self) -> Self {
98 Self {
99 version: self.version.clone(),
100 digest: None,
101 }
102 }
103
104 pub fn as_range(&self) -> core::ops::Range<Version> {
107 let start = self.without_digest();
108 let mut end = start.clone();
109 end.version.patch += 1;
110
111 start..end
112 }
113
114 pub fn is_semantically_equivalent(&self, other: &Self) -> bool {
116 self.version.cmp_precedence(&other.version).is_eq()
117 }
118
119 pub fn satisfies(&self, requirement: &VersionRequirement) -> bool {
124 match requirement {
125 VersionRequirement::Semantic(req) => req.matches(&self.version),
126 VersionRequirement::Digest(req) => {
127 self.digest.as_ref().is_some_and(|digest| req.into_inner() == *digest)
128 },
129 VersionRequirement::Exact(req) => self == req,
130 }
131 }
132}
133
134impl FromStr for Version {
135 type Err = InvalidVersionError;
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 match s.split_once('#') {
138 Some((v, digest)) => {
139 let v = v.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
140 let digest = Word::parse(digest).map_err(InvalidVersionError::Digest)?;
141 Ok(Self::new(v, digest))
142 },
143 None => {
144 let v = s.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
145 Ok(Self::from(v))
146 },
147 }
148 }
149}
150
151impl From<SemVer> for Version {
152 fn from(version: SemVer) -> Self {
153 Self { version, digest: None }
154 }
155}
156
157impl From<(SemVer, Word)> for Version {
158 fn from(version: (SemVer, Word)) -> Self {
159 let (version, word) = version;
160 Self { version, digest: Some(word) }
161 }
162}
163
164impl Borrow<SemVer> for Version {
165 #[inline(always)]
166 fn borrow(&self) -> &SemVer {
167 &self.version
168 }
169}
170
171impl fmt::Display for Version {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 if let Some(digest) = self.digest.as_ref() {
174 write!(f, "{}#{digest}", self.version)
175 } else {
176 fmt::Display::fmt(&self.version, f)
177 }
178 }
179}
180
181impl PartialOrd for Version {
182 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187impl Ord for Version {
188 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
189 use core::cmp::Ordering;
190 self.version.cmp_precedence(&other.version).then_with(|| {
191 match (self.digest.as_ref(), other.digest.as_ref()) {
192 (None, None) => Ordering::Equal,
193 (Some(l), Some(r)) => l.cmp(r),
194 (None, Some(_)) => Ordering::Less,
195 (Some(_), None) => Ordering::Greater,
196 }
197 })
198 }
199}