1use std::convert::TryFrom;
2use std::str::FromStr;
3use std::{cmp, fmt, hash, str};
4
5use bytes::Bytes;
6
7use super::{ErrorKind, InvalidUri};
8use crate::byte_str::ByteStr;
9
10#[derive(Clone)]
12pub struct PathAndQuery {
13 pub(super) data: ByteStr,
14 pub(super) query: u16,
15}
16
17const NONE: u16 = u16::MAX;
18
19impl PathAndQuery {
20 pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> {
22 let Scanned {
23 query,
24 fragment,
25 is_maybe_not_utf8,
26 } = scan_path_and_query(&src)?;
27
28 if let Some(i) = fragment {
29 src.truncate(i as usize);
30 }
31
32 let data = if is_maybe_not_utf8 {
33 ByteStr::from_utf8(src).map_err(|_| ErrorKind::InvalidUriChar)?
34 } else {
35 unsafe { ByteStr::from_utf8_unchecked(src) }
36 };
37
38 Ok(PathAndQuery { data, query })
39 }
40
41 #[inline]
60 pub const fn from_static(src: &'static str) -> Self {
61 match scan_path_and_query(src.as_bytes()) {
62 Ok(Scanned {
63 query,
64 fragment: None,
65 is_maybe_not_utf8: false,
66 }) => PathAndQuery {
67 data: ByteStr::from_static(src),
68 query,
69 },
70 _ => panic!("static str is not valid path"),
72 }
73 }
74
75 pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri>
80 where
81 T: AsRef<[u8]> + 'static,
82 {
83 if_downcast_into!(T, Bytes, src, {
84 return PathAndQuery::from_shared(src);
85 });
86
87 PathAndQuery::try_from(src.as_ref())
88 }
89
90 pub(super) fn empty() -> Self {
91 PathAndQuery {
92 data: ByteStr::new(),
93 query: NONE,
94 }
95 }
96
97 pub(super) fn slash() -> Self {
98 PathAndQuery {
99 data: ByteStr::from_static("/"),
100 query: NONE,
101 }
102 }
103
104 pub(super) fn star() -> Self {
105 PathAndQuery {
106 data: ByteStr::from_static("*"),
107 query: NONE,
108 }
109 }
110
111 #[inline]
134 pub fn path(&self) -> &str {
135 let ret = if self.query == NONE {
136 &self.data[..]
137 } else {
138 &self.data[..self.query as usize]
139 };
140
141 if ret.is_empty() {
142 return "/";
143 }
144
145 ret
146 }
147
148 #[inline]
183 pub fn query(&self) -> Option<&str> {
184 if self.query == NONE {
185 None
186 } else {
187 let i = self.query + 1;
188 Some(&self.data[i as usize..])
189 }
190 }
191
192 #[inline]
214 pub fn as_str(&self) -> &str {
215 let ret = &self.data[..];
216 if ret.is_empty() {
217 return "/";
218 }
219 ret
220 }
221}
222
223impl TryFrom<&[u8]> for PathAndQuery {
224 type Error = InvalidUri;
225 #[inline]
226 fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
227 PathAndQuery::from_shared(Bytes::copy_from_slice(s))
228 }
229}
230
231impl TryFrom<&str> for PathAndQuery {
232 type Error = InvalidUri;
233 #[inline]
234 fn try_from(s: &str) -> Result<Self, Self::Error> {
235 TryFrom::try_from(s.as_bytes())
236 }
237}
238
239impl TryFrom<Vec<u8>> for PathAndQuery {
240 type Error = InvalidUri;
241 #[inline]
242 fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
243 PathAndQuery::from_shared(vec.into())
244 }
245}
246
247impl TryFrom<String> for PathAndQuery {
248 type Error = InvalidUri;
249 #[inline]
250 fn try_from(s: String) -> Result<Self, Self::Error> {
251 PathAndQuery::from_shared(s.into())
252 }
253}
254
255impl TryFrom<&String> for PathAndQuery {
256 type Error = InvalidUri;
257 #[inline]
258 fn try_from(s: &String) -> Result<Self, Self::Error> {
259 TryFrom::try_from(s.as_bytes())
260 }
261}
262
263impl FromStr for PathAndQuery {
264 type Err = InvalidUri;
265 #[inline]
266 fn from_str(s: &str) -> Result<Self, InvalidUri> {
267 TryFrom::try_from(s)
268 }
269}
270
271impl fmt::Debug for PathAndQuery {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 fmt::Display::fmt(self, f)
274 }
275}
276
277impl fmt::Display for PathAndQuery {
278 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
279 if !self.data.is_empty() {
280 match self.data.as_bytes()[0] {
281 b'/' | b'*' => write!(fmt, "{}", &self.data[..]),
282 _ => write!(fmt, "/{}", &self.data[..]),
283 }
284 } else {
285 write!(fmt, "/")
286 }
287 }
288}
289
290impl hash::Hash for PathAndQuery {
291 fn hash<H: hash::Hasher>(&self, state: &mut H) {
292 self.data.hash(state);
293 }
294}
295
296impl PartialEq for PathAndQuery {
299 #[inline]
300 fn eq(&self, other: &PathAndQuery) -> bool {
301 self.data == other.data
302 }
303}
304
305impl Eq for PathAndQuery {}
306
307impl PartialEq<str> for PathAndQuery {
308 #[inline]
309 fn eq(&self, other: &str) -> bool {
310 self.as_str() == other
311 }
312}
313
314impl PartialEq<PathAndQuery> for &str {
315 #[inline]
316 fn eq(&self, other: &PathAndQuery) -> bool {
317 self == &other.as_str()
318 }
319}
320
321impl PartialEq<&str> for PathAndQuery {
322 #[inline]
323 fn eq(&self, other: &&str) -> bool {
324 self.as_str() == *other
325 }
326}
327
328impl PartialEq<PathAndQuery> for str {
329 #[inline]
330 fn eq(&self, other: &PathAndQuery) -> bool {
331 self == other.as_str()
332 }
333}
334
335impl PartialEq<String> for PathAndQuery {
336 #[inline]
337 fn eq(&self, other: &String) -> bool {
338 self.as_str() == other.as_str()
339 }
340}
341
342impl PartialEq<PathAndQuery> for String {
343 #[inline]
344 fn eq(&self, other: &PathAndQuery) -> bool {
345 self.as_str() == other.as_str()
346 }
347}
348
349impl PartialOrd for PathAndQuery {
350 #[inline]
351 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
352 self.as_str().partial_cmp(other.as_str())
353 }
354}
355
356impl PartialOrd<str> for PathAndQuery {
357 #[inline]
358 fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
359 self.as_str().partial_cmp(other)
360 }
361}
362
363impl PartialOrd<PathAndQuery> for str {
364 #[inline]
365 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
366 self.partial_cmp(other.as_str())
367 }
368}
369
370impl PartialOrd<&str> for PathAndQuery {
371 #[inline]
372 fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> {
373 self.as_str().partial_cmp(*other)
374 }
375}
376
377impl PartialOrd<PathAndQuery> for &str {
378 #[inline]
379 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
380 self.partial_cmp(&other.as_str())
381 }
382}
383
384impl PartialOrd<String> for PathAndQuery {
385 #[inline]
386 fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
387 self.as_str().partial_cmp(other.as_str())
388 }
389}
390
391impl PartialOrd<PathAndQuery> for String {
392 #[inline]
393 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
394 self.as_str().partial_cmp(other.as_str())
395 }
396}
397
398struct Scanned {
403 query: u16,
404 fragment: Option<u16>,
405 is_maybe_not_utf8: bool,
406}
407
408const fn scan_path_and_query(bytes: &[u8]) -> Result<Scanned, ErrorKind> {
409 let mut i = 0;
410 let mut query = NONE;
411 let mut fragment = None;
412
413 let mut is_maybe_not_utf8 = false;
414
415 if bytes.is_empty() {
416 return Err(ErrorKind::Empty);
417 }
418
419 if !matches!(bytes[0], b'/' | b'?' | b'#') {
420 return Err(ErrorKind::PathDoesNotStartWithSlash);
421 }
422
423 while i < bytes.len() {
424 match bytes[i] {
426 b'?' => {
427 debug_assert!(query == NONE);
428 query = i as u16;
429 i += 1;
430 break;
431 }
432 b'#' => {
433 fragment = Some(i as u16);
434 break;
435 }
436
437 #[rustfmt::skip]
441 0x21 |
442 0x24..=0x3B |
443 0x3D |
444 0x40..=0x5F |
445 0x61..=0x7A |
446 0x7C |
447 0x7E => {}
448
449 0x7F..=0xFF => {
451 is_maybe_not_utf8 = true;
452 }
453
454 #[rustfmt::skip]
464 b'"' |
465 b'{' | b'}' => {}
466
467 _ => return Err(ErrorKind::InvalidUriChar),
468 }
469 i += 1;
470 }
471
472 if query != NONE {
474 while i < bytes.len() {
475 match bytes[i] {
476 #[rustfmt::skip]
482 0x21 |
483 0x24..=0x3B |
484 0x3D |
485 0x3F..=0x7E => {}
486
487 0x7F..=0xFF => {
488 is_maybe_not_utf8 = true;
489 }
490
491 b'#' => {
492 fragment = Some(i as u16);
493 break;
494 }
495
496 _ => return Err(ErrorKind::InvalidUriChar),
497 }
498 i += 1;
499 }
500 }
501
502 Ok(Scanned {
503 query,
504 fragment,
505 is_maybe_not_utf8,
506 })
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn equal_to_self_of_same_path() {
515 let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
516 let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
517 assert_eq!(p1, p2);
518 assert_eq!(p2, p1);
519 }
520
521 #[test]
522 fn not_equal_to_self_of_different_path() {
523 let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
524 let p2: PathAndQuery = "/world&foo=bar".parse().unwrap();
525 assert_ne!(p1, p2);
526 assert_ne!(p2, p1);
527 }
528
529 #[test]
530 fn equates_with_a_str() {
531 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
532 assert_eq!(&path_and_query, "/hello/world&foo=bar");
533 assert_eq!("/hello/world&foo=bar", &path_and_query);
534 assert_eq!(path_and_query, "/hello/world&foo=bar");
535 assert_eq!("/hello/world&foo=bar", path_and_query);
536 }
537
538 #[test]
539 fn not_equal_with_a_str_of_a_different_path() {
540 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
541 assert_ne!(&path_and_query, "/hello&foo=bar");
543 assert_ne!("/hello&foo=bar", &path_and_query);
544 assert_ne!(path_and_query, "/hello&foo=bar");
546 assert_ne!("/hello&foo=bar", path_and_query);
547 }
548
549 #[test]
550 fn equates_with_a_string() {
551 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
552 assert_eq!(path_and_query, "/hello/world&foo=bar".to_string());
553 assert_eq!("/hello/world&foo=bar".to_string(), path_and_query);
554 }
555
556 #[test]
557 fn not_equal_with_a_string_of_a_different_path() {
558 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
559 assert_ne!(path_and_query, "/hello&foo=bar".to_string());
560 assert_ne!("/hello&foo=bar".to_string(), path_and_query);
561 }
562
563 #[test]
564 fn compares_to_self() {
565 let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap();
566 let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
567 assert!(p1 < p2);
568 assert!(p2 > p1);
569 }
570
571 #[test]
572 fn compares_with_a_str() {
573 let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
574 assert!(&path_and_query < "/c/world&foo=bar");
576 assert!("/c/world&foo=bar" > &path_and_query);
577 assert!(&path_and_query > "/a/world&foo=bar");
578 assert!("/a/world&foo=bar" < &path_and_query);
579
580 assert!(path_and_query < "/c/world&foo=bar");
582 assert!("/c/world&foo=bar" > path_and_query);
583 assert!(path_and_query > "/a/world&foo=bar");
584 assert!("/a/world&foo=bar" < path_and_query);
585 }
586
587 #[test]
588 fn compares_with_a_string() {
589 let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
590 assert!(path_and_query < "/c/world&foo=bar".to_string());
591 assert!("/c/world&foo=bar".to_string() > path_and_query);
592 assert!(path_and_query > "/a/world&foo=bar".to_string());
593 assert!("/a/world&foo=bar".to_string() < path_and_query);
594 }
595
596 #[test]
597 fn ignores_valid_percent_encodings() {
598 assert_eq!("/a%20b", pq("/a%20b?r=1").path());
599 assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap());
600 }
601
602 #[test]
603 fn ignores_invalid_percent_encodings() {
604 assert_eq!("/a%%b", pq("/a%%b?r=1").path());
605 assert_eq!("/aaa%", pq("/aaa%").path());
606 assert_eq!("/aaa%", pq("/aaa%?r=1").path());
607 assert_eq!("/aa%2", pq("/aa%2").path());
608 assert_eq!("/aa%2", pq("/aa%2?r=1").path());
609 assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap());
610 }
611
612 #[test]
613 fn allow_utf8_in_path() {
614 assert_eq!("/🍕", pq("/🍕").path());
615 }
616
617 #[test]
618 fn allow_utf8_in_query() {
619 assert_eq!(Some("pizza=🍕"), pq("/test?pizza=🍕").query());
620 }
621
622 #[test]
623 fn rejects_invalid_utf8_in_path() {
624 PathAndQuery::try_from(&[b'/', 0xFF][..]).expect_err("reject invalid utf8");
625 }
626
627 #[test]
628 fn rejects_invalid_utf8_in_query() {
629 PathAndQuery::try_from(&[b'/', b'a', b'?', 0xFF][..]).expect_err("reject invalid utf8");
630 }
631
632 #[test]
633 fn rejects_empty_string() {
634 PathAndQuery::try_from("").expect_err("reject empty str");
635 }
636
637 #[test]
638 fn requires_starting_with_slash() {
639 PathAndQuery::try_from("sneaky").expect_err("reject missing slash");
640 }
641
642 #[test]
643 fn json_is_fine() {
644 assert_eq!(
645 r#"/{"bread":"baguette"}"#,
646 pq(r#"/{"bread":"baguette"}"#).path()
647 );
648 }
649
650 fn pq(s: &str) -> PathAndQuery {
651 s.parse().expect(&format!("parsing {}", s))
652 }
653}