Skip to main content

miden_project/dependencies/
version.rs

1use core::{borrow::Borrow, fmt, str::FromStr};
2
3pub use miden_assembly_syntax::semver::{Error as SemVerError, Version as SemVer, VersionReq};
4use miden_core::{LexicographicWord, Word};
5
6use super::VersionRequirement;
7
8/// The error type raised when attempting to parse a [Version] from a string.
9#[derive(Debug, thiserror::Error)]
10pub enum InvalidVersionError {
11    #[error("invalid digest: {0}")]
12    Digest(&'static str),
13    #[error("invalid semantic version: {0}")]
14    Version(SemVerError),
15}
16
17/// The representation of versioning information associated with packages in the package index.
18///
19/// This type provides the means by which dependency resolution can satisfy versioning constraints
20/// on packages using either semantic version constraints or explicit package digests
21/// simultaneously.
22///
23/// All packages have an associated semantic version. Packages which have been assembled to MAST,
24/// also have an associated content digest. However, for the purposes of indexing and dependency
25/// resolution, we cannot assume that all packages have a content digest (as they may not have been
26/// assembled yet), and so this type is used to represent versions within the index/resolver so that
27/// it can:
28///
29/// * Satisfy requirements for a package that has a specific digest
30/// * Record multiple entries in the index for the same semantic version string, when multiple
31///   assembled packages with that version are present, disambiguating using the content digest.
32/// * Provide a total ordering for package versions that may or may not include a specific digest
33#[derive(Debug, Clone)]
34pub struct Version {
35    /// The semantic version information
36    ///
37    /// This is the canonical human-facing version for a package.
38    pub version: SemVer,
39    /// The content digest for this version, if known.
40    ///
41    /// This is the most precise version for a package, and is used to disambiguate multiple
42    /// instances of a package with the same semantic version, but differing content.
43    pub digest: Option<LexicographicWord>,
44}
45
46impl Version {
47    /// Construct a [Version] from its component parts.
48    pub fn new(version: SemVer, digest: Word) -> Self {
49        Self { version, digest: Some(digest.into()) }
50    }
51
52    /// Check if this version satisfies the given `requirement`.
53    ///
54    /// Version requirements are expressed as either a semantic version constraint OR a specific
55    /// content digest.
56    pub fn satisfies(&self, requirement: &VersionRequirement) -> bool {
57        match requirement {
58            VersionRequirement::Semantic(req) => req.matches(&self.version),
59            VersionRequirement::Digest(req) => self
60                .digest
61                .as_ref()
62                .is_some_and(|digest| &LexicographicWord::new(req.into_inner()) == digest),
63        }
64    }
65}
66
67impl FromStr for Version {
68    type Err = InvalidVersionError;
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        match s.split_once('@') {
71            Some((v, digest)) => {
72                let v = v.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
73                let digest = Word::parse(digest).map_err(InvalidVersionError::Digest)?;
74                Ok(Self::new(v, digest))
75            },
76            None => {
77                let v = s.parse::<SemVer>().map_err(InvalidVersionError::Version)?;
78                Ok(Self::from(v))
79            },
80        }
81    }
82}
83
84impl From<SemVer> for Version {
85    fn from(version: SemVer) -> Self {
86        Self { version, digest: None }
87    }
88}
89
90impl From<(SemVer, Word)> for Version {
91    fn from(version: (SemVer, Word)) -> Self {
92        let (version, word) = version;
93        Self { version, digest: Some(word.into()) }
94    }
95}
96
97impl Borrow<SemVer> for Version {
98    #[inline(always)]
99    fn borrow(&self) -> &SemVer {
100        &self.version
101    }
102}
103
104impl fmt::Display for Version {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        if let Some(digest) = self.digest.as_ref() {
107            write!(f, "{}@{}", &self.version, digest.inner())
108        } else {
109            fmt::Display::fmt(&self.version, f)
110        }
111    }
112}
113
114impl Eq for Version {}
115impl PartialEq for Version {
116    fn eq(&self, other: &Self) -> bool {
117        if self.version != other.version {
118            return false;
119        }
120        if let Some(l) = self.digest.as_ref()
121            && let Some(r) = other.digest.as_ref()
122        {
123            l == r
124        } else {
125            true
126        }
127    }
128}
129
130impl PartialOrd for Version {
131    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
132        Some(self.cmp(other))
133    }
134}
135
136impl Ord for Version {
137    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
138        use core::cmp::Ordering;
139        self.version.cmp_precedence(&other.version).then_with(|| {
140            if let Some(l) = self.digest.as_ref()
141                && let Some(r) = other.digest.as_ref()
142            {
143                l.cmp(r)
144            } else {
145                Ordering::Equal
146            }
147        })
148    }
149}