Skip to main content

wslplugins_rs/api/errors/require_update_error/
requirement_definition.rs

1use crate::{WSLVersion, WSLVersionCapability};
2use std::collections::HashSet;
3use std::fmt::{self, Display};
4use std::hash::{Hash, Hasher};
5use strum::IntoEnumIterator as _;
6
7/// Defines what WSL Plugin API support is required.
8///
9/// A requirement can be expressed either as an explicit API version or as one
10/// or more named capabilities. Capability requirements are resolved to the
11/// highest minimum version required by the listed capabilities.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum RequirementDefinition {
14    /// Requires an explicit WSL Plugin API version.
15    Version(WSLVersion),
16    /// Requires one or more WSL Plugin API capabilities.
17    Capabilities(HashSet<WSLVersionCapability>),
18}
19
20impl RequirementDefinition {
21    /// Returns the minimum WSL Plugin API version required by this definition.
22    #[must_use]
23    #[inline]
24    pub fn version(&self) -> WSLVersion {
25        match self {
26            Self::Version(version) => *version,
27            Self::Capabilities(capabilities) => capabilities
28                .iter()
29                .copied()
30                .map(WSLVersionCapability::required_version)
31                .max()
32                .unwrap_or(WSLVersion::new(0, 0, 0)),
33        }
34    }
35}
36
37impl From<WSLVersion> for RequirementDefinition {
38    #[inline]
39    fn from(value: WSLVersion) -> Self {
40        Self::Version(value)
41    }
42}
43
44impl From<WSLVersionCapability> for RequirementDefinition {
45    #[inline]
46    fn from(value: WSLVersionCapability) -> Self {
47        Self::Capabilities(HashSet::from([value]))
48    }
49}
50
51impl From<HashSet<WSLVersionCapability>> for RequirementDefinition {
52    #[inline]
53    fn from(value: HashSet<WSLVersionCapability>) -> Self {
54        Self::Capabilities(value)
55    }
56}
57
58impl<const N: usize> From<[WSLVersionCapability; N]> for RequirementDefinition {
59    #[inline]
60    fn from(value: [WSLVersionCapability; N]) -> Self {
61        Self::Capabilities(HashSet::from(value))
62    }
63}
64
65impl FromIterator<WSLVersionCapability> for RequirementDefinition {
66    #[inline]
67    fn from_iter<T: IntoIterator<Item = WSLVersionCapability>>(iter: T) -> Self {
68        Self::Capabilities(iter.into_iter().collect())
69    }
70}
71
72impl Hash for RequirementDefinition {
73    #[inline]
74    fn hash<H: Hasher>(&self, state: &mut H) {
75        match self {
76            Self::Version(version) => {
77                0_u8.hash(state);
78                version.hash(state);
79            }
80            Self::Capabilities(capabilities) => {
81                1_u8.hash(state);
82                for capability in WSLVersionCapability::iter()
83                    .filter(|capability| capabilities.contains(capability))
84                {
85                    capability.hash(state);
86                }
87            }
88        }
89    }
90}
91
92impl Display for RequirementDefinition {
93    #[inline]
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::Version(version) => write!(f, "version {version}"),
97            Self::Capabilities(capabilities) => {
98                write!(f, "capabilities ")?;
99                for (index, capability) in WSLVersionCapability::iter()
100                    .filter(|capability| capabilities.contains(capability))
101                    .enumerate()
102                {
103                    if index > 0 {
104                        write!(f, ", ")?;
105                    }
106                    write!(f, "{capability}")?;
107                }
108                Ok(())
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use proptest::prelude::*;
118    use proptest::sample::select;
119
120    fn arb_wsl_version() -> impl Strategy<Value = WSLVersion> {
121        (any::<u32>(), any::<u32>(), any::<u32>())
122            .prop_map(|(major, minor, revision)| WSLVersion::new(major, minor, revision))
123    }
124
125    fn arb_capability() -> impl Strategy<Value = WSLVersionCapability> {
126        select(WSLVersionCapability::iter().collect::<Vec<_>>())
127    }
128
129    #[test]
130    fn version_requirement_returns_explicit_version() {
131        let version = WSLVersion::new(2, 1, 2);
132        let requirement = RequirementDefinition::from(version);
133
134        assert_eq!(requirement.version(), version);
135    }
136
137    #[test]
138    fn capability_requirement_returns_highest_required_version() {
139        let requirement = RequirementDefinition::from([
140            WSLVersionCapability::DistributionInitPid,
141            WSLVersionCapability::DistributionVersion,
142            WSLVersionCapability::DistributionVersion,
143        ]);
144
145        assert_eq!(requirement.version(), WSLVersion::new(2, 4, 4));
146        assert_eq!(
147            requirement,
148            RequirementDefinition::Capabilities(HashSet::from([
149                WSLVersionCapability::DistributionInitPid,
150                WSLVersionCapability::DistributionVersion,
151            ]))
152        );
153    }
154
155    proptest! {
156        #[test]
157        fn version_requirement_always_returns_explicit_version(version in arb_wsl_version()) {
158            let requirement = RequirementDefinition::from(version);
159
160            prop_assert_eq!(requirement.version(), version);
161        }
162
163        #[test]
164        fn capability_requirement_returns_maximum_required_version(
165            capabilities in proptest::collection::hash_set(arb_capability(), 0..=6),
166        ) {
167            let expected = capabilities
168                .iter()
169                .copied()
170                .map(WSLVersionCapability::required_version)
171                .max()
172                .unwrap_or(WSLVersion::new(0, 0, 0));
173            let requirement = RequirementDefinition::from(capabilities);
174
175            prop_assert_eq!(requirement.version(), expected);
176        }
177    }
178}