Skip to content

Commit 390fe84

Browse files
committed
add custom path matcher
- this allows a custom function to - match multiple path segments - extract whatever it wants from the path - advance the consumed path to use path()/end() after
1 parent 6ae9520 commit 390fe84

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/filters/path.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,50 @@ pub fn param<T: FromStr + Send + 'static>(
274274
})
275275
}
276276

277+
/// A custom path matching filter.
278+
///
279+
/// This will call a function with the remaining path segment and allow the function
280+
/// to extract whatever it wishes and return the number of characters to advance int he path
281+
/// to continue standard path() filters.
282+
///
283+
/// The function can reject with any warp error. To pass on to other routes
284+
/// reject with warp::rejcct::not_found().
285+
///
286+
/// # Example
287+
///
288+
/// ```
289+
/// use warp::Filter;
290+
///
291+
/// let route = warp::path::custom(|tail: &str| {
292+
/// Ok((
293+
/// 10,
294+
/// ("Something Good".to_string(),)
295+
/// ))
296+
/// })
297+
/// .map(|data: String| {
298+
/// format!("You found /{}", data)
299+
/// });
300+
/// ```
301+
pub fn custom<F, T>(fun: F) -> impl Filter<Extract = T, Error = Rejection> + Copy
302+
where
303+
F: FnOnce(&str) -> Result<(usize, T), Rejection> + Copy + 'static,
304+
T: Tuple + Send + 'static,
305+
{
306+
filter_fn(move |route| {
307+
let remaining = route.path();
308+
let result = fun(remaining);
309+
match result {
310+
Ok((skip, extracted)) => {
311+
if skip > 0 {
312+
route.set_unmatched_path(skip);
313+
}
314+
future::ok(extracted)
315+
}
316+
Err(err) => future::err(err),
317+
}
318+
})
319+
}
320+
277321
/// Extract the unmatched tail of the path.
278322
///
279323
/// This will return a `Tail`, which allows access to the rest of the path

tests/path.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
extern crate warp;
44

55
use futures_util::future;
6+
use warp::path::Tail;
67
use warp::Filter;
78

89
#[tokio::test]
@@ -59,6 +60,71 @@ async fn param() {
5960
);
6061
}
6162

63+
#[tokio::test]
64+
async fn custom() {
65+
let _ = pretty_env_logger::try_init();
66+
67+
// extracting path segment and advancing
68+
let simple = warp::path::custom(|remaining| {
69+
if let Some(pos) = remaining.rfind('/') {
70+
let ret = &remaining[0..pos];
71+
Ok((ret.len(), (ret.to_string(),)))
72+
} else {
73+
Err(warp::reject::not_found())
74+
}
75+
})
76+
.and(warp::path::tail())
77+
.map(|m, t: Tail| (m, t.as_str().to_string()));
78+
79+
let req = warp::test::request().path("/one/two/three");
80+
assert_eq!(
81+
req.filter(&simple).await.unwrap(),
82+
("one/two".to_string(), "three".to_string())
83+
);
84+
85+
// no extracting
86+
let no_extract = warp::path::custom(|remaining| {
87+
if remaining.ends_with(".bmp") {
88+
Ok((0, ()))
89+
} else {
90+
Err(warp::reject::not_found())
91+
}
92+
})
93+
.and(warp::path::tail())
94+
.map(|t: Tail| (t.as_str().to_string()));
95+
96+
let req = warp::test::request().path("/one/two/three.bmp");
97+
assert_eq!(
98+
req.filter(&no_extract).await.unwrap(),
99+
("one/two/three.bmp".to_string())
100+
);
101+
102+
let req = warp::test::request().path("/one/two/three.png");
103+
assert!(
104+
!req.matches(&no_extract).await,
105+
"custom() doesn't match .png"
106+
);
107+
108+
// prefixed and postfixed path() matching
109+
let mixed = warp::path::path("prefix")
110+
.and(warp::path::custom(|remaining| {
111+
if let Some(pos) = remaining.rfind('/') {
112+
let ret = &remaining[0..pos];
113+
Ok((ret.len(), (ret.to_string(),)))
114+
} else {
115+
Err(warp::reject::not_found())
116+
}
117+
}))
118+
.and(warp::path::path("postfix"))
119+
.and(warp::path::end());
120+
121+
let req = warp::test::request().path("/prefix/middle/area/postfix");
122+
assert_eq!(
123+
req.filter(&mixed).await.unwrap(),
124+
("middle/area".to_string())
125+
);
126+
}
127+
62128
#[tokio::test]
63129
async fn end() {
64130
let _ = pretty_env_logger::try_init();

0 commit comments

Comments
 (0)