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