typed_oid/
prefix.rs

1use std::{
2    fmt,
3    hash::{Hash, Hasher},
4    str::FromStr,
5};
6
7use smallvec::SmallVec;
8
9use crate::error::{Error, Result};
10
11#[inline]
12pub(crate) fn valid_prefix_char(c: u8) -> bool {
13    (c > b'/' && c < b':') || (c > b'`' && c < b'{') || (c > b'@' && c < b'[')
14}
15
16#[cfg(test)]
17mod valid_prefix_char_tests {
18    use super::*;
19
20    #[test]
21    fn valid() {
22        assert!(valid_prefix_char(b'0'));
23        assert!(valid_prefix_char(b'9'));
24        assert!(valid_prefix_char(b'A'));
25        assert!(valid_prefix_char(b'Z'));
26        assert!(valid_prefix_char(b'a'));
27        assert!(valid_prefix_char(b'z'));
28    }
29
30    #[test]
31    fn invalid() {
32        assert!(!valid_prefix_char(b'/'));
33        assert!(!valid_prefix_char(b':'));
34        assert!(!valid_prefix_char(b'`'));
35        assert!(!valid_prefix_char(b'{'));
36        assert!(!valid_prefix_char(b'@'));
37        assert!(!valid_prefix_char(b'['));
38    }
39}
40
41/// An Object ID Prefix designed to be similar to a human readable "subject
42/// line" for the OID
43///
44/// The prefix can store up to 8 bytes of 7-bit ASCII characters inline; a
45/// prefix of longer than 8 bytes will be "spilled" to the heap
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Prefix {
48    bytes: SmallVec<[u8; 8]>,
49}
50
51impl Prefix {
52    /// Create a Prefix from a slice of bytes. The bytes must be ASCII values of
53    /// `0-9`, `A-Z`, or `a-z`, additionally the byte slice length must be
54    /// equal to the prefix length.
55    pub fn from_slice(slice: &[u8]) -> Result<Self> {
56        // Checking for ASCII 0-9,A-Z,a-z
57        if !slice.iter().all(|&c| valid_prefix_char(c)) {
58            return Err(Error::InvalidPrefix {
59                valid_until: slice
60                    .iter()
61                    .enumerate()
62                    .find(|(_i, &c)| !valid_prefix_char(c))
63                    .map(|(i, _)| i)
64                    .unwrap(),
65            });
66        }
67        Ok(Self::from_slice_unchecked(slice))
68    }
69
70    /// Create a Prefix from a slice of bytes without checking the length or
71    /// validity of the bytes
72    pub fn from_slice_unchecked(slice: &[u8]) -> Self {
73        Self {
74            bytes: SmallVec::from_slice(slice),
75        }
76    }
77}
78
79impl fmt::Display for Prefix {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        // SAFETY: self.bytes must not contain any invalid UTF-8. We don't expose the
82        // inner byte array for manipulation, and the only way to construct self
83        // checks for a subset of 7-bit ASCII which itself is a subset of UTF-8
84        unsafe {
85            write!(
86                f,
87                "{}",
88                std::str::from_utf8_unchecked(self.bytes.as_slice())
89            )
90        }
91    }
92}
93
94impl FromStr for Prefix {
95    type Err = Error;
96
97    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Self::from_slice(s.as_bytes()) }
98}
99
100impl TryFrom<&[u8]> for Prefix {
101    type Error = Error;
102
103    fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> { Self::from_slice(slice) }
104}
105
106impl TryFrom<&str> for Prefix {
107    type Error = Error;
108
109    fn try_from(s: &str) -> std::result::Result<Self, Self::Error> { s.parse() }
110}
111
112impl Hash for Prefix {
113    fn hash<H: Hasher>(&self, state: &mut H) { self.bytes.hash(state); }
114}
115
116#[cfg(test)]
117mod prefix_tests {
118    use super::*;
119    use smallvec::smallvec;
120
121    #[test]
122    fn from_str() {
123        let pfx = "PFX".parse::<Prefix>();
124        assert!(pfx.is_ok());
125        assert_eq!(
126            pfx.unwrap(),
127            Prefix {
128                bytes: smallvec![b'P', b'F', b'X']
129            }
130        );
131    }
132
133    #[test]
134    fn from_str_err_char() {
135        let pfx = "PF[".parse::<Prefix>();
136        assert!(pfx.is_err());
137        assert_eq!(pfx.unwrap_err(), Error::InvalidPrefix { valid_until: 2 });
138    }
139
140    #[test]
141    fn from_str_mixedcase() {
142        let pfx = "PFx".parse::<Prefix>();
143        assert!(pfx.is_ok());
144        assert_eq!(
145            pfx.unwrap(),
146            Prefix {
147                bytes: smallvec![b'P', b'F', b'x']
148            }
149        );
150    }
151
152    #[test]
153    fn from_slice() {
154        let arr: [u8; 3] = [b'P', b'F', b'X'];
155        let pfx = Prefix::from_slice(arr.as_slice());
156        assert!(pfx.is_ok());
157        assert_eq!(
158            pfx.unwrap(),
159            Prefix {
160                bytes: SmallVec::from_slice(&arr)
161            }
162        );
163    }
164
165    #[test]
166    fn from_slice_err_char() {
167        let arr: [u8; 3] = [b'P', b'F', b']'];
168        let pfx = Prefix::from_slice(arr.as_slice());
169        assert!(pfx.is_err());
170        assert_eq!(pfx.unwrap_err(), Error::InvalidPrefix { valid_until: 2 });
171    }
172
173    #[test]
174    fn from_slice_mixedcase() {
175        let arr: [u8; 3] = [b'P', b'F', b'x'];
176        let pfx = Prefix::from_slice(arr.as_slice());
177        assert!(pfx.is_ok());
178        assert_eq!(
179            pfx.unwrap(),
180            Prefix {
181                bytes: smallvec![b'P', b'F', b'x']
182            }
183        );
184    }
185
186    #[test]
187    fn to_string() {
188        let pfx: Prefix = "PFx".parse().unwrap();
189        assert_eq!("PFx".to_string(), pfx.to_string());
190    }
191}