1use std::fmt::{Display, Formatter};
2
3use crate::parse::Error;
4use crate::parse::Error::InvalidPath;
5
6#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
14pub struct Path<'a> {
15 path: &'a str,
16}
17
18impl Default for Path<'static> {
19 fn default() -> Self {
20 Self { path: "/" }
21 }
22}
23
24impl<'a> Path<'a> {
25 pub unsafe fn new(path: &'a str) -> Self {
32 debug_assert!(Self::is_valid(path));
33
34 Self { path }
35 }
36}
37
38impl<'a> TryFrom<&'a str> for Path<'a> {
39 type Error = Error;
40
41 fn try_from(path: &'a str) -> Result<Self, Self::Error> {
42 if Self::is_valid(path) {
43 Ok(Self { path })
44 } else {
45 Err(InvalidPath)
46 }
47 }
48}
49
50impl<'a> Path<'a> {
51 fn is_valid_char(c: u8) -> bool {
55 c.is_ascii_alphanumeric() || (c.is_ascii_punctuation() && c != b'?' && c != b'#')
56 }
57
58 pub fn is_valid(path: &str) -> bool {
60 !path.is_empty()
61 && path.as_bytes()[0] == b'/'
62 && path.as_bytes()[1..].iter().all(|c| Self::is_valid_char(*c))
63 }
64}
65
66impl<'a> Path<'a> {
67 pub const fn as_str(&self) -> &str {
71 self.path
72 }
73}
74
75impl<'a> AsRef<str> for Path<'a> {
76 fn as_ref(&self) -> &str {
77 self.path
78 }
79}
80
81impl<'a> Display for Path<'a> {
82 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83 write!(f, "{}", self.path)
84 }
85}
86
87impl<'a> Path<'a> {
88 pub const fn iter_segments(&self) -> impl Iterator<Item = &'a str> {
95 SegmentIterator {
96 remaining: self.path,
97 }
98 }
99}
100
101struct SegmentIterator<'a> {
102 remaining: &'a str,
103}
104
105impl<'a> Iterator for SegmentIterator<'a> {
106 type Item = &'a str;
107
108 fn next(&mut self) -> Option<Self::Item> {
109 if self.remaining.is_empty() {
110 None
111 } else {
112 self.remaining = &self.remaining[1..];
113 if let Some(slash) = self.remaining.as_bytes().iter().position(|c| *c == b'/') {
114 let segment: &str = &self.remaining[..slash];
115 self.remaining = &self.remaining[slash..];
116 Some(segment)
117 } else {
118 let segment: &str = self.remaining;
119 self.remaining = "";
120 Some(segment)
121 }
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use crate::parse::Error::InvalidPath;
129 use crate::Path;
130
131 #[test]
132 fn new() {
133 let path: Path = unsafe { Path::new("/the/path") };
134 assert_eq!(path.path, "/the/path");
135 }
136
137 #[test]
138 fn default() {
139 let path: Path = Path::default();
140 assert_eq!(path.as_str(), "/");
141 }
142
143 #[test]
144 fn try_from_str() {
145 assert_eq!(Path::try_from("/").unwrap().as_str(), "/");
146 assert_eq!(Path::try_from("/the/path").unwrap().as_str(), "/the/path");
147 assert_eq!(Path::try_from(""), Err(InvalidPath));
148 assert_eq!(Path::try_from("no-slash"), Err(InvalidPath));
149 assert_eq!(Path::try_from("/?"), Err(InvalidPath));
150 assert_eq!(Path::try_from("/#"), Err(InvalidPath));
151 }
152
153 #[test]
154 fn is_valid() {
155 let test_cases: &[(&str, bool)] = &[
156 ("", false),
157 ("/", true),
158 ("///", true),
159 ("/azAZ09", true),
160 ("/!/&/=/~/", true),
161 ("/?", false),
162 ("/#", false),
163 ];
164 for (path, expected) in test_cases {
165 let result: bool = Path::is_valid(path);
166 assert_eq!(result, *expected, "path={}", path);
167 }
168 }
169
170 #[test]
171 fn display() {
172 let path: Path = unsafe { Path::new("/the/path") };
173 assert_eq!(path.as_str(), "/the/path");
174 assert_eq!(path.as_ref(), "/the/path");
175 assert_eq!(path.to_string(), "/the/path");
176 }
177
178 #[test]
179 fn iter_segments() {
180 let path: Path = unsafe { Path::new("/") };
181 let result: Vec<&str> = path.iter_segments().collect();
182 let expected: Vec<&str> = vec![""];
183 assert_eq!(result, expected);
184
185 let path: Path = unsafe { Path::new("/the/path") };
186 let result: Vec<&str> = path.iter_segments().collect();
187 let expected: Vec<&str> = vec!["the", "path"];
188 assert_eq!(result, expected);
189
190 let path: Path = unsafe { Path::new("/the/path/") };
191 let result: Vec<&str> = path.iter_segments().collect();
192 let expected: Vec<&str> = vec!["the", "path", ""];
193 assert_eq!(result, expected)
194 }
195}