Coverage Report

Created: 2026-08-31 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/http/src/uri/path.rs
Line
Count
Source
1
use std::convert::TryFrom;
2
use std::str::FromStr;
3
use std::{cmp, fmt, hash, str};
4
5
use bytes::Bytes;
6
7
use super::{ErrorKind, InvalidUri, MAX_LEN};
8
use crate::byte_str::ByteStr;
9
10
/// Represents the path component of a URI
11
#[derive(Clone)]
12
pub struct PathAndQuery {
13
    pub(super) data: ByteStr,
14
    pub(super) query: u16,
15
}
16
17
const NONE: u16 = u16::MAX;
18
19
impl PathAndQuery {
20
    // Not public while `bytes` is unstable.
21
196
    pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> {
22
        let Scanned {
23
111
            query,
24
111
            fragment,
25
111
            is_maybe_not_utf8,
26
196
        } = scan_path_and_query(&src)?;
27
28
111
        if let Some(i) = fragment {
29
63
            src.truncate(i as usize);
30
63
        }
31
32
111
        let data = if is_maybe_not_utf8 {
33
48
            ByteStr::from_utf8(src).map_err(|_| ErrorKind::InvalidUriChar)?
34
        } else {
35
63
            unsafe { ByteStr::from_utf8_unchecked(src) }
36
        };
37
38
64
        Ok(PathAndQuery { data, query })
39
196
    }
40
41
    /// Convert a `PathAndQuery` from a static string.
42
    ///
43
    /// This function will not perform any copying, however the string is
44
    /// checked to ensure that it is valid.
45
    ///
46
    /// # Panics
47
    ///
48
    /// This function panics if the argument is an invalid path and query.
49
    ///
50
    /// # Examples
51
    ///
52
    /// ```
53
    /// # use http::uri::*;
54
    /// let v = PathAndQuery::from_static("/hello?world");
55
    ///
56
    /// assert_eq!(v.path(), "/hello");
57
    /// assert_eq!(v.query(), Some("world"));
58
    /// ```
59
    #[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
            // Yes, we reject fragments and non-utf8
71
            _ => panic!("static str is not valid path"),
72
        }
73
    }
74
75
    /// Attempt to convert a `Bytes` buffer to a `PathAndQuery`.
76
    ///
77
    /// This will try to prevent a copy if the type passed is the type used
78
    /// internally, and will copy the data if it is not.
79
    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
1.16k
    pub(super) fn empty() -> Self {
91
1.16k
        PathAndQuery {
92
1.16k
            data: ByteStr::new(),
93
1.16k
            query: NONE,
94
1.16k
        }
95
1.16k
    }
96
97
6.06k
    pub(super) fn slash() -> Self {
98
6.06k
        PathAndQuery {
99
6.06k
            data: ByteStr::from_static("/"),
100
6.06k
            query: NONE,
101
6.06k
        }
102
6.06k
    }
103
104
20
    pub(super) fn star() -> Self {
105
20
        PathAndQuery {
106
20
            data: ByteStr::from_static("*"),
107
20
            query: NONE,
108
20
        }
109
20
    }
110
111
    /// Returns the path component
112
    ///
113
    /// The path component is **case sensitive**.
114
    ///
115
    /// ```notrust
116
    /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
117
    ///                                        |--------|
118
    ///                                             |
119
    ///                                           path
120
    /// ```
121
    ///
122
    /// If the URI is `*` then the path component is equal to `*`.
123
    ///
124
    /// # Examples
125
    ///
126
    /// ```
127
    /// # use http::uri::*;
128
    ///
129
    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
130
    ///
131
    /// assert_eq!(path_and_query.path(), "/hello/world");
132
    /// ```
133
    #[inline]
134
0
    pub fn path(&self) -> &str {
135
0
        let ret = if self.query == NONE {
136
0
            &self.data[..]
137
        } else {
138
0
            &self.data[..self.query as usize]
139
        };
140
141
0
        if ret.is_empty() {
142
0
            return "/";
143
0
        }
144
145
0
        ret
146
0
    }
147
148
    /// Returns the query string component
149
    ///
150
    /// The query component contains non-hierarchical data that, along with data
151
    /// in the path component, serves to identify a resource within the scope of
152
    /// the URI's scheme and naming authority (if any). The query component is
153
    /// indicated by the first question mark ("?") character and terminated by a
154
    /// number sign ("#") character or by the end of the URI.
155
    ///
156
    /// ```notrust
157
    /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
158
    ///                                                   |-------------------|
159
    ///                                                             |
160
    ///                                                           query
161
    /// ```
162
    ///
163
    /// # Examples
164
    ///
165
    /// With a query string component
166
    ///
167
    /// ```
168
    /// # use http::uri::*;
169
    /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
170
    ///
171
    /// assert_eq!(path_and_query.query(), Some("key=value&foo=bar"));
172
    /// ```
173
    ///
174
    /// Without a query string component
175
    ///
176
    /// ```
177
    /// # use http::uri::*;
178
    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
179
    ///
180
    /// assert!(path_and_query.query().is_none());
181
    /// ```
182
    #[inline]
183
0
    pub fn query(&self) -> Option<&str> {
184
0
        if self.query == NONE {
185
0
            None
186
        } else {
187
0
            let i = self.query + 1;
188
0
            Some(&self.data[i as usize..])
189
        }
190
0
    }
191
192
    /// Returns the path and query as a string component.
193
    ///
194
    /// # Examples
195
    ///
196
    /// With a query string component
197
    ///
198
    /// ```
199
    /// # use http::uri::*;
200
    /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
201
    ///
202
    /// assert_eq!(path_and_query.as_str(), "/hello/world?key=value&foo=bar");
203
    /// ```
204
    ///
205
    /// Without a query string component
206
    ///
207
    /// ```
208
    /// # use http::uri::*;
209
    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
210
    ///
211
    /// assert_eq!(path_and_query.as_str(), "/hello/world");
212
    /// ```
213
    #[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
223
impl 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
231
impl 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
239
impl 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
247
impl 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
255
impl 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
263
impl 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
271
impl fmt::Debug for PathAndQuery {
272
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273
0
        fmt::Display::fmt(self, f)
274
0
    }
275
}
276
277
impl fmt::Display for PathAndQuery {
278
0
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
279
0
        if !self.data.is_empty() {
280
0
            match self.data.as_bytes()[0] {
281
0
                b'/' | b'*' => fmt.write_str(&self.data),
282
                _ => {
283
0
                    fmt.write_str("/")?;
284
0
                    fmt.write_str(&self.data)
285
                }
286
            }
287
        } else {
288
0
            fmt.write_str("/")
289
        }
290
0
    }
291
}
292
293
impl hash::Hash for PathAndQuery {
294
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
295
        self.data.hash(state);
296
    }
297
}
298
299
// ===== PartialEq / PartialOrd =====
300
301
impl PartialEq for PathAndQuery {
302
    #[inline]
303
    fn eq(&self, other: &PathAndQuery) -> bool {
304
        self.data == other.data
305
    }
306
}
307
308
impl Eq for PathAndQuery {}
309
310
impl PartialEq<str> for PathAndQuery {
311
    #[inline]
312
    fn eq(&self, other: &str) -> bool {
313
        self.as_str() == other
314
    }
315
}
316
317
impl PartialEq<PathAndQuery> for &str {
318
    #[inline]
319
    fn eq(&self, other: &PathAndQuery) -> bool {
320
        self == &other.as_str()
321
    }
322
}
323
324
impl PartialEq<&str> for PathAndQuery {
325
    #[inline]
326
    fn eq(&self, other: &&str) -> bool {
327
        self.as_str() == *other
328
    }
329
}
330
331
impl PartialEq<PathAndQuery> for str {
332
    #[inline]
333
    fn eq(&self, other: &PathAndQuery) -> bool {
334
        self == other.as_str()
335
    }
336
}
337
338
impl PartialEq<String> for PathAndQuery {
339
    #[inline]
340
    fn eq(&self, other: &String) -> bool {
341
        self.as_str() == other.as_str()
342
    }
343
}
344
345
impl PartialEq<PathAndQuery> for String {
346
    #[inline]
347
    fn eq(&self, other: &PathAndQuery) -> bool {
348
        self.as_str() == other.as_str()
349
    }
350
}
351
352
impl PartialOrd for PathAndQuery {
353
    #[inline]
354
    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
355
        self.as_str().partial_cmp(other.as_str())
356
    }
357
}
358
359
impl PartialOrd<str> for PathAndQuery {
360
    #[inline]
361
    fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
362
        self.as_str().partial_cmp(other)
363
    }
364
}
365
366
impl PartialOrd<PathAndQuery> for str {
367
    #[inline]
368
    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
369
        self.partial_cmp(other.as_str())
370
    }
371
}
372
373
impl PartialOrd<&str> for PathAndQuery {
374
    #[inline]
375
    fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> {
376
        self.as_str().partial_cmp(*other)
377
    }
378
}
379
380
impl PartialOrd<PathAndQuery> for &str {
381
    #[inline]
382
    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
383
        self.partial_cmp(&other.as_str())
384
    }
385
}
386
387
impl PartialOrd<String> for PathAndQuery {
388
    #[inline]
389
    fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
390
        self.as_str().partial_cmp(other.as_str())
391
    }
392
}
393
394
impl PartialOrd<PathAndQuery> for String {
395
    #[inline]
396
    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
397
        self.as_str().partial_cmp(other.as_str())
398
    }
399
}
400
401
// Scanner implementation that is `const fn`, usable by both `from_static`
402
// and `from_shared`.
403
// =====
404
405
struct Scanned {
406
    query: u16,
407
    fragment: Option<u16>,
408
    is_maybe_not_utf8: bool,
409
}
410
411
// Per-byte character classes for the path and query scanners.
412
const CLASS_VALID: u8 = 0;
413
const CLASS_QUERY: u8 = 1;
414
const CLASS_FRAGMENT: u8 = 2;
415
const CLASS_HIGH: u8 = 3;
416
const CLASS_INVALID: u8 = 4;
417
418
const fn build_path_map() -> [u8; 256] {
419
    let mut t = [CLASS_INVALID; 256];
420
    let mut i = 0;
421
    while i < 256 {
422
        // See https://url.spec.whatwg.org/#path-state
423
        t[i] = match i as u8 {
424
            b'?' => CLASS_QUERY,
425
            b'#' => CLASS_FRAGMENT,
426
427
            // Bytes that don't need to be percent-encoded in the path.
428
            0x21 | 0x24..=0x3B | 0x3D | 0x40..=0x5F | 0x61..=0x7A | 0x7C | 0x7E => CLASS_VALID,
429
430
            // Potentially utf8, checked later.
431
            0x80..=0xFF => CLASS_HIGH,
432
433
            // Should be percent-encoded, but accepted for parity with clients
434
            // that send them as-is (e.g. JSON embedded in the path).
435
            b'"' | b'{' | b'}' => CLASS_VALID,
436
437
            _ => CLASS_INVALID,
438
        };
439
        i += 1;
440
    }
441
    t
442
}
443
444
const fn build_query_map() -> [u8; 256] {
445
    let mut t = [CLASS_INVALID; 256];
446
    let mut i = 0;
447
    while i < 256 {
448
        // See https://url.spec.whatwg.org/#query-state
449
        t[i] = match i as u8 {
450
            b'#' => CLASS_FRAGMENT,
451
452
            // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E
453
            0x21 | 0x24..=0x3B | 0x3D | 0x3F..=0x7E => CLASS_VALID,
454
455
            0x80..=0xFF => CLASS_HIGH,
456
457
            _ => CLASS_INVALID,
458
        };
459
        i += 1;
460
    }
461
    t
462
}
463
464
const PATH_MAP: [u8; 256] = build_path_map();
465
const QUERY_MAP: [u8; 256] = build_query_map();
466
467
196
const fn scan_path_and_query(bytes: &[u8]) -> Result<Scanned, ErrorKind> {
468
196
    let mut i = 0;
469
196
    let mut query = NONE;
470
196
    let mut fragment = None;
471
472
196
    let mut is_maybe_not_utf8 = false;
473
474
196
    if bytes.is_empty() {
475
0
        return Err(ErrorKind::Empty);
476
196
    }
477
478
196
    if bytes.len() > MAX_LEN {
479
0
        return Err(ErrorKind::TooLong);
480
196
    }
481
482
196
    if bytes.len() == 1 && bytes[0] == b'*' {
483
0
        return Ok(Scanned {
484
0
            query,
485
0
            fragment,
486
0
            is_maybe_not_utf8: false,
487
0
        });
488
196
    }
489
490
196
    if !matches!(bytes[0], b'/' | b'?' | b'#') {
491
0
        return Err(ErrorKind::PathDoesNotStartWithSlash);
492
196
    }
493
494
525k
    while i < bytes.len() {
495
525k
        match PATH_MAP[bytes[i] as usize] {
496
521k
            CLASS_VALID => {}
497
            CLASS_QUERY => {
498
85
                debug_assert!(query == NONE);
499
85
                query = i as u16;
500
85
                i += 1;
501
85
                break;
502
            }
503
            CLASS_FRAGMENT => {
504
47
                fragment = Some(i as u16);
505
47
                break;
506
            }
507
3.60k
            CLASS_HIGH => {
508
3.60k
                is_maybe_not_utf8 = true;
509
3.60k
            }
510
37
            _ => return Err(ErrorKind::InvalidUriChar),
511
        }
512
525k
        i += 1;
513
    }
514
515
    // query ...
516
159
    if query != NONE {
517
100k
        while i < bytes.len() {
518
100k
            match QUERY_MAP[bytes[i] as usize] {
519
94.1k
                CLASS_VALID => {}
520
6.46k
                CLASS_HIGH => {
521
6.46k
                    is_maybe_not_utf8 = true;
522
6.46k
                }
523
                CLASS_FRAGMENT => {
524
16
                    fragment = Some(i as u16);
525
16
                    break;
526
                }
527
48
                _ => return Err(ErrorKind::InvalidUriChar),
528
            }
529
100k
            i += 1;
530
        }
531
74
    }
532
533
111
    Ok(Scanned {
534
111
        query,
535
111
        fragment,
536
111
        is_maybe_not_utf8,
537
111
    })
538
196
}
539
540
#[cfg(test)]
541
mod tests {
542
    use super::*;
543
544
    #[test]
545
    fn equal_to_self_of_same_path() {
546
        let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
547
        let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
548
        assert_eq!(p1, p2);
549
        assert_eq!(p2, p1);
550
    }
551
552
    #[test]
553
    fn not_equal_to_self_of_different_path() {
554
        let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
555
        let p2: PathAndQuery = "/world&foo=bar".parse().unwrap();
556
        assert_ne!(p1, p2);
557
        assert_ne!(p2, p1);
558
    }
559
560
    #[test]
561
    fn equates_with_a_str() {
562
        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
563
        assert_eq!(&path_and_query, "/hello/world&foo=bar");
564
        assert_eq!("/hello/world&foo=bar", &path_and_query);
565
        assert_eq!(path_and_query, "/hello/world&foo=bar");
566
        assert_eq!("/hello/world&foo=bar", path_and_query);
567
    }
568
569
    #[test]
570
    fn not_equal_with_a_str_of_a_different_path() {
571
        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
572
        // as a reference
573
        assert_ne!(&path_and_query, "/hello&foo=bar");
574
        assert_ne!("/hello&foo=bar", &path_and_query);
575
        // without reference
576
        assert_ne!(path_and_query, "/hello&foo=bar");
577
        assert_ne!("/hello&foo=bar", path_and_query);
578
    }
579
580
    #[test]
581
    fn equates_with_a_string() {
582
        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
583
        assert_eq!(path_and_query, "/hello/world&foo=bar".to_string());
584
        assert_eq!("/hello/world&foo=bar".to_string(), path_and_query);
585
    }
586
587
    #[test]
588
    fn not_equal_with_a_string_of_a_different_path() {
589
        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
590
        assert_ne!(path_and_query, "/hello&foo=bar".to_string());
591
        assert_ne!("/hello&foo=bar".to_string(), path_and_query);
592
    }
593
594
    #[test]
595
    fn compares_to_self() {
596
        let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap();
597
        let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
598
        assert!(p1 < p2);
599
        assert!(p2 > p1);
600
    }
601
602
    #[test]
603
    fn compares_with_a_str() {
604
        let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
605
        // by ref
606
        assert!(&path_and_query < "/c/world&foo=bar");
607
        assert!("/c/world&foo=bar" > &path_and_query);
608
        assert!(&path_and_query > "/a/world&foo=bar");
609
        assert!("/a/world&foo=bar" < &path_and_query);
610
611
        // by val
612
        assert!(path_and_query < "/c/world&foo=bar");
613
        assert!("/c/world&foo=bar" > path_and_query);
614
        assert!(path_and_query > "/a/world&foo=bar");
615
        assert!("/a/world&foo=bar" < path_and_query);
616
    }
617
618
    #[test]
619
    fn compares_with_a_string() {
620
        let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
621
        assert!(path_and_query < "/c/world&foo=bar".to_string());
622
        assert!("/c/world&foo=bar".to_string() > path_and_query);
623
        assert!(path_and_query > "/a/world&foo=bar".to_string());
624
        assert!("/a/world&foo=bar".to_string() < path_and_query);
625
    }
626
627
    #[test]
628
    fn ignores_valid_percent_encodings() {
629
        assert_eq!("/a%20b", pq("/a%20b?r=1").path());
630
        assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap());
631
    }
632
633
    #[test]
634
    fn ignores_invalid_percent_encodings() {
635
        assert_eq!("/a%%b", pq("/a%%b?r=1").path());
636
        assert_eq!("/aaa%", pq("/aaa%").path());
637
        assert_eq!("/aaa%", pq("/aaa%?r=1").path());
638
        assert_eq!("/aa%2", pq("/aa%2").path());
639
        assert_eq!("/aa%2", pq("/aa%2?r=1").path());
640
        assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap());
641
    }
642
643
    #[test]
644
    fn allow_utf8_in_path() {
645
        assert_eq!("/🍕", pq("/🍕").path());
646
    }
647
648
    #[test]
649
    fn allow_utf8_in_query() {
650
        assert_eq!(Some("pizza=🍕"), pq("/test?pizza=🍕").query());
651
    }
652
653
    #[test]
654
    fn rejects_invalid_utf8_in_path() {
655
        PathAndQuery::try_from(&[b'/', 0xFF][..]).expect_err("reject invalid utf8");
656
    }
657
658
    #[test]
659
    fn rejects_invalid_utf8_in_query() {
660
        PathAndQuery::try_from(&[b'/', b'a', b'?', 0xFF][..]).expect_err("reject invalid utf8");
661
    }
662
663
    #[test]
664
    fn rejects_empty_string() {
665
        PathAndQuery::try_from("").expect_err("reject empty str");
666
    }
667
668
    #[test]
669
    fn requires_starting_with_slash() {
670
        PathAndQuery::try_from("sneaky").expect_err("reject missing slash");
671
    }
672
673
    #[test]
674
    fn rejects_del_in_path() {
675
        PathAndQuery::try_from(&[b'/', 0x7F][..]).expect_err("reject DEL");
676
    }
677
678
    #[test]
679
    fn rejects_del_in_query() {
680
        PathAndQuery::try_from(&[b'/', b'a', b'?', 0x7F][..]).expect_err("reject DEL");
681
    }
682
683
    #[test]
684
    fn rejects_too_long_path_and_query() {
685
        let path = format!("/{}?query", "a".repeat(MAX_LEN));
686
        let err = PathAndQuery::try_from(path).expect_err("reject overly long path and query");
687
        assert_eq!(err.0, ErrorKind::TooLong);
688
    }
689
690
    #[test]
691
    fn accepts_max_length_path_and_query() {
692
        let path = format!("/{}?", "a".repeat(MAX_LEN - 2));
693
        let path_and_query = PathAndQuery::try_from(path).expect("accept maximum length");
694
        assert_eq!(path_and_query.as_str().len(), MAX_LEN);
695
        assert_eq!(path_and_query.query(), Some(""));
696
    }
697
698
    #[test]
699
    fn json_is_fine() {
700
        assert_eq!(
701
            r#"/{"bread":"baguette"}"#,
702
            pq(r#"/{"bread":"baguette"}"#).path()
703
        );
704
    }
705
706
    fn pq(s: &str) -> PathAndQuery {
707
        s.parse().expect(&format!("parsing {}", s))
708
    }
709
}