winget_types/installer/
channel.rs

1use core::{fmt, str::FromStr};
2
3use compact_str::CompactString;
4use thiserror::Error;
5
6#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(feature = "serde", serde(try_from = "CompactString"))]
9#[repr(transparent)]
10pub struct Channel(CompactString);
11
12#[derive(Debug, Error, Eq, PartialEq)]
13pub enum ChannelError {
14    #[error("Channel must not be empty")]
15    Empty,
16    #[error(
17        "Channel must not have more than {} characters but has {_0}",
18        Channel::MAX_CHAR_LENGTH
19    )]
20    TooLong(usize),
21}
22
23impl Channel {
24    pub const MAX_CHAR_LENGTH: usize = 16;
25
26    /// Creates a new `Channel` from any type that implements `AsRef<str>` and
27    /// `Into<CompactString>`.
28    ///
29    /// # Errors
30    ///
31    /// Returns an `Err` if the channel is empty or more than 16 characters long.
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// use winget_types::installer::Channel;
37    /// # use winget_types::installer::ChannelError;
38    ///
39    /// # fn main() -> Result<(), ChannelError>  {
40    /// let channel = Channel::new("beta")?;
41    ///
42    /// assert_eq!(channel.as_str(), "beta");
43    /// # Ok(())
44    /// # }
45    /// ```
46    pub fn new<T: AsRef<str> + Into<CompactString>>(channel: T) -> Result<Self, ChannelError> {
47        let channel_str = channel.as_ref();
48
49        if channel_str.is_empty() {
50            return Err(ChannelError::Empty);
51        }
52
53        let char_count = channel_str.chars().count();
54        if char_count > Self::MAX_CHAR_LENGTH {
55            return Err(ChannelError::TooLong(char_count));
56        }
57
58        Ok(Self(channel.into()))
59    }
60
61    /// Creates a new `Channel` from any type that implements `Into<CompactString>` without checking
62    /// its validity.
63    ///
64    /// # Safety
65    ///
66    /// The channel must not be empty or more than 16 characters long.
67    #[must_use]
68    #[inline]
69    pub unsafe fn new_unchecked<T: Into<CompactString>>(channel: T) -> Self {
70        Self(channel.into())
71    }
72
73    /// Extracts a string slice containing the entire `Channel`.
74    #[must_use]
75    #[inline]
76    pub fn as_str(&self) -> &str {
77        self.0.as_str()
78    }
79}
80
81impl AsRef<str> for Channel {
82    #[inline]
83    fn as_ref(&self) -> &str {
84        self.as_str()
85    }
86}
87
88impl fmt::Display for Channel {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        self.0.fmt(f)
91    }
92}
93
94impl FromStr for Channel {
95    type Err = ChannelError;
96
97    #[inline]
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        Self::new(s)
100    }
101}
102
103impl TryFrom<CompactString> for Channel {
104    type Error = ChannelError;
105
106    #[inline]
107    fn try_from(value: CompactString) -> Result<Self, Self::Error> {
108        Self::new(value)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use rstest::rstest;
115
116    use super::{Channel, ChannelError};
117
118    #[rstest]
119    #[case("stable")]
120    #[case("beta")]
121    #[case("dev")]
122    #[case("nightly")]
123    #[case("canary")]
124    fn valid_channel(#[case] channel: &str) {
125        assert!(channel.parse::<Channel>().is_ok());
126    }
127
128    #[test]
129    fn empty_channel() {
130        assert_eq!("".parse::<Channel>(), Err(ChannelError::Empty));
131    }
132
133    #[test]
134    fn channel_too_long() {
135        assert_eq!(
136            "frequent_nightly_builds".parse::<Channel>(),
137            Err(ChannelError::TooLong(23))
138        );
139    }
140}