tablo_core/declaration/
segment.rs1use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum SegmentFault {
9 Empty,
11 Dot,
13 Char(char),
16}
17
18impl fmt::Display for SegmentFault {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 match self {
21 Self::Empty => f.write_str("is empty"),
22 Self::Dot => f.write_str("is '.' or '..'"),
23 Self::Char(c) => write!(
24 f,
25 "contains {c:?}: quotes, backslashes, control characters, whitespace, URL \
26 punctuation and the route pattern characters '{{', '}}', '(' and ')' are refused"
27 ),
28 }
29 }
30}
31
32pub(crate) const fn segment_fault(segment: &str) -> Option<SegmentFault> {
36 let bytes = segment.as_bytes();
37 if bytes.is_empty() {
38 return Some(SegmentFault::Empty);
39 }
40 if matches!(bytes, b"." | b"..") {
41 return Some(SegmentFault::Dot);
42 }
43 let mut at = 0;
44 while at < bytes.len() {
45 let (c, width) = decode_char(bytes, at);
46 if c.is_control()
47 || c.is_whitespace()
48 || matches!(
49 c,
50 '"' | '\\' | '/' | '?' | '#' | '%' | '&' | '=' | '{' | '}' | '(' | ')'
51 )
52 {
53 return Some(SegmentFault::Char(c));
54 }
55 at += width;
56 }
57 None
58}
59
60const fn decode_char(bytes: &[u8], at: usize) -> (char, usize) {
62 let lead = bytes[at] as u32;
63 let (code, width) = if lead < 0x80 {
64 (lead, 1)
65 } else if lead < 0xE0 {
66 ((lead & 0x1F) << 6 | tail(bytes, at + 1), 2)
67 } else if lead < 0xF0 {
68 (
69 (lead & 0x0F) << 12 | tail(bytes, at + 1) << 6 | tail(bytes, at + 2),
70 3,
71 )
72 } else {
73 (
74 (lead & 0x07) << 18
75 | tail(bytes, at + 1) << 12
76 | tail(bytes, at + 2) << 6
77 | tail(bytes, at + 3),
78 4,
79 )
80 };
81 match char::from_u32(code) {
82 Some(c) => (c, width),
83 None => panic!("a str holds valid UTF-8"),
84 }
85}
86
87const fn tail(bytes: &[u8], at: usize) -> u32 {
89 bytes[at] as u32 & 0x3F
90}