rocket_http_community/uri/
origin.rs

1use std::borrow::Cow;
2
3use crate::ext::IntoOwned;
4use crate::parse::{uri::tables::is_pchar, Extent, IndexedStr};
5use crate::uri::{as_utf8_unchecked, fmt, Data, Error, Path, Query};
6use crate::{RawStr, RawStrBuf};
7
8/// A URI with an absolute path and optional query: `/path?query`.
9///
10/// Origin URIs are the primary type of URI encountered in Rocket applications.
11/// They are also the _simplest_ type of URIs, made up of only a path and an
12/// optional query.
13///
14/// # Structure
15///
16/// The following diagram illustrates the syntactic structure of an origin URI:
17///
18/// ```text
19/// /first_segment/second_segment/third?optional=query
20/// |---------------------------------| |------------|
21///                 path                    query
22/// ```
23///
24/// The URI must begin with a `/`, can be followed by any number of _segments_,
25/// and an optional `?` query separator and query string.
26///
27/// # Normalization
28///
29/// Rocket prefers, and will sometimes require, origin URIs to be _normalized_.
30/// A normalized origin URI is a valid origin URI that contains no empty
31/// segments except optionally a trailing slash.
32///
33/// As an example, the following URIs are all valid, normalized URIs:
34///
35/// ```rust
36/// # extern crate rocket;
37/// # use rocket::http::uri::Origin;
38/// # let valid_uris = [
39/// "/",
40/// "/?",
41/// "/a/b/",
42/// "/a/b/c",
43/// "/a/b/c/",
44/// "/a/b/c?",
45/// "/a/b/c?q",
46/// "/hello?lang=en",
47/// "/hello/?lang=en",
48/// "/some%20thing?q=foo&lang=fr",
49/// # ];
50/// # for uri in &valid_uris {
51/// #   assert!(Origin::parse(uri).unwrap().is_normalized());
52/// # }
53/// ```
54///
55/// By contrast, the following are valid but _non-normal_ URIs:
56///
57/// ```rust
58/// # extern crate rocket;
59/// # use rocket::http::uri::Origin;
60/// # let invalid = [
61/// "//",               // an empty segment
62/// "/a/ab//c//d",      // two empty segments
63/// "/?a&&b",           // empty query segment
64/// "/?foo&",           // trailing empty query segment
65/// # ];
66/// # for uri in &invalid {
67/// #   assert!(!Origin::parse(uri).unwrap().is_normalized());
68/// # }
69/// ```
70///
71/// The [`Origin::into_normalized()`](crate::uri::Origin::into_normalized())
72/// method can be used to normalize any `Origin`:
73///
74/// ```rust
75/// # extern crate rocket;
76/// # use rocket::http::uri::Origin;
77/// # let invalid = [
78/// // non-normal versions
79/// "//", "/a/b//c", "/a/ab//c//d/", "/a?a&&b&",
80///
81/// // normalized versions
82/// "/",  "/a/b/c",  "/a/ab/c/d/", "/a?a&b",
83/// # ];
84/// # for i in 0..(invalid.len() / 2) {
85/// #     let abnormal = Origin::parse(invalid[i]).unwrap();
86/// #     let expected = Origin::parse(invalid[i + (invalid.len() / 2)]).unwrap();
87/// #     assert_eq!(abnormal.into_normalized(), expected);
88/// # }
89/// ```
90///
91/// # (De)serialization
92///
93/// `Origin` is both `Serialize` and `Deserialize`:
94///
95/// ```rust
96/// # #[cfg(feature = "serde")] mod serde_impl {
97/// # use serde as serde;
98/// use serde::{Serialize, Deserialize};
99/// use rocket::http::uri::Origin;
100///
101/// #[derive(Deserialize, Serialize)]
102/// # #[serde(crate = "serde")]
103/// struct UriOwned {
104///     uri: Origin<'static>,
105/// }
106///
107/// #[derive(Deserialize, Serialize)]
108/// # #[serde(crate = "serde")]
109/// struct UriBorrowed<'a> {
110///     uri: Origin<'a>,
111/// }
112/// # }
113/// ```
114#[derive(Debug, Clone)]
115pub struct Origin<'a> {
116    pub(crate) source: Option<Cow<'a, str>>,
117    pub(crate) path: Data<'a, fmt::Path>,
118    pub(crate) query: Option<Data<'a, fmt::Query>>,
119}
120
121impl<'a> Origin<'a> {
122    /// The root: `'/'`.
123    #[doc(hidden)]
124    pub fn root() -> &'static Origin<'static> {
125        static ROOT_ORIGIN: Origin<'static> = Origin::const_new("/", None);
126        &ROOT_ORIGIN
127    }
128
129    /// SAFETY: `source` must be UTF-8.
130    #[inline]
131    pub(crate) unsafe fn raw(
132        source: Cow<'a, [u8]>,
133        path: Extent<&'a [u8]>,
134        query: Option<Extent<&'a [u8]>>,
135    ) -> Origin<'a> {
136        Origin {
137            source: Some(as_utf8_unchecked(source)),
138            path: Data::raw(path),
139            query: query.map(Data::raw),
140        }
141    }
142
143    // Used mostly for testing and to construct known good URIs from other parts
144    // of Rocket. This should _really_ not be used outside of Rocket because the
145    // resulting `Origin's` are not guaranteed to be valid origin URIs!
146    #[doc(hidden)]
147    pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
148    where
149        P: Into<Cow<'a, str>>,
150        Q: Into<Cow<'a, str>>,
151    {
152        Origin {
153            source: None,
154            path: Data::new(path.into()),
155            query: query.map(Data::new),
156        }
157    }
158
159    // Used mostly for testing and to construct known good URIs from other parts
160    // of Rocket. This should _really_ not be used outside of Rocket because the
161    // resulting `Origin's` are not guaranteed to be valid origin URIs!
162    #[doc(hidden)]
163    pub fn path_only<P: Into<Cow<'a, str>>>(path: P) -> Origin<'a> {
164        Origin::new(path, None::<&'a str>)
165    }
166
167    // Used mostly for testing and to construct known good URIs from other parts
168    // of Rocket. This should _really_ not be used outside of Rocket because the
169    // resulting `Origin's` are not guaranteed to be valid origin URIs!
170    #[doc(hidden)]
171    pub const fn const_new(path: &'a str, query: Option<&'a str>) -> Origin<'a> {
172        Origin {
173            source: None,
174            path: Data {
175                value: IndexedStr::Concrete(Cow::Borrowed(path)),
176                decoded_segments: state::InitCell::new(),
177            },
178            query: match query {
179                Some(query) => Some(Data {
180                    value: IndexedStr::Concrete(Cow::Borrowed(query)),
181                    decoded_segments: state::InitCell::new(),
182                }),
183                None => None,
184            },
185        }
186    }
187
188    pub(crate) fn set_query<Q: Into<Option<Cow<'a, str>>>>(&mut self, query: Q) {
189        self.query = query.into().map(Data::new);
190    }
191
192    /// Parses the string `string` into an `Origin`. Parsing will never
193    /// allocate. Returns an `Error` if `string` is not a valid origin URI.
194    ///
195    /// # Example
196    ///
197    /// ```rust
198    /// # #[macro_use] extern crate rocket;
199    /// use rocket::http::uri::Origin;
200    ///
201    /// // Parse a valid origin URI.
202    /// let uri = Origin::parse("/a/b/c?query").expect("valid URI");
203    /// assert_eq!(uri.path(), "/a/b/c");
204    /// assert_eq!(uri.query().unwrap(), "query");
205    ///
206    /// // Invalid URIs fail to parse.
207    /// Origin::parse("foo bar").expect_err("invalid URI");
208    ///
209    /// // Prefer to use `uri!()` when the input is statically known:
210    /// let uri = uri!("/a/b/c?query");
211    /// assert_eq!(uri.path(), "/a/b/c");
212    /// assert_eq!(uri.query().unwrap(), "query");
213    /// ```
214    pub fn parse(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
215        crate::parse::uri::origin_from_str(string)
216    }
217
218    // Parses an `Origin` which is allowed to contain _any_ `UTF-8` character.
219    // The path must still be absolute `/..`. Don't use this outside of Rocket!
220    #[doc(hidden)]
221    pub fn parse_route(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
222        use pear::error::Expected;
223
224        if !string.starts_with('/') {
225            return Err(Error {
226                expected: Expected::token(Some(&b'/'), string.as_bytes().first().cloned()),
227                index: 0,
228            });
229        }
230
231        let (path, query) = string
232            .split_once('?')
233            .map(|(path, query)| (path, Some(query)))
234            .unwrap_or((string, None));
235
236        Ok(Origin::new(path, query))
237    }
238
239    /// Parses the string `string` into an `Origin`. Never allocates on success.
240    /// May allocate on error.
241    ///
242    /// This method should be used instead of [`Origin::parse()`] when
243    /// the source URI is already a `String`. Returns an `Error` if `string` is
244    /// not a valid origin URI.
245    ///
246    /// # Example
247    ///
248    /// ```rust
249    /// # extern crate rocket;
250    /// use rocket::http::uri::Origin;
251    ///
252    /// let source = format!("/foo/{}/three", 2);
253    /// let uri = Origin::parse_owned(source).expect("valid URI");
254    /// assert_eq!(uri.path(), "/foo/2/three");
255    /// assert!(uri.query().is_none());
256    /// ```
257    pub fn parse_owned(string: String) -> Result<Origin<'static>, Error<'static>> {
258        let origin = Origin::parse(&string).map_err(|e| e.into_owned())?;
259        debug_assert!(origin.source.is_some(), "Origin parsed w/o source");
260
261        Ok(Origin {
262            path: origin.path.into_owned(),
263            query: origin.query.into_owned(),
264            source: Some(Cow::Owned(string)),
265        })
266    }
267
268    /// Returns the path part of this URI.
269    ///
270    /// # Example
271    ///
272    /// ```rust
273    /// # #[macro_use] extern crate rocket;
274    /// let uri = uri!("/a/b/c");
275    /// assert_eq!(uri.path(), "/a/b/c");
276    ///
277    /// let uri = uri!("/a/b/c?name=bob");
278    /// assert_eq!(uri.path(), "/a/b/c");
279    /// ```
280    #[inline]
281    pub fn path(&self) -> Path<'_> {
282        Path {
283            source: &self.source,
284            data: &self.path,
285        }
286    }
287
288    /// Returns the query part of this URI without the question mark, if there
289    /// is any.
290    ///
291    /// # Example
292    ///
293    /// ```rust
294    /// # #[macro_use] extern crate rocket;
295    /// let uri = uri!("/a/b/c?alphabet=true");
296    /// assert_eq!(uri.query().unwrap(), "alphabet=true");
297    ///
298    /// let uri = uri!("/a/b/c");
299    /// assert!(uri.query().is_none());
300    /// ```
301    #[inline]
302    pub fn query(&self) -> Option<Query<'_>> {
303        self.query.as_ref().map(|data| Query {
304            source: &self.source,
305            data,
306        })
307    }
308
309    /// Applies the function `f` to the internal `path` and returns a new
310    /// `Origin` with the new path. If the path returned from `f` is invalid,
311    /// returns `None`. Otherwise, returns `Some`, even if the new path is
312    /// _abnormal_.
313    ///
314    /// ### Examples
315    ///
316    /// Affix a trailing slash if one isn't present.
317    ///
318    /// ```rust
319    /// # #[macro_use] extern crate rocket;
320    /// let uri = uri!("/a/b/c");
321    /// let expected_uri = uri!("/a/b/c/d");
322    /// assert_eq!(uri.map_path(|p| format!("{}/d", p)), Some(expected_uri));
323    ///
324    /// let uri = uri!("/a/b/c");
325    /// let abnormal_map = uri.map_path(|p| format!("{}///d", p));
326    /// assert_eq!(abnormal_map.unwrap(), "/a/b/c///d");
327    ///
328    /// let uri = uri!("/a/b/c");
329    /// let expected = uri!("/b/c");
330    /// let mapped = uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p));
331    /// assert_eq!(mapped, Some(expected));
332    ///
333    /// let uri = uri!("/a");
334    /// assert_eq!(uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None);
335    ///
336    /// let uri = uri!("/a/b/c");
337    /// assert_eq!(uri.map_path(|p| format!("hi/{}", p)), None);
338    /// ```
339    #[inline]
340    pub fn map_path<'s, F, P>(&'s self, f: F) -> Option<Self>
341    where
342        F: FnOnce(&'s RawStr) -> P,
343        P: Into<RawStrBuf> + 's,
344    {
345        let path = f(self.path().raw()).into();
346        if !path.starts_with('/') || !path.as_bytes().iter().all(is_pchar) {
347            return None;
348        }
349
350        Some(Origin {
351            source: self.source.clone(),
352            path: Data::new(Cow::from(path.into_string())),
353            query: self.query.clone(),
354        })
355    }
356
357    /// Removes the query part of this URI, if there is any.
358    ///
359    /// # Example
360    ///
361    /// ```rust
362    /// # #[macro_use] extern crate rocket;
363    /// let mut uri = uri!("/a/b/c?query=some");
364    /// assert_eq!(uri.query().unwrap(), "query=some");
365    ///
366    /// uri.clear_query();
367    /// assert!(uri.query().is_none());
368    /// ```
369    pub fn clear_query(&mut self) {
370        self.set_query(None);
371    }
372
373    /// Returns `true` if `self` is normalized. Otherwise, returns `false`.
374    ///
375    /// See [Normalization](Self#normalization) for more information on what it
376    /// means for an origin URI to be normalized. Note that `uri!()` always
377    /// normalizes static input.
378    ///
379    /// # Example
380    ///
381    /// ```rust
382    /// # #[macro_use] extern crate rocket;
383    /// use rocket::http::uri::Origin;
384    ///
385    /// assert!(Origin::parse("/").unwrap().is_normalized());
386    /// assert!(Origin::parse("/a/b/c").unwrap().is_normalized());
387    /// assert!(Origin::parse("/a/b/c?a=b&c").unwrap().is_normalized());
388    ///
389    /// assert!(!Origin::parse("/a/b/c//d").unwrap().is_normalized());
390    /// assert!(!Origin::parse("/a?q&&b").unwrap().is_normalized());
391    ///
392    /// assert!(uri!("/a/b/c//d").is_normalized());
393    /// assert!(uri!("/a?q&&b").is_normalized());
394    /// ```
395    pub fn is_normalized(&self) -> bool {
396        self.path().is_normalized(true) && self.query().is_none_or(|q| q.is_normalized())
397    }
398
399    fn _normalize(&mut self, allow_trail: bool) {
400        if !self.path().is_normalized(true) {
401            self.path = self.path().to_normalized(true, allow_trail);
402        }
403
404        if let Some(query) = self.query() {
405            if !query.is_normalized() {
406                self.query = Some(query.to_normalized());
407            }
408        }
409    }
410
411    /// Normalizes `self`. This is a no-op if `self` is already normalized.
412    ///
413    /// See [Normalization](#normalization) for more information on what it
414    /// means for an origin URI to be normalized.
415    ///
416    /// # Example
417    ///
418    /// ```rust
419    /// # extern crate rocket;
420    /// use rocket::http::uri::Origin;
421    ///
422    /// let mut abnormal = Origin::parse("/a/b/c//d").unwrap();
423    /// assert!(!abnormal.is_normalized());
424    /// abnormal.normalize();
425    /// assert!(abnormal.is_normalized());
426    /// ```
427    pub fn normalize(&mut self) {
428        self._normalize(true);
429    }
430
431    /// Consumes `self` and returns a normalized version.
432    ///
433    /// This is a no-op if `self` is already normalized. See
434    /// [Normalization](#normalization) for more information on what it means
435    /// for an origin URI to be normalized.
436    ///
437    /// # Example
438    ///
439    /// ```rust
440    /// # extern crate rocket;
441    /// use rocket::http::uri::Origin;
442    ///
443    /// let abnormal = Origin::parse("/a/b/c//d").unwrap();
444    /// assert!(!abnormal.is_normalized());
445    /// assert!(abnormal.into_normalized().is_normalized());
446    /// ```
447    pub fn into_normalized(mut self) -> Self {
448        self.normalize();
449        self
450    }
451
452    /// Returns `true` if `self` has a _trailing_ slash.
453    ///
454    /// This is defined as `path.len() > 1` && `path.ends_with('/')`. This
455    /// implies that the URI `/` is _not_ considered to have a trailing slash.
456    ///
457    /// # Example
458    ///
459    /// ```rust
460    /// # #[macro_use] extern crate rocket;
461    ///
462    /// assert!(!uri!("/").has_trailing_slash());
463    /// assert!(!uri!("/a").has_trailing_slash());
464    /// assert!(!uri!("/foo/bar/baz").has_trailing_slash());
465    ///
466    /// assert!(uri!("/a/").has_trailing_slash());
467    /// assert!(uri!("/foo/").has_trailing_slash());
468    /// assert!(uri!("/foo/bar/baz/").has_trailing_slash());
469    /// ```
470    pub fn has_trailing_slash(&self) -> bool {
471        self.path().len() > 1 && self.path().ends_with('/')
472    }
473
474    /// Returns `true` if `self` is normalized ([`Origin::is_normalized()`]) and
475    /// **does not** have a trailing slash ([Origin::has_trailing_slash()]).
476    /// Otherwise returns `false`.
477    ///
478    /// # Example
479    ///
480    /// ```rust
481    /// # #[macro_use] extern crate rocket;
482    /// use rocket::http::uri::Origin;
483    ///
484    /// let origin = Origin::parse("/").unwrap();
485    /// assert!(origin.is_normalized_nontrailing());
486    ///
487    /// let origin = Origin::parse("/foo/bar").unwrap();
488    /// assert!(origin.is_normalized_nontrailing());
489    ///
490    /// let origin = Origin::parse("//").unwrap();
491    /// assert!(!origin.is_normalized_nontrailing());
492    ///
493    /// let origin = Origin::parse("/foo/bar//baz/").unwrap();
494    /// assert!(!origin.is_normalized_nontrailing());
495    ///
496    /// let origin = Origin::parse("/foo/bar/").unwrap();
497    /// assert!(!origin.is_normalized_nontrailing());
498    /// ```
499    pub fn is_normalized_nontrailing(&self) -> bool {
500        self.is_normalized() && !self.has_trailing_slash()
501    }
502
503    /// Converts `self` into a normalized origin path without a trailing slash.
504    /// Does nothing is `self` is already [`normalized_nontrailing`].
505    ///
506    /// [`normalized_nontrailing`]: Origin::is_normalized_nontrailing()
507    ///
508    /// # Example
509    ///
510    /// ```rust
511    /// # #[macro_use] extern crate rocket;
512    /// use rocket::http::uri::Origin;
513    ///
514    /// let origin = Origin::parse("/").unwrap();
515    /// assert!(origin.is_normalized_nontrailing());
516    ///
517    /// let normalized = origin.into_normalized_nontrailing();
518    /// assert_eq!(normalized, uri!("/"));
519    ///
520    /// let origin = Origin::parse("//").unwrap();
521    /// assert!(!origin.is_normalized_nontrailing());
522    ///
523    /// let normalized = origin.into_normalized_nontrailing();
524    /// assert_eq!(normalized, uri!("/"));
525    ///
526    /// let origin = Origin::parse_owned("/foo/bar//baz/".into()).unwrap();
527    /// assert!(!origin.is_normalized_nontrailing());
528    ///
529    /// let normalized = origin.into_normalized_nontrailing();
530    /// assert_eq!(normalized, uri!("/foo/bar/baz"));
531    ///
532    /// let origin = Origin::parse("/foo/bar/").unwrap();
533    /// assert!(!origin.is_normalized_nontrailing());
534    ///
535    /// let normalized = origin.into_normalized_nontrailing();
536    /// assert_eq!(normalized, uri!("/foo/bar"));
537    /// ```
538    pub fn into_normalized_nontrailing(mut self) -> Self {
539        if !self.is_normalized_nontrailing() {
540            if self.is_normalized() && self.has_trailing_slash() {
541                let indexed = match self.path.value {
542                    IndexedStr::Indexed(i, j) => IndexedStr::Indexed(i, j - 1),
543                    IndexedStr::Concrete(cow) => IndexedStr::Concrete(match cow {
544                        Cow::Borrowed(s) => Cow::Borrowed(&s[..s.len() - 1]),
545                        Cow::Owned(mut s) => Cow::Owned({
546                            s.pop();
547                            s
548                        }),
549                    }),
550                };
551
552                self.path = Data {
553                    value: indexed,
554                    decoded_segments: state::InitCell::new(),
555                };
556            } else {
557                self._normalize(false);
558            }
559        }
560
561        self
562    }
563}
564
565impl_serde!(Origin<'a>, "an origin-form URI");
566
567impl_traits!(Origin[parse_route], path, query);
568
569impl std::fmt::Display for Origin<'_> {
570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        write!(f, "{}", self.path())?;
572        if let Some(query) = self.query() {
573            write!(f, "?{}", query)?;
574        }
575
576        Ok(())
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::Origin;
583
584    fn seg_count(path: &str, expected: usize) -> bool {
585        let origin = Origin::parse(path).unwrap();
586        let segments = origin.path().segments();
587        let actual = segments.num();
588        if actual != expected {
589            eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
590            eprintln!(
591                "{}",
592                if actual != expected {
593                    "lifetime"
594                } else {
595                    "buf"
596                }
597            );
598            eprintln!("Segments (for {}):", path);
599            for (i, segment) in segments.enumerate() {
600                eprintln!("{}: {}", i, segment);
601            }
602        }
603
604        actual == expected
605    }
606
607    fn eq_segments(path: &str, expected: &[&str]) -> bool {
608        let uri = match Origin::parse(path) {
609            Ok(uri) => uri,
610            Err(e) => panic!("failed to parse {}: {}", path, e),
611        };
612
613        let actual: Vec<&str> = uri.path().segments().collect();
614        actual == expected
615    }
616
617    #[test]
618    fn send_and_sync() {
619        fn assert<T: Send + Sync>() {}
620        assert::<Origin<'_>>();
621    }
622
623    #[test]
624    fn simple_segment_count() {
625        assert!(seg_count("/", 1));
626        assert!(seg_count("/a", 1));
627        assert!(seg_count("/a/", 2));
628        assert!(seg_count("/a/b", 2));
629        assert!(seg_count("/a/b/", 3));
630        assert!(seg_count("/ab/", 2));
631    }
632
633    #[test]
634    fn segment_count() {
635        assert!(seg_count("////", 1));
636        assert!(seg_count("//a//", 2));
637        assert!(seg_count("//abc//", 2));
638        assert!(seg_count("//abc/def/", 3));
639        assert!(seg_count("//////abc///def//////////", 3));
640        assert!(seg_count("/a/b/c/d/e/f/g", 7));
641        assert!(seg_count("/a/b/c/d/e/f/g", 7));
642        assert!(seg_count("/a/b/c/d/e/f/g/", 8));
643        assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
644        assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
645        assert!(seg_count("/a/b", 2));
646    }
647
648    #[test]
649    fn single_segments_match() {
650        assert!(eq_segments("/", &[""]));
651        assert!(eq_segments("/a", &["a"]));
652        assert!(eq_segments("/a/", &["a", ""]));
653        assert!(eq_segments("///a/", &["a", ""]));
654        assert!(eq_segments("///a///////", &["a", ""]));
655        assert!(eq_segments("/a///////", &["a", ""]));
656        assert!(eq_segments("//a", &["a"]));
657        assert!(eq_segments("/abc", &["abc"]));
658        assert!(eq_segments("/abc/", &["abc", ""]));
659        assert!(eq_segments("///abc/", &["abc", ""]));
660        assert!(eq_segments("///abc///////", &["abc", ""]));
661        assert!(eq_segments("/abc///////", &["abc", ""]));
662        assert!(eq_segments("//abc", &["abc"]));
663    }
664
665    #[test]
666    fn multi_segments_match() {
667        assert!(eq_segments("/a/b/c", &["a", "b", "c"]));
668        assert!(eq_segments("/a/b", &["a", "b"]));
669        assert!(eq_segments("/a///b", &["a", "b"]));
670        assert!(eq_segments("/a/b/c/d", &["a", "b", "c", "d"]));
671        assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
672        assert!(eq_segments("/abc/abc", &["abc", "abc"]));
673        assert!(eq_segments("/abc/abc/", &["abc", "abc", ""]));
674        assert!(eq_segments("///abc///////a", &["abc", "a"]));
675        assert!(eq_segments("/////abc/b", &["abc", "b"]));
676        assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
677        assert!(eq_segments("//abc//c////////d/", &["abc", "c", "d", ""]));
678    }
679
680    #[test]
681    fn multi_segments_match_funky_chars() {
682        assert!(eq_segments("/a/b/c!!!", &["a", "b", "c!!!"]));
683    }
684
685    #[test]
686    fn segment_mismatch() {
687        assert!(!eq_segments("/", &["a"]));
688        assert!(!eq_segments("/a", &[]));
689        assert!(!eq_segments("/a/a", &["a"]));
690        assert!(!eq_segments("/a/b", &["b", "a"]));
691        assert!(!eq_segments("/a/a/b", &["a", "b"]));
692        assert!(!eq_segments("///a/", &[]));
693        assert!(!eq_segments("///a/", &["a"]));
694        assert!(!eq_segments("///a/", &["a", "a"]));
695    }
696
697    fn test_query(uri: &str, query: Option<&str>) {
698        let uri = Origin::parse(uri).unwrap();
699        assert_eq!(uri.query().map(|q| q.as_str()), query);
700    }
701
702    #[test]
703    fn query_does_not_exist() {
704        test_query("/test", None);
705        test_query("/a/b/c/d/e", None);
706        test_query("/////", None);
707        test_query("//a///", None);
708        test_query("/a/b/c", None);
709        test_query("/", None);
710    }
711
712    #[test]
713    fn query_exists() {
714        test_query("/test?abc", Some("abc"));
715        test_query("/a/b/c?abc", Some("abc"));
716        test_query("/a/b/c/d/e/f/g/?abc", Some("abc"));
717        test_query("/?123", Some("123"));
718        test_query("/?", Some(""));
719        test_query("/?", Some(""));
720        test_query("/?hi", Some("hi"));
721    }
722}