Skip to main content

miden_package_registry/
version_requirement.rs

1use core::fmt;
2
3use miden_assembly_syntax::debuginfo::Span;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::*;
8use crate::{LexicographicWord, Word};
9
10/// Represents a requirement on a specific version (or versions) of a dependency.
11#[derive(Debug, Clone)]
12pub enum VersionRequirement {
13    /// A semantic versioning constraint, e.g. `~> 0.1`
14    ///
15    /// In general, this is meant to indicate that any version of a package that satisfies the
16    /// version constraint can be used to resolve the dependency.
17    ///
18    /// This form of constraint also permits us to compile a dependency from source, so long as
19    /// the semantic versioning constraint is satisfied.
20    Semantic(Span<VersionReq>),
21    /// The most precise and onerous form of versioning constraint.
22    ///
23    /// This requires that the dependency's package digest exactly matches the one provided here.
24    ///
25    /// Digest constraints also effectively require that the dependency already be compiled to a
26    /// Miden package, as digests are derived from the MAST of a compiled package. This means that
27    /// when the dependency is resolved, we must be able to find a `.masp` file with the expected
28    /// digest.
29    Digest(Span<Word>),
30    /// Requires an exact assembled package version, including both semantic version and digest.
31    Exact(Version),
32}
33
34impl VersionRequirement {
35    /// Returns true if this version requirement is a semantic versioning requirement
36    pub fn is_semantic_version(&self) -> bool {
37        matches!(self, Self::Semantic(_))
38    }
39
40    /// Returns true if this version requirement requires an exact digest match
41    pub fn is_digest(&self) -> bool {
42        matches!(self, Self::Digest(_))
43    }
44
45    /// Returns true if this version requirement requires an exact assembled version match.
46    pub fn is_exact(&self) -> bool {
47        matches!(self, Self::Exact(_))
48    }
49}
50
51impl Eq for VersionRequirement {}
52
53impl PartialEq for VersionRequirement {
54    fn eq(&self, other: &Self) -> bool {
55        match (self, other) {
56            (Self::Exact(l), Self::Exact(r)) => l == r,
57            (Self::Digest(l), Self::Digest(r)) => {
58                LexicographicWord::new(l.into_inner()) == LexicographicWord::new(r.into_inner())
59            },
60            (Self::Semantic(l), Self::Semantic(r)) => l == r,
61            (Self::Semantic(_) | Self::Exact(_), Self::Digest(_))
62            | (Self::Semantic(_), Self::Exact(_))
63            | (Self::Digest(_), Self::Semantic(_) | Self::Exact(_))
64            | (Self::Exact(_), Self::Semantic(_)) => false,
65        }
66    }
67}
68
69impl fmt::Display for VersionRequirement {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::Semantic(v) => fmt::Display::fmt(v, f),
73            Self::Digest(word) => fmt::Display::fmt(word, f),
74            Self::Exact(version) => {
75                assert!(
76                    version.digest.is_some(),
77                    "exact requirements must include an artifact digest"
78                );
79                write!(f, "{version}")
80            },
81        }
82    }
83}
84
85impl From<VersionReq> for VersionRequirement {
86    fn from(version: VersionReq) -> Self {
87        Self::Semantic(Span::unknown(version))
88    }
89}
90
91impl From<Word> for VersionRequirement {
92    fn from(digest: Word) -> Self {
93        Self::Digest(Span::unknown(digest))
94    }
95}
96
97impl From<Version> for VersionRequirement {
98    fn from(value: Version) -> Self {
99        if value.digest.is_none() {
100            Self::Semantic(Span::unknown(format!("={}", &value.version).parse().unwrap()))
101        } else {
102            Self::Exact(value)
103        }
104    }
105}
106
107#[cfg(feature = "serde")]
108impl Serialize for VersionRequirement {
109    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: serde::Serializer,
112    {
113        use alloc::string::ToString;
114        serializer.serialize_str(&self.to_string())
115    }
116}
117
118#[cfg(feature = "serde")]
119impl<'de> Deserialize<'de> for VersionRequirement {
120    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121    where
122        D: serde::Deserializer<'de>,
123    {
124        use core::str::FromStr;
125
126        let value = <alloc::string::String as Deserialize>::deserialize(deserializer)?;
127
128        if value == "*" {
129            return Ok(Self::from(VersionReq::STAR.clone()));
130        }
131
132        if let Some((version, digest)) = value.split_once('#') {
133            let version = version.parse::<SemVer>().map_err(serde::de::Error::custom)?;
134            let digest = Word::parse(digest).map_err(serde::de::Error::custom)?;
135            return Ok(Self::Exact(Version::new(version, digest)));
136        }
137
138        if let Ok(digest) = Word::parse(&value) {
139            return Ok(Self::from(digest));
140        }
141
142        let requirement = VersionReq::from_str(&value).map_err(serde::de::Error::custom)?;
143        Ok(Self::from(requirement))
144    }
145}