finchers_core/endpoint/
context.rs

1use Input;
2use percent_encoding::percent_decode;
3use std::borrow::Cow;
4use std::fmt;
5use std::ops::Range;
6use std::str::{self, Utf8Error};
7
8/// A context during the routing.
9#[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    /// Return the reference to `Input`.
24    pub fn input(&self) -> &'a Input {
25        self.input
26    }
27
28    /// Return the reference to the instance of `Segments`.
29    pub fn segments(&mut self) -> &mut Segments<'a> {
30        &mut self.segments
31    }
32}
33
34/// An iterator over the remaining path segments.
35#[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    /// Returns the remaining path in this segments
56    #[inline]
57    pub fn remaining_path(&self) -> &'a str {
58        &self.path[self.pos..]
59    }
60
61    /// Returns the cursor position in the original path
62    #[inline]
63    pub fn position(&self) -> usize {
64        self.pos
65    }
66
67    /// Returns the number of segments already popped
68    #[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/// A path segment in the HTTP requests.
108#[derive(Debug, Clone)]
109pub struct Segment<'a> {
110    s: &'a str,
111    range: Range<usize>,
112}
113
114impl<'a> Segment<'a> {
115    /// Create a `Segment` from a pair of path string and the range of segment.
116    pub fn new(s: &'a str, range: Range<usize>) -> Segment<'a> {
117        Segment { s, range }
118    }
119
120    /// Return an `EncodedStr` from this segment.
121    pub fn as_encoded_str(&self) -> &'a EncodedStr {
122        unsafe { EncodedStr::new_unchecked(self.s[self.range.clone()].as_bytes()) }
123    }
124
125    /// Returns the range of this segment in the original path.
126    #[inline]
127    pub fn as_range(&self) -> Range<usize> {
128        self.range.clone()
129    }
130}
131
132/// A percent-encoded string.
133#[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    /// Create a new instance of `EncodedStr` from an encoded `str`.
151    ///
152    /// # Safety
153    /// The given string must be a percent-encoded sequence.
154    #[inline(always)]
155    pub unsafe fn new_unchecked(s: &[u8]) -> &EncodedStr {
156        &*(s as *const [u8] as *const EncodedStr)
157    }
158
159    /// Return the reference to the underling `[u8]` of this value.
160    #[inline(always)]
161    pub fn as_bytes(&self) -> &[u8] {
162        &self.0
163    }
164
165    /// Decode this encoded string as an UTF-8 string.
166    #[inline]
167    pub fn percent_decode(&self) -> Result<Cow<str>, Utf8Error> {
168        percent_decode(&self.0).decode_utf8()
169    }
170
171    /// Decode this encoded string as an UTF-8 string.
172    ///
173    /// This method will not fail and the invalid UTF-8 characters will be
174    /// replaced to � (U+FFFD).
175    #[inline]
176    pub fn percent_decode_lossy(&self) -> Cow<str> {
177        percent_decode(&self.0).decode_utf8_lossy()
178    }
179
180    /// Decode this encoded string as an UTF-8 string.
181    ///
182    /// This method will replace the plus ('+') character with a half-width space
183    /// before decoding.
184    #[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}