tsukuyomi_internal/
uri.rs

1use {
2    failure::Error,
3    indexmap::IndexSet,
4    std::{
5        fmt,
6        hash::{Hash, Hasher},
7        str::FromStr,
8    },
9};
10
11/// Concatenate a list of Uris to an Uri.
12#[deprecated(since = "0.4.3")]
13pub fn join_all<I>(segments: I) -> Result<Uri, Error>
14where
15    I: IntoIterator,
16    I::Item: AsRef<Uri>,
17{
18    segments
19        .into_iter()
20        .fold(Ok(Uri::root()), |acc, uri| acc?.join(uri))
21}
22
23/// A type representing the URI of a route.
24#[derive(Debug, Clone)]
25pub struct Uri(UriKind);
26
27#[derive(Debug, Clone, PartialEq)]
28enum UriKind {
29    Root,
30    Asterisk,
31    Segments(String, Option<CaptureNames>),
32}
33
34impl PartialEq for Uri {
35    fn eq(&self, other: &Self) -> bool {
36        match (&self.0, &other.0) {
37            (&UriKind::Root, &UriKind::Root) | (&UriKind::Asterisk, &UriKind::Asterisk) => true,
38            (&UriKind::Segments(ref s, ..), &UriKind::Segments(ref o, ..)) if s == o => true,
39            _ => false,
40        }
41    }
42}
43
44impl Eq for Uri {}
45
46impl Hash for Uri {
47    fn hash<H: Hasher>(&self, state: &mut H) {
48        self.as_str().hash(state)
49    }
50}
51
52impl FromStr for Uri {
53    type Err = Error;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        Self::parse(s)
57    }
58}
59
60impl Uri {
61    pub fn root() -> Self {
62        Uri(UriKind::Root)
63    }
64
65    pub fn asterisk() -> Self {
66        Uri(UriKind::Asterisk)
67    }
68
69    pub fn from_static(s: &'static str) -> Self {
70        s.parse().expect("invalid URI")
71    }
72
73    pub fn parse(mut s: &str) -> Result<Self, Error> {
74        if !s.is_ascii() {
75            failure::bail!("The URI is not ASCII");
76        }
77
78        if s.starts_with('*') {
79            if s.len() > 1 {
80                failure::bail!("the URI with wildcard parameter must start with '/'");
81            }
82            return Ok(Uri(UriKind::Asterisk));
83        }
84
85        if !s.starts_with('/') {
86            failure::bail!("the URI must start with '/'");
87        }
88
89        if s == "/" {
90            return Ok(Self::root());
91        }
92
93        let mut has_trailing_slash = false;
94        if s.ends_with('/') {
95            has_trailing_slash = true;
96            s = &s[..s.len() - 1];
97        }
98
99        let mut names: Option<CaptureNames> = None;
100        for segment in s[1..].split('/') {
101            if names.as_ref().map_or(false, |names| names.has_wildcard) {
102                failure::bail!("The wildcard parameter has already set.");
103            }
104            if segment.is_empty() {
105                failure::bail!("empty segment");
106            }
107            if segment
108                .get(1..)
109                .map_or(false, |s| s.bytes().any(|b| b == b':' || b == b'*'))
110            {
111                failure::bail!("invalid character in a segment");
112            }
113            match segment.as_bytes()[0] {
114                b':' | b'*' => {
115                    names.get_or_insert_with(Default::default).push(segment)?;
116                }
117                _ => {}
118            }
119        }
120
121        if has_trailing_slash {
122            Ok(Self::segments(format!("{}/", s), names))
123        } else {
124            Ok(Self::segments(s, names))
125        }
126    }
127
128    fn segments(s: impl Into<String>, names: Option<CaptureNames>) -> Self {
129        Uri(UriKind::Segments(s.into(), names))
130    }
131
132    #[cfg(test)]
133    fn static_(s: impl Into<String>) -> Self {
134        Self::segments(s, None)
135    }
136
137    #[cfg(test)]
138    fn captured(s: impl Into<String>, names: CaptureNames) -> Self {
139        Uri(UriKind::Segments(s.into(), Some(names)))
140    }
141
142    pub fn as_str(&self) -> &str {
143        match self.0 {
144            UriKind::Root => "/",
145            UriKind::Asterisk => "*",
146            UriKind::Segments(ref s, ..) => s.as_str(),
147        }
148    }
149
150    pub fn is_asterisk(&self) -> bool {
151        match self.0 {
152            UriKind::Asterisk => true,
153            _ => false,
154        }
155    }
156
157    pub fn capture_names(&self) -> Option<&CaptureNames> {
158        match self.0 {
159            UriKind::Segments(_, Some(ref names)) => Some(names),
160            _ => None,
161        }
162    }
163
164    pub fn join(&self, other: impl AsRef<Self>) -> Result<Self, Error> {
165        match self.0.clone() {
166            UriKind::Root => Ok(other.as_ref().clone()),
167            UriKind::Asterisk => {
168                failure::bail!("the asterisk URI cannot be joined with other URI(s)")
169            }
170            UriKind::Segments(mut segment, mut names) => match other.as_ref().0 {
171                UriKind::Root => Ok(Self::segments(segment, names)),
172                UriKind::Asterisk => {
173                    failure::bail!("the asterisk URI cannot be joined with other URI(s)")
174                }
175                UriKind::Segments(ref other_segment, ref other_names) => {
176                    segment += if segment.ends_with('/') {
177                        other_segment.trim_left_matches('/')
178                    } else {
179                        other_segment
180                    };
181                    match (&mut names, other_names) {
182                        (&mut Some(ref mut names), &Some(ref other_names)) => {
183                            names.extend(other_names.params.iter().cloned())?;
184                        }
185                        (ref mut names @ None, &Some(ref other_names)) => {
186                            **names = Some(other_names.clone());
187                        }
188                        (_, &None) => {}
189                    }
190                    Ok(Self::segments(segment, names))
191                }
192            },
193        }
194    }
195}
196
197impl AsRef<Uri> for Uri {
198    fn as_ref(&self) -> &Self {
199        self
200    }
201}
202
203impl fmt::Display for Uri {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        f.write_str(self.as_str())
206    }
207}
208
209#[derive(Clone, Debug, Default, PartialEq)]
210pub struct CaptureNames {
211    params: IndexSet<String>,
212    has_wildcard: bool,
213}
214
215impl CaptureNames {
216    fn push(&mut self, segment: &str) -> Result<(), Error> {
217        if self.has_wildcard {
218            failure::bail!("The wildcard parameter has already set");
219        }
220
221        let (kind, name) = segment.split_at(1);
222        match kind {
223            ":" | "*" => {}
224            "" => failure::bail!("empty segment"),
225            c => failure::bail!("unknown parameter kind: '{}'", c),
226        }
227
228        if name.is_empty() {
229            failure::bail!("empty parameter name");
230        }
231
232        if !self.params.insert(name.into()) {
233            failure::bail!("the duplicated parameter name");
234        }
235
236        if kind == "*" {
237            self.has_wildcard = true;
238        }
239
240        Ok(())
241    }
242
243    fn extend<T>(&mut self, names: impl IntoIterator<Item = T>) -> Result<(), Error>
244    where
245        T: AsRef<str>,
246    {
247        for name in names {
248            self.push(name.as_ref())?;
249        }
250        Ok(())
251    }
252
253    pub fn position(&self, name: &str) -> Option<usize> {
254        Some(self.params.get_full(name)?.0)
255    }
256}
257
258#[cfg_attr(feature = "cargo-clippy", allow(non_ascii_literal))]
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    macro_rules! t {
264        (@case $name:ident, $input:expr, $expected:expr) => {
265            #[test]
266            fn $name() {
267                assert_eq!($input.ok().map(|uri: Uri| uri.0), Some($expected.0));
268            }
269        };
270        ($(
271            $name:ident ($input:expr, $expected:expr);
272        )*) => {$(
273            t!(@case $name, $input, $expected);
274        )*};
275    }
276
277    t! [
278        parse_uri_root(
279            "/".parse(),
280            Uri::root()
281        );
282        parse_uri_static(
283            "/path/to/lib".parse(),
284            Uri::static_("/path/to/lib")
285        );
286        parse_uri_static_has_trailing_slash(
287            "/path/to/lib/".parse(),
288            Uri::static_("/path/to/lib/")
289        );
290        parse_uri_has_wildcard_params(
291            "/api/v1/:param/*path".parse(),
292            Uri::captured(
293                "/api/v1/:param/*path",
294                CaptureNames {
295                    params: indexset!["param".into(), "path".into()],
296                    has_wildcard: true,
297                }
298            )
299        );
300    ];
301
302    #[test]
303    fn parse_uri_failcase_empty() {
304        assert!("".parse::<Uri>().is_err());
305    }
306
307    #[test]
308    fn parse_uri_failcase_without_prefix_root() {
309        assert!("foo/bar".parse::<Uri>().is_err());
310    }
311
312    #[test]
313    fn parse_uri_failcase_duplicated_slashes() {
314        assert!("//foo/bar/".parse::<Uri>().is_err());
315        assert!("/foo//bar/".parse::<Uri>().is_err());
316        assert!("/foo/bar//".parse::<Uri>().is_err());
317    }
318
319    #[test]
320    fn parse_uri_failcase_invalid_wildcard_specifier_pos() {
321        assert!("/pa:th".parse::<Uri>().is_err());
322    }
323
324    #[test]
325    fn parse_uri_failcase_non_ascii() {
326        // FIXME: allow non-ascii URIs with encoding
327        assert!("/パス".parse::<Uri>().is_err());
328    }
329
330    #[test]
331    fn parse_uri_failcase_duplicated_param_name() {
332        assert!("/:id/:id".parse::<Uri>().is_err());
333    }
334
335    #[test]
336    fn parse_uri_failcase_after_wildcard_name() {
337        assert!("/path/to/*a/id".parse::<Uri>().is_err());
338    }
339
340    t! [
341        join_roots(
342            Uri::root().join(Uri::root()),
343            Uri::root()
344        );
345        join_root_and_static(
346            Uri::root().join(Uri::static_("/path/to")),
347            Uri::static_("/path/to")
348        );
349        join_trailing_slash_before_root_1(
350            Uri::static_("/path/to/").join(Uri::root()),
351            Uri::static_("/path/to/")
352        );
353        join_trailing_slash_before_root_2(
354            Uri::static_("/path/to").join(Uri::root()),
355            Uri::static_("/path/to")
356        );
357        join_trailing_slash_before_static_1(
358            Uri::static_("/path").join(Uri::static_("/to")),
359            Uri::static_("/path/to")
360        );
361        join_trailing_slash_before_static_2(
362            Uri::static_("/path/").join(Uri::static_("/to")),
363            Uri::static_("/path/to")
364        );
365    ];
366}