Skip to main content

wslplugins_rs/wsl_version/
semver_impl.rs

1use super::WSLVersion;
2use semver::{BuildMetadata, Prerelease, Version};
3use std::num::TryFromIntError;
4use thiserror::Error;
5
6/// Errors returned when converting a [`semver::Version`] into [`WSLVersion`].
7///
8/// `WSLVersion` only models the numeric `major.minor.revision` components used
9/// by the WSL plugin API. Semantic-versioning pre-release identifiers and build
10/// metadata therefore cannot be represented and are rejected.
11#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum SemverConversionError {
13    /// The semantic version contains a pre-release identifier such as
14    /// `-alpha.1`, which has no equivalent in `WSLVersion`.
15    #[error("semantic versions with pre-release identifiers cannot be converted into WSLVersion")]
16    PrereleaseNotSupported,
17    /// The semantic version contains build metadata such as `+build.1`, which
18    /// has no equivalent in `WSLVersion`.
19    #[error("semantic versions with build metadata cannot be converted into WSLVersion")]
20    BuildMetadataNotSupported,
21    /// One of the numeric version components does not fit into the `u32`
22    /// representation used by `WSLVersion`.
23    #[error("semantic version numeric component exceeds u32 range")]
24    ComponentOutOfRange,
25}
26
27impl From<TryFromIntError> for SemverConversionError {
28    #[inline]
29    fn from(_: TryFromIntError) -> Self {
30        Self::ComponentOutOfRange
31    }
32}
33
34impl From<WSLVersion> for Version {
35    #[inline]
36    fn from(value: WSLVersion) -> Self {
37        Self {
38            major: u64::from(value.major()),
39            minor: u64::from(value.minor()),
40            patch: u64::from(value.revision()),
41            pre: Prerelease::EMPTY,
42            build: BuildMetadata::EMPTY,
43        }
44    }
45}
46
47impl TryFrom<Version> for WSLVersion {
48    type Error = SemverConversionError;
49
50    #[inline]
51    fn try_from(value: Version) -> Result<Self, Self::Error> {
52        if !value.pre.is_empty() {
53            return Err(SemverConversionError::PrereleaseNotSupported);
54        }
55
56        if !value.build.is_empty() {
57            return Err(SemverConversionError::BuildMetadataNotSupported);
58        }
59
60        Ok(Self::new(
61            u32::try_from(value.major)?,
62            u32::try_from(value.minor)?,
63            u32::try_from(value.patch)?,
64        ))
65    }
66}
67
68#[cfg(test)]
69mod tests {
70
71    use super::*;
72    use proptest::prelude::*;
73
74    #[test]
75    fn test_try_from_semver_version_rejects_prerelease() {
76        let semver_version = Version {
77            major: 2,
78            minor: 4,
79            patch: 4,
80            #[allow(clippy::unwrap_used, reason = "test data is valid")]
81            pre: "alpha.1".parse().unwrap(),
82            build: BuildMetadata::EMPTY,
83        };
84
85        let result = WSLVersion::try_from(semver_version);
86
87        assert_eq!(result, Err(SemverConversionError::PrereleaseNotSupported));
88    }
89
90    #[test]
91    fn test_try_from_semver_version_rejects_build_metadata() {
92        let semver_version = Version {
93            major: 2,
94            minor: 4,
95            patch: 4,
96            pre: Prerelease::EMPTY,
97            #[allow(clippy::unwrap_used, reason = "test data is valid")]
98            build: "build.1".parse().unwrap(),
99        };
100
101        let result = WSLVersion::try_from(semver_version);
102
103        assert_eq!(
104            result,
105            Err(SemverConversionError::BuildMetadataNotSupported)
106        );
107    }
108
109    #[test]
110    fn test_try_from_semver_version_rejects_out_of_range_component() {
111        let semver_version = Version::new(u64::from(u32::MAX) + 1, 0, 0);
112
113        let result = WSLVersion::try_from(semver_version);
114
115        assert_eq!(result, Err(SemverConversionError::ComponentOutOfRange));
116    }
117
118    proptest! {
119        #[test]
120        fn proptest_into_semver_preserves_components(
121            major in any::<u32>(),
122            minor in any::<u32>(),
123            revision in any::<u32>(),
124        ) {
125            let version = WSLVersion::new(major, minor, revision);
126            let semver_version: Version = version.into();
127
128            prop_assert_eq!(semver_version.major, u64::from(major));
129            prop_assert_eq!(semver_version.minor, u64::from(minor));
130            prop_assert_eq!(semver_version.patch, u64::from(revision));
131            prop_assert_eq!(semver_version.pre, Prerelease::EMPTY);
132            prop_assert_eq!(semver_version.build, BuildMetadata::EMPTY);
133        }
134
135        #[test]
136        fn proptest_try_from_semver_roundtrip(
137            major in any::<u32>(),
138            minor in any::<u32>(),
139            patch in any::<u32>(),
140        ) {
141            let semver_version = Version::new(
142                u64::from(major),
143                u64::from(minor),
144                u64::from(patch),
145            );
146
147            prop_assert_eq!(
148                WSLVersion::try_from(semver_version),
149                Ok(WSLVersion::new(major, minor, patch))
150            );
151        }
152    }
153}