Skip to main content

wslplugins_rs/
wsl_version.rs

1use std::{
2    fmt::{self, Debug, Display},
3    hash::Hash,
4    ptr,
5    str::FromStr,
6};
7
8mod parse_error;
9pub use parse_error::WSLVersionParseError;
10
11#[cfg(feature = "semver")]
12mod semver_impl;
13#[cfg(feature = "semver")]
14pub use semver_impl::SemverConversionError;
15
16#[cfg(feature = "serde")]
17mod serde_impl;
18
19/// Represents a WSL version number.
20///
21/// This struct wraps the `WSLVersion` from the WSL Plugin API and provides
22/// safe, idiomatic Rust access to its fields.
23/// # Example
24/// ```
25/// use wslplugins_rs::WSLVersion;
26/// let version = WSLVersion::new(2, 0, 0);
27/// assert_eq!(version.major(), 2);
28/// assert_eq!(version.minor(), 0);
29/// assert_eq!(version.revision(), 0);
30/// ```
31#[repr(transparent)]
32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct WSLVersion(wslpluginapi_sys::WSLVersion);
34
35impl WSLVersion {
36    /// Creates a new `WSLVersion` instance.
37    /// # Parameters
38    /// - `major`: The major version number.
39    /// - `minor`: The minor version number.
40    /// - `revision`: The revision number.
41    /// # Returns
42    /// The new `WSLVersion` instance.
43    #[must_use]
44    #[inline]
45    pub const fn new(major: u32, minor: u32, revision: u32) -> Self {
46        Self(wslpluginapi_sys::WSLVersion {
47            Major: major,
48            Minor: minor,
49            Revision: revision,
50        })
51    }
52
53    /// Retrieves the major version number.
54    #[must_use]
55    #[inline]
56    pub const fn major(&self) -> u32 {
57        self.0.Major
58    }
59
60    /// Set the major version number.
61    #[inline]
62    pub const fn set_major(&mut self, major: u32) {
63        self.0.Major = major;
64    }
65
66    /// Retrieves the minor version number.
67    #[must_use]
68    #[inline]
69    pub const fn minor(&self) -> u32 {
70        self.0.Minor
71    }
72
73    /// Set the minor version number.
74    #[inline]
75    pub const fn set_minor(&mut self, minor: u32) {
76        self.0.Minor = minor;
77    }
78
79    /// Retrieves the revision version number.
80    #[must_use]
81    #[inline]
82    pub const fn revision(&self) -> u32 {
83        self.0.Revision
84    }
85
86    /// Set the revision version number.
87    #[inline]
88    pub const fn set_revision(&mut self, revision: u32) {
89        self.0.Revision = revision;
90    }
91}
92
93impl From<wslpluginapi_sys::WSLVersion> for WSLVersion {
94    #[inline]
95    fn from(value: wslpluginapi_sys::WSLVersion) -> Self {
96        Self(value)
97    }
98}
99
100impl From<WSLVersion> for wslpluginapi_sys::WSLVersion {
101    #[inline]
102    fn from(value: WSLVersion) -> Self {
103        value.0
104    }
105}
106
107impl AsRef<WSLVersion> for wslpluginapi_sys::WSLVersion {
108    #[inline]
109    fn as_ref(&self) -> &WSLVersion {
110        // SAFETY: Converting this reference is safe because `WSLVersion` is
111        // `#[repr(transparent)]` over `wslpluginapi_sys::WSLVersion`.
112        unsafe { &*ptr::from_ref(self).cast::<WSLVersion>() }
113    }
114}
115
116impl AsRef<wslpluginapi_sys::WSLVersion> for WSLVersion {
117    #[inline]
118    fn as_ref(&self) -> &wslpluginapi_sys::WSLVersion {
119        &self.0
120    }
121}
122
123impl Display for WSLVersion {
124    #[inline]
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "{}.{}.{}", self.major(), self.minor(), self.revision())
127    }
128}
129
130impl Debug for WSLVersion {
131    #[inline]
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_struct(stringify!(WSLVersion))
134            .field("major", &self.major())
135            .field("minor", &self.minor())
136            .field("revision", &self.revision())
137            .finish()
138    }
139}
140
141impl FromStr for WSLVersion {
142    type Err = WSLVersionParseError;
143
144    #[inline]
145    #[expect(
146        clippy::indexing_slicing,
147        reason = "We check the length of `parts` before indexing it, so this is safe."
148    )]
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        let parts: Vec<&str> = s.split('.').collect();
151        if !matches!(parts.len(), 2 | 3) {
152            return Err(WSLVersionParseError::InvalidFormat {
153                input: s.to_owned(),
154            });
155        }
156
157        let major = parts[0]
158            .parse::<u32>()
159            .map_err(|_| WSLVersionParseError::InvalidMajor {
160                input: s.to_owned(),
161            })?;
162        let minor = parts[1]
163            .parse::<u32>()
164            .map_err(|_| WSLVersionParseError::InvalidMinor {
165                input: s.to_owned(),
166            })?;
167        let revision = parts
168            .get(2)
169            .map(|s| s.parse::<u32>())
170            .transpose()
171            .map_err(|_| WSLVersionParseError::InvalidRevision {
172                input: s.to_owned(),
173            })?
174            .unwrap_or(0);
175
176        Ok(Self::new(major, minor, revision))
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::utils::test_transparence;
184
185    #[test]
186    fn test_layouts() {
187        test_transparence::<wslpluginapi_sys::WSLVersion, WSLVersion>();
188    }
189
190    #[test]
191    fn test_from_str_rejects_invalid_format() {
192        let result = "2".parse::<WSLVersion>();
193
194        assert_eq!(
195            result,
196            Err(WSLVersionParseError::InvalidFormat {
197                input: "2".to_owned(),
198            })
199        );
200    }
201
202    #[test]
203    fn test_from_str_rejects_invalid_major() {
204        let result = "a.0.0".parse::<WSLVersion>();
205
206        assert_eq!(
207            result,
208            Err(WSLVersionParseError::InvalidMajor {
209                input: "a.0.0".to_owned(),
210            })
211        );
212    }
213
214    #[test]
215    fn test_from_str_rejects_invalid_minor() {
216        let result = "2.a.0".parse::<WSLVersion>();
217
218        assert_eq!(
219            result,
220            Err(WSLVersionParseError::InvalidMinor {
221                input: "2.a.0".to_owned(),
222            })
223        );
224    }
225
226    #[test]
227    fn test_from_str_rejects_invalid_revision() {
228        let result = "2.0.a".parse::<WSLVersion>();
229
230        assert_eq!(
231            result,
232            Err(WSLVersionParseError::InvalidRevision {
233                input: "2.0.a".to_owned(),
234            })
235        );
236    }
237}