iri_rs_core/
components.rs1use std::ops::Deref;
4
5use crate::types::{Iri, IriRef, Uri, UriRef};
6
7#[inline]
8pub fn path_segments(path: &str) -> PathSegments<'_> {
9 let rest = path.strip_prefix('/').unwrap_or(path);
10 PathSegments {
11 inner: if rest.is_empty() {
12 None
13 } else {
14 Some(rest.split('/'))
15 },
16 }
17}
18
19pub struct PathSegments<'a> {
20 inner: Option<std::str::Split<'a, char>>,
21}
22
23impl<'a> Iterator for PathSegments<'a> {
24 type Item = &'a str;
25 #[inline]
26 fn next(&mut self) -> Option<&'a str> {
27 self.inner.as_mut()?.next()
28 }
29}
30
31impl<'a> DoubleEndedIterator for PathSegments<'a> {
32 #[inline]
33 fn next_back(&mut self) -> Option<&'a str> {
34 self.inner.as_mut()?.next_back()
35 }
36}
37
38#[inline]
39pub fn path_is_absolute(path: &str) -> bool {
40 path.starts_with('/')
41}
42
43pub fn normalize_path(path: &str) -> String {
44 if memchr::memchr(b'.', path.as_bytes()).is_none() {
45 return path.to_owned();
46 }
47 let mut input = path;
48 let mut out = String::with_capacity(path.len());
49
50 while !input.is_empty() {
51 if let Some(rest) = input.strip_prefix("../") {
52 input = rest;
53 } else if let Some(rest) = input.strip_prefix("./") {
54 input = rest;
55 } else if input.starts_with("/./") {
56 input = &input[2..];
57 } else if input == "/." {
58 input = "/";
59 } else if input.starts_with("/../") {
60 input = &input[3..];
61 pop_to_slash(&mut out);
62 } else if input == "/.." {
63 input = "/";
64 pop_to_slash(&mut out);
65 } else if input == "." || input == ".." {
66 input = "";
67 } else {
68 let rest = if let Some(r) = input.strip_prefix('/') {
69 out.push('/');
70 r
71 } else {
72 input
73 };
74 let end = memchr::memchr(b'/', rest.as_bytes()).unwrap_or(rest.len());
75 out.push_str(&rest[..end]);
76 input = &rest[end..];
77 }
78 }
79 out
80}
81
82fn pop_to_slash(out: &mut String) {
83 if let Some(i) = memchr::memrchr(b'/', out.as_bytes()) {
84 out.truncate(i);
85 } else {
86 out.clear();
87 }
88}
89
90pub fn split_authority(authority: &str) -> (Option<&str>, &str, Option<&str>) {
91 let (user_info, rest) = match memchr::memchr(b'@', authority.as_bytes()) {
92 Some(i) => (Some(&authority[..i]), &authority[i + 1..]),
93 None => (None, authority),
94 };
95 let (host, port) = if let Some(rest_in) = rest.strip_prefix('[') {
96 if let Some(end) = memchr::memchr(b']', rest_in.as_bytes()) {
97 let host = &rest[..end + 2];
98 let tail = &rest[end + 2..];
99 match tail.strip_prefix(':') {
100 Some(p) => (host, Some(p)),
101 None => (host, None),
102 }
103 } else {
104 (rest, None)
105 }
106 } else {
107 match memchr::memchr(b':', rest.as_bytes()) {
108 Some(i) => (&rest[..i], Some(&rest[i + 1..])),
109 None => (rest, None),
110 }
111 };
112 (user_info, host, port)
113}
114
115impl<T: Deref<Target = str>> Iri<T> {
116 pub fn path_segments(&self) -> PathSegments<'_> {
117 path_segments(self.path())
118 }
119
120 pub fn authority_parts(&self) -> Option<(Option<&str>, &str, Option<&str>)> {
121 self.authority().map(split_authority)
122 }
123}
124
125impl<T: Deref<Target = str>> IriRef<T> {
126 pub fn path_segments(&self) -> PathSegments<'_> {
127 path_segments(self.path())
128 }
129 pub fn authority_parts(&self) -> Option<(Option<&str>, &str, Option<&str>)> {
130 self.authority().map(split_authority)
131 }
132}
133
134impl<T: Deref<Target = str>> Uri<T> {
135 pub fn path_segments(&self) -> PathSegments<'_> {
136 path_segments(self.path())
137 }
138 pub fn authority_parts(&self) -> Option<(Option<&str>, &str, Option<&str>)> {
139 self.authority().map(split_authority)
140 }
141}
142
143impl<T: Deref<Target = str>> UriRef<T> {
144 pub fn path_segments(&self) -> PathSegments<'_> {
145 path_segments(self.path())
146 }
147 pub fn authority_parts(&self) -> Option<(Option<&str>, &str, Option<&str>)> {
148 self.authority().map(split_authority)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn segments_absolute() {
158 let segs: Vec<&str> = path_segments("/a/b/c").collect();
159 assert_eq!(segs, vec!["a", "b", "c"]);
160 }
161
162 #[test]
163 fn segments_relative() {
164 let segs: Vec<&str> = path_segments("a/b/c").collect();
165 assert_eq!(segs, vec!["a", "b", "c"]);
166 }
167
168 #[test]
169 fn segments_trailing_slash() {
170 let segs: Vec<&str> = path_segments("/a/b/").collect();
171 assert_eq!(segs, vec!["a", "b", ""]);
172 }
173
174 #[test]
175 fn split_auth_host_only() {
176 assert_eq!(split_authority("example.com"), (None, "example.com", None));
177 }
178
179 #[test]
180 fn split_auth_full() {
181 assert_eq!(
182 split_authority("user:pass@host:80"),
183 (Some("user:pass"), "host", Some("80"))
184 );
185 }
186
187 #[test]
188 fn split_auth_ipv6() {
189 assert_eq!(split_authority("[::1]:8080"), (None, "[::1]", Some("8080")));
190 }
191
192 #[test]
193 fn normalize_basic() {
194 assert_eq!(normalize_path("/a/b/../c"), "/a/c");
195 assert_eq!(normalize_path("a/./b/../c"), "a/c");
196 }
197}