finchers_core/endpoint/
context.rs1use Input;
2use percent_encoding::percent_decode;
3use std::borrow::Cow;
4use std::fmt;
5use std::ops::Range;
6use std::str::{self, Utf8Error};
7
8#[derive(Debug, Clone)]
10pub struct Context<'a> {
11 input: &'a Input,
12 segments: Segments<'a>,
13}
14
15impl<'a> Context<'a> {
16 pub(crate) fn new(input: &'a Input) -> Self {
17 Context {
18 input: input,
19 segments: Segments::from(input.request().uri().path()),
20 }
21 }
22
23 pub fn input(&self) -> &'a Input {
25 self.input
26 }
27
28 pub fn segments(&mut self) -> &mut Segments<'a> {
30 &mut self.segments
31 }
32}
33
34#[derive(Debug, Copy, Clone)]
36pub struct Segments<'a> {
37 path: &'a str,
38 pos: usize,
39 popped: usize,
40}
41
42impl<'a> From<&'a str> for Segments<'a> {
43 fn from(path: &'a str) -> Self {
44 debug_assert!(!path.is_empty());
45 debug_assert_eq!(path.chars().next(), Some('/'));
46 Segments {
47 path,
48 pos: 1,
49 popped: 0,
50 }
51 }
52}
53
54impl<'a> Segments<'a> {
55 #[inline]
57 pub fn remaining_path(&self) -> &'a str {
58 &self.path[self.pos..]
59 }
60
61 #[inline]
63 pub fn position(&self) -> usize {
64 self.pos
65 }
66
67 #[inline]
69 pub fn popped(&self) -> usize {
70 self.popped
71 }
72}
73
74impl<'a> Iterator for Segments<'a> {
75 type Item = Segment<'a>;
76
77 fn next(&mut self) -> Option<Self::Item> {
78 if self.pos == self.path.len() {
79 return None;
80 }
81 if let Some(offset) = self.path[self.pos..].find('/') {
82 let segment = Segment {
83 s: self.path,
84 range: Range {
85 start: self.pos,
86 end: self.pos + offset,
87 },
88 };
89 self.pos += offset + 1;
90 self.popped += 1;
91 Some(segment)
92 } else {
93 let segment = Segment {
94 s: self.path,
95 range: Range {
96 start: self.pos,
97 end: self.path.len(),
98 },
99 };
100 self.pos = self.path.len();
101 self.popped += 1;
102 Some(segment)
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct Segment<'a> {
110 s: &'a str,
111 range: Range<usize>,
112}
113
114impl<'a> Segment<'a> {
115 pub fn new(s: &'a str, range: Range<usize>) -> Segment<'a> {
117 Segment { s, range }
118 }
119
120 pub fn as_encoded_str(&self) -> &'a EncodedStr {
122 unsafe { EncodedStr::new_unchecked(self.s[self.range.clone()].as_bytes()) }
123 }
124
125 #[inline]
127 pub fn as_range(&self) -> Range<usize> {
128 self.range.clone()
129 }
130}
131
132#[repr(C)]
134pub struct EncodedStr([u8]);
135
136impl fmt::Debug for EncodedStr {
137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138 f.debug_tuple("EncodedStr").field(&&self.0).finish()
139 }
140}
141
142impl AsRef<[u8]> for EncodedStr {
143 #[inline(always)]
144 fn as_ref(&self) -> &[u8] {
145 self.as_bytes()
146 }
147}
148
149impl EncodedStr {
150 #[inline(always)]
155 pub unsafe fn new_unchecked(s: &[u8]) -> &EncodedStr {
156 &*(s as *const [u8] as *const EncodedStr)
157 }
158
159 #[inline(always)]
161 pub fn as_bytes(&self) -> &[u8] {
162 &self.0
163 }
164
165 #[inline]
167 pub fn percent_decode(&self) -> Result<Cow<str>, Utf8Error> {
168 percent_decode(&self.0).decode_utf8()
169 }
170
171 #[inline]
176 pub fn percent_decode_lossy(&self) -> Cow<str> {
177 percent_decode(&self.0).decode_utf8_lossy()
178 }
179
180 #[inline]
185 pub fn url_decode(&self) -> Result<Cow<str>, Utf8Error> {
186 let replaced = replace_plus(&self.0);
187 let v = match percent_decode(&*replaced).if_any() {
188 Some(v) => v,
189 None => match replaced {
190 Cow::Borrowed(b) => return str::from_utf8(b).map(Cow::Borrowed),
191 Cow::Owned(v) => v,
192 },
193 };
194 String::from_utf8(v).map(Cow::Owned).map_err(|e| e.utf8_error())
195 }
196}
197
198fn replace_plus<'a>(input: &'a [u8]) -> Cow<'a, [u8]> {
199 match input.iter().position(|&b| b == b'+') {
200 None => Cow::Borrowed(input),
201 Some(pos) => {
202 let mut replaced = input.to_owned();
203 replaced[pos] = b' ';
204 replaced[pos + 1..].iter_mut().for_each(|b| {
205 if *b == b'+' {
206 *b = b' ';
207 }
208 });
209 Cow::Owned(replaced)
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_segments() {
220 let mut segments = Segments::from("/foo/bar.txt");
221 assert_eq!(segments.remaining_path(), "foo/bar.txt");
222 assert_eq!(
223 segments.next().map(|s| s.as_encoded_str().as_bytes()),
224 Some(&b"foo"[..])
225 );
226 assert_eq!(segments.remaining_path(), "bar.txt");
227 assert_eq!(
228 segments.next().map(|s| s.as_encoded_str().as_bytes()),
229 Some(&b"bar.txt"[..])
230 );
231 assert_eq!(segments.remaining_path(), "");
232 assert_eq!(segments.next().map(|s| s.as_encoded_str().as_bytes()), None);
233 assert_eq!(segments.remaining_path(), "");
234 assert_eq!(segments.next().map(|s| s.as_encoded_str().as_bytes()), None);
235 }
236
237 #[test]
238 fn test_segments_from_root_path() {
239 let mut segments = Segments::from("/");
240 assert_eq!(segments.remaining_path(), "");
241 assert_eq!(segments.next().map(|s| s.as_encoded_str().as_bytes()), None);
242 }
243
244}