Skip to main content

compare_version/
impl.rs

1use crate::*;
2
3/// Implements the `Display` trait for `VersionError` to provide user-friendly error messages.
4impl fmt::Display for VersionError {
5    /// Formats the `VersionError` into a human-readable string.
6    ///
7    /// # Arguments
8    ///
9    /// - `&mut fmt::Formatter` - Formatter to write the output to.
10    ///
11    /// # Returns
12    ///
13    /// - `fmt::Result` - Result of the formatting operation.
14    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15        match self {
16            VersionError::ParseError(msg) => write!(f, "Parse error: {msg}"),
17            VersionError::InvalidRangeFormat => {
18                write!(f, "Unsupported range format, only '^' or '~' are supported")
19            }
20            VersionError::MajorVersionError => write!(f, "Major version parsing error"),
21            VersionError::MinorVersionError => write!(f, "Minor version parsing error"),
22            VersionError::PatchVersionError => write!(f, "Patch version parsing error"),
23        }
24    }
25}
26
27impl Version {
28    /// Parses a version from a string.
29    ///
30    /// # Arguments
31    ///
32    /// - `&str` - A string slice that holds the version in the format 'x.y.z'.
33    ///
34    /// # Returns
35    ///
36    /// - `Result<Self, VersionError>` - A `Result` indicating the parsed `Version` struct on success, or a `VersionError` on failure.
37    pub(crate) fn parse(version: &str) -> Result<Self, VersionError> {
38        let mut parts: Vec<&str> = version.split('.').collect();
39        let (patch_part, pre_release) = if let Some(patch_with_prerelease) = parts.pop() {
40            let mut patch_parts = patch_with_prerelease.splitn(2, '-');
41            (
42                patch_parts.next().unwrap_or(""),
43                patch_parts.next().map(|s| s.to_string()),
44            )
45        } else {
46            return Err(VersionError::ParseError(
47                "Version format error, should be in the form 'x.y.z'.".to_string(),
48            ));
49        };
50        let major: u32 = parts
51            .first()
52            .unwrap_or(&"0")
53            .parse::<u32>()
54            .map_err(|_| VersionError::MajorVersionError)?;
55        let minor: u32 = parts
56            .get(1)
57            .unwrap_or(&"0")
58            .parse::<u32>()
59            .map_err(|_| VersionError::MinorVersionError)?;
60        let patch: u32 = patch_part
61            .parse::<u32>()
62            .map_err(|_| VersionError::PatchVersionError)?;
63        Ok(Self {
64            major,
65            minor,
66            patch,
67            pre_release,
68        })
69    }
70}
71
72impl CompareVersion {
73    /// Compares two version strings and returns a `VersionLevel` enum.
74    ///
75    /// # Arguments
76    ///
77    /// - `&str` - The first version string to compare.
78    /// - `&str` - The second version string to compare.
79    ///
80    /// # Returns
81    ///
82    /// - `Result<VersionLevel, VersionError>` - A `Result` indicating the comparison result on success, or a `VersionError` on failure.
83    pub fn compare_version(version1: &str, version2: &str) -> Result<VersionLevel, VersionError> {
84        let v1: Version = Version::parse(version1)?;
85        let v2: Version = Version::parse(version2)?;
86        match v1.cmp(&v2) {
87            std::cmp::Ordering::Greater => Ok(VersionLevel::Greater),
88            std::cmp::Ordering::Less => Ok(VersionLevel::Less),
89            std::cmp::Ordering::Equal => Ok(VersionLevel::Equal),
90        }
91    }
92
93    /// Checks whether a version matches a specified range, supporting `^` and `~` ranges.
94    ///
95    /// # Arguments
96    ///
97    /// - `&str` - The version string to check.
98    /// - `&str` - The version range string to match against.
99    ///
100    /// # Returns
101    ///
102    /// - `Result<bool, VersionError>` - A `Result` indicating whether the version matches the range on success, or a `VersionError` on failure.
103    pub fn matches_version_range(version: &str, range: &str) -> Result<bool, VersionError> {
104        let target_version: Version = Version::parse(version)?;
105        if let Some(stripped_range) = range.strip_prefix('^') {
106            let base_version = Version::parse(stripped_range)?;
107            // `^` indicates major version compatibility
108            Ok(target_version.major == base_version.major
109                && (target_version.minor > base_version.minor
110                    || (target_version.minor == base_version.minor
111                        && target_version >= base_version)))
112        } else if let Some(stripped_range) = range.strip_prefix('~') {
113            let base_version: Version = Version::parse(stripped_range)?;
114            // `~` indicates minor version compatibility
115            Ok(target_version.major == base_version.major
116                && target_version.minor == base_version.minor
117                && target_version >= base_version)
118        } else {
119            Err(VersionError::InvalidRangeFormat)
120        }
121    }
122}