Skip to main content

miden_package_registry/
version_requirement.rs

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