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

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