warp/filters/
path.rs

1//! Path Filters
2//!
3//! The [`Filter`](crate::Filter)s here work on the "path" of requests.
4//!
5//! - [`path`](./fn.path.html) matches a specific segment, like `/foo`.
6//! - [`param`](./fn.param.html) tries to parse a segment into a type, like `/:u16`.
7//! - [`end`](./fn.end.html) matches when the path end is found.
8//! - [`path!`](../../macro.path.html) eases combining multiple `path` and `param` filters.
9//!
10//! # Routing
11//!
12//! Routing in warp is simple yet powerful.
13//!
14//! First up, matching a single segment:
15//!
16//! ```
17//! use warp::Filter;
18//!
19//! // GET /hi
20//! let hi = warp::path("hi").map(|| {
21//!     "Hello, World!"
22//! });
23//! ```
24//!
25//! How about multiple segments? It's easiest with the `path!` macro:
26//!
27//! ```
28//! # use warp::Filter;
29//! // GET /hello/from/warp
30//! let hello_from_warp = warp::path!("hello" / "from" / "warp").map(|| {
31//!     "Hello from warp!"
32//! });
33//! ```
34//!
35//! Neat! But how do I handle **parameters** in paths?
36//!
37//! ```
38//! # use warp::Filter;
39//! // GET /sum/:u32/:u32
40//! let sum = warp::path!("sum" / u32 / u32).map(|a, b| {
41//!     format!("{} + {} = {}", a, b, a + b)
42//! });
43//! ```
44//!
45//! In fact, any type that implements `FromStr` can be used, in any order:
46//!
47//! ```
48//! # use warp::Filter;
49//! // GET /:u16/times/:u16
50//! let times = warp::path!(u16 / "times" / u16).map(|a, b| {
51//!     format!("{} times {} = {}", a, b, a * b)
52//! });
53//! ```
54//!
55//! Oh shoot, those math routes should be **mounted** at a different path,
56//! is that possible? Yep!
57//!
58//! ```
59//! # use warp::Filter;
60//! # let sum = warp::any().map(warp::reply);
61//! # let times = sum.clone();
62//! // GET /math/sum/:u32/:u32
63//! // GET /math/:u16/times/:u16
64//! let math = warp::path("math");
65//! let math_sum = math.and(sum);
66//! let math_times = math.and(times);
67//! ```
68//!
69//! What! `and`? What's that do?
70//!
71//! It combines the filters in a sort of "this and then that" order. In fact,
72//! it's exactly what the `path!` macro has been doing internally.
73//!
74//! ```
75//! # use warp::Filter;
76//! // GET /bye/:string
77//! let bye = warp::path("bye")
78//!     .and(warp::path::param())
79//!     .map(|name: String| {
80//!         format!("Good bye, {}!", name)
81//!     });
82//! ```
83//!
84//! Ah, so, can filters do things besides `and`?
85//!
86//! Why, yes they can! They can also `or`! As you might expect, `or` creates a
87//! "this or else that" chain of filters. If the first doesn't succeed, then
88//! it tries the other.
89//!
90//! So, those `math` routes could have been **mounted** all as one, with `or`.
91//!
92//!
93//! ```
94//! # use warp::Filter;
95//! # let sum = warp::path("sum");
96//! # let times = warp::path("times");
97//! // GET /math/sum/:u32/:u32
98//! // GET /math/:u16/times/:u16
99//! let math = warp::path("math")
100//!     .and(sum.or(times));
101//! ```
102//!
103//! It turns out, using `or` is how you combine everything together into a
104//! single API.
105//!
106//! ```
107//! # use warp::Filter;
108//! # let hi = warp::path("hi");
109//! # let hello_from_warp = hi.clone();
110//! # let bye = hi.clone();
111//! # let math = hi.clone();
112//! // GET /hi
113//! // GET /hello/from/warp
114//! // GET /bye/:string
115//! // GET /math/sum/:u32/:u32
116//! // GET /math/:u16/times/:u16
117//! let routes = hi
118//!     .or(hello_from_warp)
119//!     .or(bye)
120//!     .or(math);
121//! ```
122//!
123//! Note that you will generally want path filters to come **before** other filters
124//! like `body` or `headers`. If a different type of filter comes first, a request
125//! with an invalid body for route `/right-path-wrong-body` may try matching against `/wrong-path`
126//! and return the error from `/wrong-path` instead of the correct body-related error.
127
128use std::convert::Infallible;
129use std::fmt;
130use std::str::FromStr;
131
132use futures_util::future;
133use http::uri::PathAndQuery;
134
135use self::internal::Opaque;
136use crate::filter::{filter_fn, one, Filter, FilterBase, Internal, One, Tuple};
137use crate::reject::{self, Rejection};
138use crate::route::{self, Route};
139
140/// Create an exact match path segment [`Filter`](crate::Filter).
141///
142/// This will try to match exactly to the current request path segment.
143///
144/// # Note
145///
146/// - [`end()`](./fn.end.html) should be used to match the end of a path to avoid having
147///   filters for shorter paths like `/math` unintentionally match a longer
148///   path such as `/math/sum`
149/// - Path-related filters should generally come **before** other types of filters, such
150///   as those checking headers or body types. Including those other filters before
151///   the path checks may result in strange errors being returned because a given request
152///   does not match the parameters for a completely separate route.
153///
154/// # Panics
155///
156/// Exact path filters cannot be empty, or contain slashes.
157///
158/// # Example
159///
160/// ```
161/// use warp::Filter;
162///
163/// // Matches '/hello'
164/// let hello = warp::path("hello")
165///     .map(|| "Hello, World!");
166/// ```
167pub fn path<P>(p: P) -> Exact<Opaque<P>>
168where
169    P: AsRef<str>,
170{
171    let s = p.as_ref();
172    assert!(!s.is_empty(), "exact path segments should not be empty");
173    assert!(
174        !s.contains('/'),
175        "exact path segments should not contain a slash: {:?}",
176        s
177    );
178
179    Exact(Opaque(p))
180    /*
181    segment(move |seg| {
182        tracing::trace!("{:?}?: {:?}", p, seg);
183        if seg == p {
184            Ok(())
185        } else {
186            Err(reject::not_found())
187        }
188    })
189    */
190}
191
192/// A [`Filter`](crate::Filter) matching an exact path segment.
193///
194/// Constructed from `path()` or `path!()`.
195#[allow(missing_debug_implementations)]
196#[derive(Clone, Copy)]
197pub struct Exact<P>(P);
198
199impl<P> FilterBase for Exact<P>
200where
201    P: AsRef<str>,
202{
203    type Extract = ();
204    type Error = Rejection;
205    type Future = future::Ready<Result<Self::Extract, Self::Error>>;
206
207    #[inline]
208    fn filter(&self, _: Internal) -> Self::Future {
209        route::with(|route| {
210            let p = self.0.as_ref();
211            future::ready(with_segment(route, |seg| {
212                tracing::trace!("{:?}?: {:?}", p, seg);
213
214                if seg == p {
215                    Ok(())
216                } else {
217                    Err(reject::not_found())
218                }
219            }))
220        })
221    }
222}
223
224/// Matches the end of a route.
225///
226/// Note that _not_ including `end()` may result in shorter paths like
227/// `/math` unintentionally matching `/math/sum`.
228///
229/// # Example
230///
231/// ```
232/// use warp::Filter;
233///
234/// // Matches '/'
235/// let hello = warp::path::end()
236///     .map(|| "Hello, World!");
237/// ```
238pub fn end() -> impl Filter<Extract = (), Error = Rejection> + Copy {
239    filter_fn(move |route| {
240        if route.path().is_empty() {
241            future::ok(())
242        } else {
243            future::err(reject::not_found())
244        }
245    })
246}
247
248/// Extract a parameter from a path segment.
249///
250/// This will try to parse a value from the current request path
251/// segment, and if successful, the value is returned as the `Filter`'s
252/// "extracted" value.
253///
254/// If the value could not be parsed, rejects with a `404 Not Found`.
255///
256/// # Example
257///
258/// ```
259/// use warp::Filter;
260///
261/// let route = warp::path::param()
262///     .map(|id: u32| {
263///         format!("You asked for /{}", id)
264///     });
265/// ```
266pub fn param<T: FromStr + Send + 'static>(
267) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
268    filter_segment(|seg| {
269        tracing::trace!("param?: {:?}", seg);
270        if seg.is_empty() {
271            return Err(reject::not_found());
272        }
273        T::from_str(seg).map(one).map_err(|_| reject::not_found())
274    })
275}
276
277/// Extract the unmatched tail of the path.
278///
279/// This will return a `Tail`, which allows access to the rest of the path
280/// that previous filters have not already matched.
281///
282/// # Example
283///
284/// ```
285/// use warp::Filter;
286///
287/// let route = warp::path("foo")
288///     .and(warp::path::tail())
289///     .map(|tail| {
290///         // GET /foo/bar/baz would return "bar/baz".
291///         format!("The tail after foo is {:?}", tail)
292///     });
293/// ```
294pub fn tail() -> impl Filter<Extract = One<Tail>, Error = Infallible> + Copy {
295    filter_fn(move |route| {
296        let path = path_and_query(route);
297        let idx = route.matched_path_index();
298
299        // Giving the user the full tail means we assume the full path
300        // has been matched now.
301        let end = path.path().len() - idx;
302        route.set_unmatched_path(end);
303
304        future::ok(one(Tail {
305            path,
306            start_index: idx,
307        }))
308    })
309}
310
311/// Represents the tail part of a request path, returned by the [`tail()`] filter.
312pub struct Tail {
313    path: PathAndQuery,
314    start_index: usize,
315}
316
317impl Tail {
318    /// Get the `&str` representation of the remaining path.
319    pub fn as_str(&self) -> &str {
320        &self.path.path()[self.start_index..]
321    }
322}
323
324impl fmt::Debug for Tail {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        fmt::Debug::fmt(self.as_str(), f)
327    }
328}
329
330/// Peek at the unmatched tail of the path, without affecting the matched path.
331///
332/// This will return a `Peek`, which allows access to the rest of the path
333/// that previous filters have not already matched. This differs from `tail`
334/// in that `peek` will **not** set the entire path as matched.
335///
336/// # Example
337///
338/// ```
339/// use warp::Filter;
340///
341/// let route = warp::path("foo")
342///     .and(warp::path::peek())
343///     .map(|peek| {
344///         // GET /foo/bar/baz would return "bar/baz".
345///         format!("The path after foo is {:?}", peek)
346///     });
347/// ```
348pub fn peek() -> impl Filter<Extract = One<Peek>, Error = Infallible> + Copy {
349    filter_fn(move |route| {
350        let path = path_and_query(route);
351        let idx = route.matched_path_index();
352
353        future::ok(one(Peek {
354            path,
355            start_index: idx,
356        }))
357    })
358}
359
360/// Represents the tail part of a request path, returned by the [`peek()`] filter.
361pub struct Peek {
362    path: PathAndQuery,
363    start_index: usize,
364}
365
366impl Peek {
367    /// Get the `&str` representation of the remaining path.
368    pub fn as_str(&self) -> &str {
369        &self.path.path()[self.start_index..]
370    }
371
372    /// Get an iterator over the segments of the peeked path.
373    pub fn segments(&self) -> impl Iterator<Item = &str> {
374        self.as_str().split('/').filter(|seg| !seg.is_empty())
375    }
376}
377
378impl fmt::Debug for Peek {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        fmt::Debug::fmt(self.as_str(), f)
381    }
382}
383
384/// Returns the full request path, irrespective of other filters.
385///
386/// This will return a `FullPath`, which can be stringified to return the
387/// full path of the request.
388///
389/// This is more useful in generic pre/post-processing filters, and should
390/// probably not be used for request matching/routing.
391///
392/// # Example
393///
394/// ```
395/// use warp::{Filter, path::FullPath};
396/// use std::{collections::HashMap, sync::{Arc, Mutex}};
397///
398/// let counts = Arc::new(Mutex::new(HashMap::new()));
399/// let access_counter = warp::path::full()
400///     .map(move |path: FullPath| {
401///         let mut counts = counts.lock().unwrap();
402///
403///         *counts.entry(path.as_str().to_string())
404///             .and_modify(|c| *c += 1)
405///             .or_insert(0)
406///     });
407///
408/// let route = warp::path("foo")
409///     .and(warp::path("bar"))
410///     .and(access_counter)
411///     .map(|count| {
412///         format!("This is the {}th visit to this URL!", count)
413///     });
414/// ```
415pub fn full() -> impl Filter<Extract = One<FullPath>, Error = Infallible> + Copy {
416    filter_fn(move |route| future::ok(one(FullPath(path_and_query(route)))))
417}
418
419/// Represents the full request path, returned by the [`full()`] filter.
420pub struct FullPath(PathAndQuery);
421
422impl FullPath {
423    /// Get the `&str` representation of the request path.
424    pub fn as_str(&self) -> &str {
425        self.0.path()
426    }
427}
428
429impl fmt::Debug for FullPath {
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        fmt::Debug::fmt(self.as_str(), f)
432    }
433}
434
435fn filter_segment<F, U>(func: F) -> impl Filter<Extract = U, Error = Rejection> + Copy
436where
437    F: Fn(&str) -> Result<U, Rejection> + Copy,
438    U: Tuple + Send + 'static,
439{
440    filter_fn(move |route| future::ready(with_segment(route, func)))
441}
442
443fn with_segment<F, U>(route: &mut Route, func: F) -> Result<U, Rejection>
444where
445    F: Fn(&str) -> Result<U, Rejection>,
446{
447    let seg = segment(route);
448    let ret = func(seg);
449    if ret.is_ok() {
450        let idx = seg.len();
451        route.set_unmatched_path(idx);
452    }
453    ret
454}
455
456fn segment(route: &Route) -> &str {
457    route
458        .path()
459        .splitn(2, '/')
460        .next()
461        .expect("split always has at least 1")
462}
463
464fn path_and_query(route: &Route) -> PathAndQuery {
465    route
466        .uri()
467        .path_and_query()
468        .cloned()
469        .unwrap_or_else(|| PathAndQuery::from_static(""))
470}
471
472/// Convenient way to chain multiple path filters together.
473///
474/// Any number of either type identifiers or string expressions can be passed,
475/// each separated by a forward slash (`/`). Strings will be used to match
476/// path segments exactly, and type identifiers are used just like
477/// [`param`](crate::path::param) filters.
478///
479/// # Example
480///
481/// ```
482/// use warp::Filter;
483///
484/// // Match `/sum/:a/:b`
485/// let route = warp::path!("sum" / u32 / u32)
486///     .map(|a, b| {
487///         format!("{} + {} = {}", a, b, a + b)
488///     });
489/// ```
490///
491/// The equivalent filter chain without using the `path!` macro looks this:
492///
493/// ```
494/// use warp::Filter;
495///
496/// let route = warp::path("sum")
497///     .and(warp::path::param::<u32>())
498///     .and(warp::path::param::<u32>())
499///     .and(warp::path::end())
500///     .map(|a, b| {
501///         format!("{} + {} = {}", a, b, a + b)
502///     });
503/// ```
504///
505/// # Path Prefixes
506///
507/// The `path!` macro automatically assumes the path should include an `end()`
508/// filter. To build up a path filter *prefix*, such that the `end()` isn't
509/// included, use the `/ ..` syntax.
510///
511///
512/// ```
513/// use warp::Filter;
514///
515/// let prefix = warp::path!("math" / "sum" / ..);
516///
517/// let sum = warp::path!(u32 / u32)
518///     .map(|a, b| {
519///         format!("{} + {} = {}", a, b, a + b)
520///     });
521///
522/// let help = warp::path::end()
523///     .map(|| "This API returns the sum of two u32's");
524///
525/// let api = prefix.and(sum.or(help));
526/// ```
527#[macro_export]
528macro_rules! path {
529    ($($pieces:tt)*) => ({
530        $crate::__internal_path!(@start $($pieces)*)
531    });
532}
533
534#[doc(hidden)]
535#[macro_export]
536// not public API
537macro_rules! __internal_path {
538    (@start) => (
539        $crate::path::end()
540    );
541    (@start ..) => ({
542        compile_error!("'..' cannot be the only segment")
543    });
544    (@start $first:tt $(/ $tail:tt)*) => ({
545        $crate::__internal_path!(@munch $crate::any(); [$first] [$(/ $tail)*])
546    });
547
548    (@munch $sum:expr; [$cur:tt] [/ $next:tt $(/ $tail:tt)*]) => ({
549        $crate::__internal_path!(@munch $crate::Filter::and($sum, $crate::__internal_path!(@segment $cur)); [$next] [$(/ $tail)*])
550    });
551    (@munch $sum:expr; [$cur:tt] []) => ({
552        $crate::__internal_path!(@last $sum; $cur)
553    });
554
555    (@last $sum:expr; ..) => (
556        $sum
557    );
558    (@last $sum:expr; $end:tt) => (
559        $crate::Filter::and(
560            $crate::Filter::and($sum, $crate::__internal_path!(@segment $end)),
561            $crate::path::end()
562        )
563    );
564
565    (@segment ..) => (
566        compile_error!("'..' must be the last segment")
567    );
568    (@segment $param:ty) => (
569        $crate::path::param::<$param>()
570    );
571    // Constructs a unique ZST so the &'static str pointer doesn't need to
572    // be carried around.
573    (@segment $s:literal) => ({
574        #[derive(Clone, Copy)]
575        struct __StaticPath;
576        impl ::std::convert::AsRef<str> for __StaticPath {
577            fn as_ref(&self) -> &str {
578                static S: &str = $s;
579                S
580            }
581        }
582        $crate::path(__StaticPath)
583    });
584}
585
586// path! compile fail tests
587
588/// ```compile_fail
589/// warp::path!("foo" / .. / "bar");
590/// ```
591///
592/// ```compile_fail
593/// warp::path!(.. / "bar");
594/// ```
595///
596/// ```compile_fail
597/// warp::path!("foo" ..);
598/// ```
599///
600/// ```compile_fail
601/// warp::path!("foo" / .. /);
602/// ```
603///
604/// ```compile_fail
605/// warp::path!(..);
606/// ```
607fn _path_macro_compile_fail() {}
608
609mod internal {
610    // Used to prevent users from naming this type.
611    //
612    // For instance, `Exact<Opaque<String>>` means a user cannot depend
613    // on it being `Exact<String>`.
614    #[allow(missing_debug_implementations)]
615    #[derive(Clone, Copy)]
616    pub struct Opaque<T>(pub(super) T);
617
618    impl<T: AsRef<str>> AsRef<str> for Opaque<T> {
619        #[inline]
620        fn as_ref(&self) -> &str {
621            self.0.as_ref()
622        }
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629
630    #[test]
631    fn test_path_exact_size() {
632        use std::mem::{size_of, size_of_val};
633
634        assert_eq!(
635            size_of_val(&path("hello")),
636            size_of::<&str>(),
637            "exact(&str) is size of &str"
638        );
639
640        assert_eq!(
641            size_of_val(&path(String::from("world"))),
642            size_of::<String>(),
643            "exact(String) is size of String"
644        );
645
646        assert_eq!(
647            size_of_val(&path!("zst")),
648            size_of::<()>(),
649            "path!(&str) is ZST"
650        );
651    }
652}