xdid_core/
uri.rs

1#[derive(Copy, Clone, PartialEq, Eq)]
2pub enum Segment {
3    /// segment
4    Base,
5    /// segment-nz
6    Nz,
7    // segment-nz-nc
8    NzNc,
9}
10
11/// Whether the string conforms to a given [Segment], following [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.3).
12pub fn is_segment(value: &str, segment: Segment) -> bool {
13    if (segment == Segment::Nz || segment == Segment::NzNc) && value.is_empty() {
14        return false;
15    }
16
17    let mut processing_pct_encoded = false;
18    let mut pct_encoded_char = 0usize;
19
20    for c in value.chars() {
21        // pct-encoded = "%" HEXDIG HEXDIG
22        if processing_pct_encoded {
23            if pct_encoded_char == 2 {
24                pct_encoded_char = 0;
25                processing_pct_encoded = false;
26            } else {
27                pct_encoded_char += 1;
28
29                if c.is_ascii_hexdigit() {
30                    continue;
31                } else {
32                    return false;
33                }
34            }
35        }
36
37        if c == '%' {
38            processing_pct_encoded = true;
39            continue;
40        }
41
42        if is_unreserved(c) {
43            continue;
44        }
45
46        if is_sub_delim(c) {
47            continue;
48        }
49
50        // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
51        // segment       = *pchar
52        // segment-nz    = 1*pchar
53        // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
54        //            ; non-zero-length segment without any colon ":"
55        if c == '@' {
56            continue;
57        }
58
59        match segment {
60            Segment::Base | Segment::Nz => {
61                if c == ':' {
62                    continue;
63                }
64            }
65            Segment::NzNc => {}
66        }
67
68        return false;
69    }
70
71    true
72}
73
74/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
75fn is_unreserved(c: char) -> bool {
76    c.is_alphanumeric() || c == '-' || c == '.' || c == '_' || c == '~'
77}
78
79/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
80fn is_sub_delim(c: char) -> bool {
81    c == '!'
82        || c == '$'
83        || c == '&'
84        || c == '\''
85        || c == '('
86        || c == ')'
87        || c == '*'
88        || c == '+'
89        || c == ','
90        || c == ';'
91        || c == '='
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_segment_length() {
100        assert!(is_segment("", Segment::Base));
101        assert!(!is_segment("", Segment::Nz));
102        assert!(!is_segment("", Segment::NzNc));
103    }
104
105    #[test]
106    fn test_segment_alphanumeric() {
107        assert!(is_segment(
108            "abcdefghijklmnopqrstuvwxyz0123456789",
109            Segment::Base
110        ));
111    }
112
113    #[test]
114    fn test_segment_symbols() {
115        assert!(is_segment("!$&'()*+,;=@", Segment::Base));
116        assert!(is_segment("!$&'()*+,;=@", Segment::Nz));
117        assert!(is_segment("!$&'()*+,;=@", Segment::NzNc));
118    }
119
120    #[test]
121    fn test_segment_colon() {
122        assert!(is_segment(":", Segment::Base));
123        assert!(is_segment(":", Segment::Nz));
124        assert!(!is_segment(":", Segment::NzNc));
125    }
126
127    #[test]
128    fn test_segment_pct_encode() {
129        assert!(is_segment("%30%f9a", Segment::Base));
130        assert!(!is_segment("%3%f9a", Segment::Base));
131        assert!(!is_segment("%%f9a", Segment::Base));
132    }
133}