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::{LexicographicWord, Word};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use super::VersionRequirement;
9
10#[derive(Debug, thiserror::Error)]
12pub enum InvalidVersionError {
13 #[error("invalid digest: {0}")]
14 Digest(&'static str),
15 #[error("invalid semantic version: {0}")]
16 Version(SemVerError),
17}
18
19#[derive(Debug, Clone, Eq, PartialEq)]
35pub struct Version {
36 pub version: SemVer,
40 pub digest: Option<LexicographicWord>,
45}
46
47#[cfg(feature = "serde")]
48impl Serialize for Version {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: serde::Serializer,
52 {
53 use alloc::string::ToString;
54
55 serializer.serialize_str(&self.to_string())
56 }
57}
58
59#[cfg(feature = "serde")]
60impl<'de> Deserialize<'de> for Version {
61 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62 where
63 D: serde::Deserializer<'de>,
64 {
65 let value = <alloc::string::String as Deserialize>::deserialize(deserializer)?;
66 value.parse().map_err(serde::de::Error::custom)
67 }
68}
69
70impl Version {
71 pub fn new(version: SemVer, digest: Word) -> Self {
73 Self { version, digest: Some(digest.into()) }
74 }
75
76 pub fn without_digest(&self) -> Self {
78 Self {
79 version: self.version.clone(),
80 digest: None,
81 }
82 }
83
84 pub fn as_range(&self) -> core::ops::Range<Version> {
87 let start = self.without_digest();
88 let mut end = start.clone();
89 end.version.patch += 1;
90
91 start..end
92 }
93
94 pub fn is_semantically_equivalent(&self, other: &Self) -> bool {
96 self.version.cmp_precedence(&other.version).is_eq()
97 }
98
99 pub fn satisfies(&self, requirement: &VersionRequirement) -> bool {
104 match requirement {
105 VersionRequirement::Semantic(req) => req.matches(&self.version),
106 VersionRequirement::Digest(req) => self
107 .digest
108 .as_ref()
109 .is_some_and(|digest| &LexicographicWord::new(req.into_inner()) == digest),
110 VersionRequirement::Exact(req) => self == req,
111 }
112 }
113}
114
115impl FromStr for Version {
116 type Err = InvalidVersionError;
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 match s.split_once('#') {
119 Some((v, digest)) => {
120 let v = v.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
121 let digest = Word::parse(digest).map_err(InvalidVersionError::Digest)?;
122 Ok(Self::new(v, digest))
123 },
124 None => {
125 let v = s.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
126 Ok(Self::from(v))
127 },
128 }
129 }
130}
131
132impl From<SemVer> for Version {
133 fn from(version: SemVer) -> Self {
134 Self { version, digest: None }
135 }
136}
137
138impl From<(SemVer, Word)> for Version {
139 fn from(version: (SemVer, Word)) -> Self {
140 let (version, word) = version;
141 Self { version, digest: Some(word.into()) }
142 }
143}
144
145impl Borrow<SemVer> for Version {
146 #[inline(always)]
147 fn borrow(&self) -> &SemVer {
148 &self.version
149 }
150}
151
152impl fmt::Display for Version {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 if let Some(digest) = self.digest.as_ref() {
155 write!(f, "{}#{}", &self.version, digest.inner())
156 } else {
157 fmt::Display::fmt(&self.version, f)
158 }
159 }
160}
161
162impl PartialOrd for Version {
163 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
164 Some(self.cmp(other))
165 }
166}
167
168impl Ord for Version {
169 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
170 use core::cmp::Ordering;
171 self.version.cmp_precedence(&other.version).then_with(|| {
172 match (self.digest.as_ref(), other.digest.as_ref()) {
173 (None, None) => Ordering::Equal,
174 (Some(l), Some(r)) => l.cmp(r),
175 (None, Some(_)) => Ordering::Less,
176 (Some(_), None) => Ordering::Greater,
177 }
178 })
179 }
180}