use std::convert::Infallible;
use std::fmt;
use std::str::FromStr;
use futures::future;
use http::uri::PathAndQuery;
use self::internal::Opaque;
use crate::filter::{filter_fn, one, Filter, FilterBase, Internal, One, Tuple};
use crate::reject::{self, Rejection};
use crate::route::{self, Route};
pub fn path<P>(p: P) -> Exact<Opaque<P>>
where
P: AsRef<str>,
{
let s = p.as_ref();
assert!(!s.is_empty(), "exact path segments should not be empty");
assert!(
!s.contains('/'),
"exact path segments should not contain a slash: {:?}",
s
);
Exact(Opaque(p))
}
#[allow(missing_debug_implementations)]
#[derive(Clone, Copy)]
pub struct Exact<P>(P);
impl<P> FilterBase for Exact<P>
where
P: AsRef<str>,
{
type Extract = ();
type Error = Rejection;
type Future = future::Ready<Result<Self::Extract, Self::Error>>;
#[inline]
fn filter(&self, _: Internal) -> Self::Future {
route::with(|route| {
let p = self.0.as_ref();
future::ready(with_segment(route, |seg| {
tracing::trace!("{:?}?: {:?}", p, seg);
if seg == p {
Ok(())
} else {
Err(reject::not_found())
}
}))
})
}
}
pub fn end() -> impl Filter<Extract = (), Error = Rejection> + Copy {
filter_fn(move |route| {
if route.path().is_empty() {
future::ok(())
} else {
future::err(reject::not_found())
}
})
}
pub fn param<T: FromStr + Send + 'static>(
) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
filter_segment(|seg| {
tracing::trace!("param?: {:?}", seg);
if seg.is_empty() {
return Err(reject::not_found());
}
T::from_str(seg).map(one).map_err(|_| reject::not_found())
})
}
pub fn tail() -> impl Filter<Extract = One<Tail>, Error = Infallible> + Copy {
filter_fn(move |route| {
let path = path_and_query(&route);
let idx = route.matched_path_index();
let end = path.path().len() - idx;
route.set_unmatched_path(end);
future::ok(one(Tail {
path,
start_index: idx,
}))
})
}
pub struct Tail {
path: PathAndQuery,
start_index: usize,
}
impl Tail {
pub fn as_str(&self) -> &str {
&self.path.path()[self.start_index..]
}
}
impl fmt::Debug for Tail {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
pub fn peek() -> impl Filter<Extract = One<Peek>, Error = Infallible> + Copy {
filter_fn(move |route| {
let path = path_and_query(&route);
let idx = route.matched_path_index();
future::ok(one(Peek {
path,
start_index: idx,
}))
})
}
pub struct Peek {
path: PathAndQuery,
start_index: usize,
}
impl Peek {
pub fn as_str(&self) -> &str {
&self.path.path()[self.start_index..]
}
pub fn segments(&self) -> impl Iterator<Item = &str> {
self.as_str().split('/').filter(|seg| !seg.is_empty())
}
}
impl fmt::Debug for Peek {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
pub fn full() -> impl Filter<Extract = One<FullPath>, Error = Infallible> + Copy {
filter_fn(move |route| future::ok(one(FullPath(path_and_query(&route)))))
}
pub struct FullPath(PathAndQuery);
impl FullPath {
pub fn as_str(&self) -> &str {
&self.0.path()
}
}
impl fmt::Debug for FullPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
fn filter_segment<F, U>(func: F) -> impl Filter<Extract = U, Error = Rejection> + Copy
where
F: Fn(&str) -> Result<U, Rejection> + Copy,
U: Tuple + Send + 'static,
{
filter_fn(move |route| future::ready(with_segment(route, func)))
}
fn with_segment<F, U>(route: &mut Route, func: F) -> Result<U, Rejection>
where
F: Fn(&str) -> Result<U, Rejection>,
{
let seg = segment(route);
let ret = func(seg);
if ret.is_ok() {
let idx = seg.len();
route.set_unmatched_path(idx);
}
ret
}
fn segment(route: &Route) -> &str {
route
.path()
.splitn(2, '/')
.next()
.expect("split always has at least 1")
}
fn path_and_query(route: &Route) -> PathAndQuery {
route
.uri()
.path_and_query()
.cloned()
.unwrap_or_else(|| PathAndQuery::from_static(""))
}
#[macro_export]
macro_rules! path {
($($pieces:tt)*) => ({
$crate::__internal_path!(@start $($pieces)*)
});
}
#[doc(hidden)]
#[macro_export]
macro_rules! __internal_path {
(@start ..) => ({
compile_error!("'..' cannot be the only segment")
});
(@start $first:tt $(/ $tail:tt)*) => ({
$crate::__internal_path!(@munch $crate::any(); [$first] [$(/ $tail)*])
});
(@munch $sum:expr; [$cur:tt] [/ $next:tt $(/ $tail:tt)*]) => ({
$crate::__internal_path!(@munch $crate::Filter::and($sum, $crate::__internal_path!(@segment $cur)); [$next] [$(/ $tail)*])
});
(@munch $sum:expr; [$cur:tt] []) => ({
$crate::__internal_path!(@last $sum; $cur)
});
(@last $sum:expr; ..) => (
$sum
);
(@last $sum:expr; $end:tt) => (
$crate::Filter::and(
$crate::Filter::and($sum, $crate::__internal_path!(@segment $end)),
$crate::path::end()
)
);
(@segment ..) => (
compile_error!("'..' must be the last segment")
);
(@segment $param:ty) => (
$crate::path::param::<$param>()
);
(@segment $s:literal) => ({
#[derive(Clone, Copy)]
struct __StaticPath;
impl ::std::convert::AsRef<str> for __StaticPath {
fn as_ref(&self) -> &str {
static S: &str = $s;
S
}
}
$crate::path(__StaticPath)
});
}
fn _path_macro_compile_fail() {}
mod internal {
#[allow(missing_debug_implementations)]
#[derive(Clone, Copy)]
pub struct Opaque<T>(pub(super) T);
impl<T: AsRef<str>> AsRef<str> for Opaque<T> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_exact_size() {
use std::mem::{size_of, size_of_val};
assert_eq!(
size_of_val(&path("hello")),
size_of::<&str>(),
"exact(&str) is size of &str"
);
assert_eq!(
size_of_val(&path(String::from("world"))),
size_of::<String>(),
"exact(String) is size of String"
);
assert_eq!(
size_of_val(&path!("zst")),
size_of::<()>(),
"path!(&str) is ZST"
);
}
}