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}