tor_netdoc/parse2/
keyword.rs

1//! Keywords in netdocs
2
3use super::*;
4
5/// A netdoc keyword
6///
7/// # Safety
8///
9/// Invariants:
10///
11///   * length is between 1 and 255 ([`MAX_LEN`]) inclusive
12///   * there are no nul bytes
13//
14// (These are not currently relied on but may become safety invariants in the future.)
15#[derive(Debug, Clone, Copy, Eq, PartialEq, derive_more::Display)]
16pub struct KeywordRef<'s>(&'s str);
17
18/// Invalid keyword
19#[derive(Error, Clone, Copy, Debug, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum InvalidKeyword {
22    /// Empty keyword
23    #[error("Keyword cannot be empty")]
24    Empty,
25    /// Keyword too long
26    #[error("Keyword longer than {MAX_LEN} bytes")]
27    TooLong,
28    /// Keyword contained nul byte
29    #[error("Keyword contains nul byte")]
30    ContainsNul,
31}
32
33/// Maximum length of a keyword
34pub const MAX_LEN: usize = 255;
35
36impl<'s> KeywordRef<'s> {
37    /// Make a new `Keyword` from a string in const context
38    ///
39    /// # Panics
40    ///
41    /// Panics if the string does not meet the invariants.
42    pub const fn new_const(s: &'s str) -> Self {
43        // unwrap_or_else isn't const.  expect isn't const.
44        match Self::new(s) {
45            Ok(y) => y,
46            Err(_e) => panic!("new_const failed"), // can't format error in const
47        }
48    }
49
50    /// Make a new `Keyword` from a string, without checking invariants
51    pub const fn new(s: &'s str) -> Result<Self, InvalidKeyword> {
52        use InvalidKeyword as IK;
53        if s.is_empty() {
54            return Err(IK::Empty);
55        }
56        if s.len() > MAX_LEN {
57            return Err(IK::TooLong);
58        }
59        // s.as_bytes().contains(&b'0'),
60        // but
61        //   (&[u8]).contains() isn't const
62        //   for b in (&[u8]) isn't const
63        {
64            let mut unchecked = s.as_bytes();
65            while let Some((h, t)) = unchecked.split_first() {
66                if *h == b'0' {
67                    return Err(IK::ContainsNul);
68                }
69                unchecked = t;
70            }
71        }
72        Ok(KeywordRef(s))
73    }
74
75    /// Make a new `Keyword` from a string, without checking invariants
76    ///
77    /// ### Safety
78    ///
79    /// The invariants for [`KeywordRef`] must be satisfied.
80    pub unsafe fn new_unchecked(s: &'s str) -> Self {
81        KeywordRef(s)
82    }
83
84    /// Obtain the `Keyword` as a `str`
85    fn as_str(&self) -> &str {
86        self.0
87    }
88    /// Obtain the `Keyword`'s length
89    #[allow(clippy::len_without_is_empty)] // they can't ever be empty
90    pub fn len(&self) -> usize {
91        self.as_str().len()
92    }
93}
94
95impl<'s> AsRef<str> for KeywordRef<'s> {
96    fn as_ref(&self) -> &str {
97        self.as_str()
98    }
99}