Skip to main content

wslplugins_rs/
distribution_id.rs

1//! Distribution identifiers used by the crate.
2//!
3//! [`DistributionID`] models the two kinds of distributions exposed by WSL:
4//! the shared system distribution and user-installed distributions identified by
5//! a [`UserDistributionID`].
6//!
7//! The module also exposes the conversions commonly needed by the API surface:
8//!
9//! - converting from a [`UserDistributionID`] or `Option<UserDistributionID>`
10//!   into a [`DistributionID`],
11//! - converting a [`DistributionID`] back into `Option<UserDistributionID>`,
12//! - retrieving a distribution identifier from any
13//!   [`CoreWSLDistributionInformation`] implementation.
14//!
15//! When a caller needs a user distribution identifier and receives
16//! [`DistributionID::System`] instead, [`UserDistributionIDConversionError`] is returned.
17
18use crate::{CoreWSLDistributionInformation, UserDistributionID};
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21use std::fmt::Display;
22
23pub use crate::user_distribution_id::UserDistributionIDConversionError;
24
25/// Represents a distribution identifier in the Windows Subsystem for Linux (WSL).
26///
27/// A distribution can either be the system-level distribution or a user-specific distribution
28/// identified by a [`UserDistributionID`].
29///
30/// ## Variants
31///
32/// - `System`: Represents the system distribution, a central distribution used by WSL
33///   for managing low-level functionalities such as audio and graphical interaction.
34///   Refer to the [WSLg Architecture blogpost](https://devblogs.microsoft.com/commandline/wslg-architecture/#system-distro).
35///
36/// - `User(UserDistributionID)`: Represents an individual distribution installed by a user. Each distribution
37///   is uniquely identified by a [`UserDistributionID`], which is consistent across reboots.
38///
39/// ## Note
40///
41/// - The system distribution serves as a foundational component in WSL, often interacting with
42///   user distributions for operations like Linux GUI apps.
43/// - User distributions provide isolated environments for specific Linux distributions, allowing
44///   users to install and run various Linux distributions on their Windows machines.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub enum DistributionID {
48    /// Represents the system-level distribution.
49    /// For more info about the system distribution please check the [WSLg architecture blogpost](https://devblogs.microsoft.com/commandline/wslg-architecture/#system-distro)
50    System,
51    /// Represents an installed user-specific distribution identified by a [`UserDistributionID`].
52    User(UserDistributionID),
53}
54
55impl DistributionID {
56    /// Checks if the distribution is a distribution installed by the user.
57    #[must_use]
58    #[inline]
59    pub const fn is_user(&self) -> bool {
60        matches!(*self, Self::User(_))
61    }
62    /// Checks if the distribution is the system distribution.
63    #[must_use]
64    #[inline]
65    pub const fn is_system(&self) -> bool {
66        matches!(*self, Self::System)
67    }
68}
69
70impl From<UserDistributionID> for DistributionID {
71    #[inline]
72    fn from(value: UserDistributionID) -> Self {
73        Self::User(value)
74    }
75}
76
77impl From<&UserDistributionID> for DistributionID {
78    #[inline]
79    fn from(value: &UserDistributionID) -> Self {
80        Self::User(*value)
81    }
82}
83
84impl<T: CoreWSLDistributionInformation> From<&T> for DistributionID {
85    /// Converts a reference to a type implementing `CoreWSLDistributionInformation` into a `DistributionID`.
86    #[inline]
87    fn from(value: &T) -> Self {
88        value.id().into()
89    }
90}
91
92impl From<Option<UserDistributionID>> for DistributionID {
93    /// Converts an `Option<GUID>` into a `DistributionID`, defaulting to `System` if `None`.
94    #[inline]
95    fn from(value: Option<UserDistributionID>) -> Self {
96        value.map_or(Self::System, Self::User)
97    }
98}
99
100impl From<DistributionID> for Option<UserDistributionID> {
101    /// Converts a `DistributionID` into an `Option<GUID>`.
102    #[inline]
103    fn from(value: DistributionID) -> Self {
104        match value {
105            DistributionID::System => None,
106            DistributionID::User(id) => Some(id),
107        }
108    }
109}
110
111impl Display for DistributionID {
112    /// Formats the `DistributionID` for display.
113    ///
114    /// Displays "System" for the system-level distribution, or the GUID for a user-specific distribution.
115    #[inline]
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self {
118            Self::System => f.write_str("System"),
119            Self::User(id) => std::fmt::Display::fmt(id, f),
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::api::errors::require_update_error::Result;
128    use proptest::prelude::*;
129    use std::ffi::OsString;
130
131    #[derive(Clone, Copy)]
132    struct TestDistribution {
133        id: UserDistributionID,
134    }
135
136    impl CoreWSLDistributionInformation for TestDistribution {
137        fn id(&self) -> UserDistributionID {
138            self.id
139        }
140
141        fn name(&self) -> OsString {
142            OsString::from("test")
143        }
144
145        fn package_family_name(&self) -> Option<OsString> {
146            None
147        }
148
149        fn flavor(&self) -> Result<Option<OsString>> {
150            Ok(None)
151        }
152
153        fn version(&self) -> Result<Option<OsString>> {
154            Ok(None)
155        }
156    }
157
158    #[test]
159    fn is_system_returns_true_for_system_distribution() {
160        assert!(DistributionID::System.is_system());
161    }
162
163    #[test]
164    fn is_user_returns_false_for_system_distribution() {
165        assert!(!DistributionID::System.is_user());
166    }
167
168    #[test]
169    fn from_none_returns_system() {
170        assert_eq!(
171            DistributionID::from(Option::<UserDistributionID>::None),
172            DistributionID::System
173        );
174    }
175
176    #[test]
177    fn option_from_system_returns_none() {
178        let maybe_user: Option<UserDistributionID> = DistributionID::System.into();
179        assert_eq!(maybe_user, None);
180    }
181
182    #[test]
183    fn display_for_system_is_system() {
184        assert_eq!(format!("{}", DistributionID::System), "System");
185    }
186
187    proptest! {
188        #[test]
189        fn is_system_returns_false_for_user_distribution(raw_id in any::<u128>()) {
190            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
191
192            prop_assert!(!DistributionID::User(user_id).is_system());
193        }
194
195        #[test]
196        fn is_user_returns_true_for_user_distribution(raw_id in any::<u128>()) {
197            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
198
199            prop_assert!(DistributionID::User(user_id).is_user());
200        }
201
202        #[test]
203        fn try_from_user_returns_inner_id(raw_id in any::<u128>()) {
204            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
205
206            prop_assert_eq!(
207                UserDistributionID::try_from(DistributionID::User(user_id)),
208                Ok(user_id)
209            );
210        }
211
212        #[test]
213        fn from_user_distribution_id_wraps_user_variant(raw_id in any::<u128>()) {
214            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
215
216            prop_assert_eq!(DistributionID::from(user_id), DistributionID::User(user_id));
217        }
218
219        #[test]
220        fn from_user_distribution_id_reference_wraps_user_variant(raw_id in any::<u128>()) {
221            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
222
223            prop_assert_eq!(DistributionID::from(&user_id), DistributionID::User(user_id));
224        }
225
226        #[test]
227        fn from_some_user_distribution_id_wraps_user_variant(raw_id in any::<u128>()) {
228            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
229
230            prop_assert_eq!(
231                DistributionID::from(Some(user_id)),
232                DistributionID::User(user_id)
233            );
234        }
235
236        #[test]
237        fn option_from_user_distribution_returns_some_inner_id(raw_id in any::<u128>()) {
238            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
239            let maybe_user: Option<UserDistributionID> = DistributionID::User(user_id).into();
240
241            prop_assert_eq!(maybe_user, Some(user_id));
242        }
243
244        #[test]
245        fn display_for_user_delegates_to_user_distribution_id(raw_id in any::<u128>()) {
246            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
247
248            prop_assert_eq!(
249                format!("{}", DistributionID::User(user_id)),
250                format!("{user_id}")
251            );
252        }
253
254        #[test]
255        fn from_core_distribution_information_uses_the_underlying_id(raw_id in any::<u128>()) {
256            let user_id = UserDistributionID::from(windows_core::GUID::from_u128(raw_id));
257            let distribution = TestDistribution { id: user_id };
258
259            prop_assert_eq!(DistributionID::from(&distribution), DistributionID::User(user_id));
260        }
261    }
262}