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}