1use std::fmt::{Display, Formatter};
2
3use crate::parse::Error;
4use crate::parse::Error::InvalidScheme;
5
6#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
11pub struct Scheme<'a> {
12 scheme: &'a str,
13}
14
15impl<'a> Scheme<'a> {
16 pub unsafe fn new(scheme: &'a str) -> Self {
23 debug_assert!(Self::is_valid(scheme, false));
24
25 Self { scheme }
26 }
27}
28
29impl<'a> TryFrom<&'a str> for Scheme<'a> {
30 type Error = Error;
31
32 fn try_from(scheme: &'a str) -> Result<Self, Self::Error> {
33 if Self::is_valid(scheme, false) {
34 Ok(Self { scheme })
35 } else {
36 Err(InvalidScheme)
37 }
38 }
39}
40
41impl<'a> Scheme<'a> {
42 fn is_valid_first_char(c: u8, ignore_case: bool) -> bool {
46 c.is_ascii_lowercase() || (ignore_case && c.is_ascii_uppercase())
47 }
48
49 fn is_valid_char(c: u8, ignore_case: bool) -> bool {
51 Self::is_valid_first_char(c, ignore_case)
52 || c.is_ascii_digit()
53 || c == b'+'
54 || c == b'-'
55 || c == b'.'
56 }
57
58 pub fn is_valid(scheme: &'a str, ignore_case: bool) -> bool {
60 !scheme.is_empty()
61 && Self::is_valid_first_char(scheme.as_bytes()[0], ignore_case)
62 && scheme.as_bytes()[1..]
63 .iter()
64 .all(|c| Self::is_valid_char(*c, ignore_case))
65 }
66}
67
68impl<'a> Scheme<'a> {
69 pub const fn as_str(&self) -> &str {
73 self.scheme
74 }
75}
76
77impl<'a> AsRef<str> for Scheme<'a> {
78 fn as_ref(&self) -> &str {
79 self.scheme
80 }
81}
82
83impl<'a> Display for Scheme<'a> {
84 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85 write!(f, "{}", self.scheme)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::parse::Error::InvalidScheme;
92 use crate::Scheme;
93
94 #[test]
95 fn new() {
96 let scheme: Scheme = unsafe { Scheme::new("scheme") };
97 assert_eq!(scheme.scheme, "scheme");
98 }
99
100 #[test]
101 fn try_from_str() {
102 assert_eq!(Scheme::try_from("http").unwrap().as_str(), "http");
103 assert_eq!(Scheme::try_from("a0+-.").unwrap().as_str(), "a0+-.");
104 assert_eq!(Scheme::try_from(""), Err(InvalidScheme));
105 assert_eq!(Scheme::try_from("0abc"), Err(InvalidScheme));
106 assert_eq!(Scheme::try_from("a~"), Err(InvalidScheme));
107 assert_eq!(Scheme::try_from("ABC"), Err(InvalidScheme));
108 }
109
110 #[test]
111 fn is_valid() {
112 let test_cases: &[(&str, bool, bool)] = &[
113 ("", false, false),
114 ("A", true, false),
115 ("a", true, true),
116 ("0", false, false),
117 ("a~", false, false),
118 ("az09+-.", true, true),
119 ("azAZ09+-.", true, false),
120 ];
121 for (scheme, expected_ic_true, expected_ic_false) in test_cases {
122 let result: bool = Scheme::is_valid(scheme, true);
123 assert_eq!(result, *expected_ic_true, "scheme={}", scheme);
124
125 let result: bool = Scheme::is_valid(scheme, false);
126 assert_eq!(result, *expected_ic_false, "scheme={}", scheme);
127 }
128 }
129
130 #[test]
131 fn display() {
132 let scheme: Scheme = unsafe { Scheme::new("scheme") };
133 assert_eq!(scheme.as_str(), "scheme");
134 assert_eq!(scheme.as_ref(), "scheme");
135 assert_eq!(scheme.to_string(), "scheme");
136 }
137}