1
//! Keywords in netdocs
2

            
3
use 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)]
16
pub struct KeywordRef<'s>(&'s str);
17

            
18
/// Invalid keyword
19
#[derive(Error, Clone, Copy, Debug, Eq, PartialEq)]
20
#[non_exhaustive]
21
pub 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
34
pub const MAX_LEN: usize = 255;
35

            
36
impl<'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
237962
    pub const fn new_const(s: &'s str) -> Self {
43
        // unwrap_or_else isn't const.  expect isn't const.
44
237962
        match Self::new(s) {
45
237962
            Ok(y) => y,
46
            Err(_e) => panic!("new_const failed"), // can't format error in const
47
        }
48
237962
    }
49

            
50
    /// Make a new `Keyword` from a string, without checking invariants
51
320080
    pub const fn new(s: &'s str) -> Result<Self, InvalidKeyword> {
52
        use InvalidKeyword as IK;
53
320080
        if s.is_empty() {
54
            return Err(IK::Empty);
55
320080
        }
56
320080
        if s.len() > MAX_LEN {
57
            return Err(IK::TooLong);
58
320080
        }
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
320080
            let mut unchecked = s.as_bytes();
65
4511814
            while let Some((h, t)) = unchecked.split_first() {
66
4191734
                if *h == b'\0' {
67
                    return Err(IK::ContainsNul);
68
4191734
                }
69
4191734
                unchecked = t;
70
            }
71
        }
72
320080
        Ok(KeywordRef(s))
73
320080
    }
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
84361
    pub fn as_str(&self) -> &str {
86
84361
        self.0
87
84361
    }
88
    /// Obtain the `Keyword`'s length
89
    #[allow(clippy::len_without_is_empty)] // they can't ever be empty
90
83867
    pub fn len(&self) -> usize {
91
83867
        self.as_str().len()
92
83867
    }
93
}
94

            
95
impl<'s> AsRef<str> for KeywordRef<'s> {
96
    fn as_ref(&self) -> &str {
97
        self.as_str()
98
    }
99
}
100

            
101
// We could implement `PartialEq<str>` instead but that leads to unnatural code like
102
//
103
//   let kw: KeywordRef<'_> = ...;
104
//   if kw == *"expected" { ...
105
//
106
impl PartialEq<&str> for KeywordRef<'_> {
107
448
    fn eq(&self, s: &&str) -> bool {
108
448
        self.as_str() == *s
109
448
    }
110
}