Compare commits

..

No commits in common. "a44969566120b9acd3e365602e5da2acb1ab5e7b" and "ee7621594ce14b3162de02b51a5e41149c9319c0" have entirely different histories.

1 changed files with 75 additions and 102 deletions

View File

@ -11,7 +11,7 @@
//! or handler. This interface is defined by the [`Guard`] trait. //! or handler. This interface is defined by the [`Guard`] trait.
//! //!
//! Commonly-used guards are provided in this module as well as a way of creating a guard from a //! Commonly-used guards are provided in this module as well as a way of creating a guard from a
//! closure ([`fn_guard`]). The [`Not`], [`Any()`], and [`All()`] guards are noteworthy, as they can be //! closure ([`fn_guard`]). The [`Not`], [`Any()`], and [`All`] guards are noteworthy, as they can be
//! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on //! used to compose other guards in a more flexible and semantic way than calling `.guard(...)` on
//! services multiple times (which might have different combining behavior than you want). //! services multiple times (which might have different combining behavior than you want).
//! //!
@ -50,6 +50,7 @@
//! [`Route`]: crate::Route::guard() //! [`Route`]: crate::Route::guard()
use std::{ use std::{
any::Any,
cell::{Ref, RefMut}, cell::{Ref, RefMut},
rc::Rc, rc::Rc,
}; };
@ -66,17 +67,6 @@ pub use self::{
host::{Host, HostGuard}, host::{Host, HostGuard},
}; };
/// Enum to encapsulate various introspection details of a Guard.
#[derive(Debug, Clone)]
pub enum GuardDetail {
/// Detail associated with HTTP methods.
HttpMethods(Vec<String>),
/// Detail associated with headers (header, value).
Headers(Vec<(String, String)>),
/// Generic detail.
Generic(String),
}
/// Provides access to request parts that are useful during routing. /// Provides access to request parts that are useful during routing.
#[derive(Debug)] #[derive(Debug)]
pub struct GuardContext<'a> { pub struct GuardContext<'a> {
@ -132,31 +122,15 @@ impl<'a> GuardContext<'a> {
/// Interface for routing guards. /// Interface for routing guards.
/// ///
/// See [module level documentation](self) for more. /// See [module level documentation](self) for more.
pub trait Guard { pub trait Guard: AsAny {
/// Returns true if predicate condition is met for a given request. /// Returns true if predicate condition is met for a given request.
fn check(&self, ctx: &GuardContext<'_>) -> bool; fn check(&self, ctx: &GuardContext<'_>) -> bool;
/// Returns a nominal representation of the guard.
fn name(&self) -> String {
std::any::type_name::<Self>().to_string()
}
/// Returns detailed introspection information.
fn details(&self) -> Option<Vec<GuardDetail>> {
None
}
} }
impl Guard for Rc<dyn Guard> { impl Guard for Rc<dyn Guard> {
fn check(&self, ctx: &GuardContext<'_>) -> bool { fn check(&self, ctx: &GuardContext<'_>) -> bool {
(**self).check(ctx) (**self).check(ctx)
} }
fn name(&self) -> String {
(**self).name()
}
fn details(&self) -> Option<Vec<GuardDetail>> {
(**self).details()
}
} }
/// Creates a guard using the given function. /// Creates a guard using the given function.
@ -173,7 +147,7 @@ impl Guard for Rc<dyn Guard> {
/// ``` /// ```
pub fn fn_guard<F>(f: F) -> impl Guard pub fn fn_guard<F>(f: F) -> impl Guard
where where
F: Fn(&GuardContext<'_>) -> bool, F: Fn(&GuardContext<'_>) -> bool + 'static,
{ {
FnGuard(f) FnGuard(f)
} }
@ -182,7 +156,7 @@ struct FnGuard<F: Fn(&GuardContext<'_>) -> bool>(F);
impl<F> Guard for FnGuard<F> impl<F> Guard for FnGuard<F>
where where
F: Fn(&GuardContext<'_>) -> bool, F: Fn(&GuardContext<'_>) -> bool + 'static,
{ {
fn check(&self, ctx: &GuardContext<'_>) -> bool { fn check(&self, ctx: &GuardContext<'_>) -> bool {
(self.0)(ctx) (self.0)(ctx)
@ -191,7 +165,7 @@ where
impl<F> Guard for F impl<F> Guard for F
where where
F: Fn(&GuardContext<'_>) -> bool, F: for<'a> Fn(&'a GuardContext<'a>) -> bool + 'static,
{ {
fn check(&self, ctx: &GuardContext<'_>) -> bool { fn check(&self, ctx: &GuardContext<'_>) -> bool {
(self)(ctx) (self)(ctx)
@ -246,24 +220,6 @@ impl Guard for AnyGuard {
false false
} }
fn name(&self) -> String {
format!(
"AnyGuard({})",
self.guards
.iter()
.map(|g| g.name())
.collect::<Vec<_>>()
.join(", ")
)
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(
self.guards
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.collect(),
)
}
} }
/// Creates a guard that matches if all added guards match. /// Creates a guard that matches if all added guards match.
@ -292,7 +248,7 @@ pub fn All<F: Guard + 'static>(guard: F) -> AllGuard {
/// ///
/// That is, **all** contained guard needs to match in order for the aggregate guard to match. /// That is, **all** contained guard needs to match in order for the aggregate guard to match.
/// ///
/// Construct an `AllGuard` using [`All()`]. /// Construct an `AllGuard` using [`All`].
pub struct AllGuard { pub struct AllGuard {
guards: Vec<Box<dyn Guard>>, guards: Vec<Box<dyn Guard>>,
} }
@ -316,24 +272,6 @@ impl Guard for AllGuard {
true true
} }
fn name(&self) -> String {
format!(
"AllGuard({})",
self.guards
.iter()
.map(|g| g.name())
.collect::<Vec<_>>()
.join(", ")
)
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(
self.guards
.iter()
.flat_map(|g| g.details().unwrap_or_default())
.collect(),
)
}
} }
/// Wraps a guard and inverts the outcome of its `Guard` implementation. /// Wraps a guard and inverts the outcome of its `Guard` implementation.
@ -347,19 +285,13 @@ impl Guard for AllGuard {
/// .guard(guard::Not(guard::Get())) /// .guard(guard::Not(guard::Get()))
/// .to(|| HttpResponse::Ok()); /// .to(|| HttpResponse::Ok());
/// ``` /// ```
pub struct Not<G>(pub G); pub struct Not<G: 'static>(pub G);
impl<G: Guard> Guard for Not<G> { impl<G: Guard> Guard for Not<G> {
#[inline] #[inline]
fn check(&self, ctx: &GuardContext<'_>) -> bool { fn check(&self, ctx: &GuardContext<'_>) -> bool {
!self.0.check(ctx) !self.0.check(ctx)
} }
fn name(&self) -> String {
format!("Not({})", self.0.name())
}
fn details(&self) -> Option<Vec<GuardDetail>> {
self.0.details()
}
} }
/// Creates a guard that matches a specified HTTP method. /// Creates a guard that matches a specified HTTP method.
@ -389,12 +321,6 @@ impl Guard for MethodGuard {
ctx.head().method == self.0 ctx.head().method == self.0
} }
fn name(&self) -> String {
self.0.to_string()
}
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(vec![GuardDetail::HttpMethods(vec![self.0.to_string()])])
}
} }
#[cfg(feature = "resources-introspection")] #[cfg(feature = "resources-introspection")]
@ -405,24 +331,70 @@ pub trait HttpMethodsExtractor {
#[cfg(feature = "resources-introspection")] #[cfg(feature = "resources-introspection")]
impl HttpMethodsExtractor for dyn Guard { impl HttpMethodsExtractor for dyn Guard {
fn extract_http_methods(&self) -> Vec<String> { fn extract_http_methods(&self) -> Vec<String> {
let methods: Vec<String> = self if let Some(method_guard) = self.as_any().downcast_ref::<MethodGuard>() {
.details() vec![method_guard.0.to_string()]
.unwrap_or_default() } else if let Some(any_guard) = self.as_any().downcast_ref::<AnyGuard>() {
any_guard
.guards
.iter() .iter()
.flat_map(|detail| { .flat_map(|g| g.extract_http_methods())
if let GuardDetail::HttpMethods(methods) = detail { .collect()
methods.clone() } else if let Some(all_guard) = self.as_any().downcast_ref::<AllGuard>() {
all_guard
.guards
.iter()
.flat_map(|g| g.extract_http_methods())
.collect()
} else { } else {
vec!["UNKNOWN".to_string()] vec!["UNKNOWN".to_string()]
} }
}
}
#[cfg(feature = "resources-introspection")]
impl std::fmt::Display for MethodGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]", self.0)
}
}
#[cfg(feature = "resources-introspection")]
impl std::fmt::Display for AllGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let methods: Vec<String> = self
.guards
.iter()
.filter_map(|guard| {
guard
.as_any()
.downcast_ref::<MethodGuard>()
.map(|method_guard| method_guard.0.to_string())
})
.collect();
write!(f, "[{}]", methods.join(", "))
}
}
#[cfg(feature = "resources-introspection")]
impl std::fmt::Display for AnyGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let methods: Vec<String> = self
.guards
.iter()
.map(|guard| {
let guard_ref = &**guard;
if let Some(method_guard) = guard_ref.as_any().downcast_ref::<MethodGuard>() {
method_guard.0.to_string()
} else if let Some(all_guard) = guard_ref.as_any().downcast_ref::<AllGuard>() {
all_guard.to_string()
} else {
"UNKNOWN".to_string()
}
}) })
.collect(); .collect();
if methods.is_empty() { write!(f, "[{}]", methods.join(", "))
vec!["UNKNOWN".to_string()]
} else {
methods
}
} }
} }
@ -486,14 +458,15 @@ impl Guard for HeaderGuard {
false false
} }
fn name(&self) -> String {
format!("Header({}, {})", self.0, self.1.to_str().unwrap_or(""))
} }
fn details(&self) -> Option<Vec<GuardDetail>> {
Some(vec![GuardDetail::Headers(vec![( pub trait AsAny {
self.0.to_string(), fn as_any(&self) -> &dyn Any;
self.1.to_str().unwrap_or("").to_string(), }
)])])
impl<T: Any> AsAny for T {
fn as_any(&self) -> &dyn Any {
self
} }
} }
@ -608,7 +581,7 @@ mod tests {
#[test] #[test]
fn function_guard() { fn function_guard() {
let domain = "rust-lang.org".to_owned(); let domain = "rust-lang.org".to_owned();
let guard = fn_guard(|ctx| ctx.head().uri.host().unwrap().ends_with(&domain)); let guard = fn_guard(move |ctx| ctx.head().uri.host().unwrap().ends_with(&domain));
let req = TestRequest::default() let req = TestRequest::default()
.uri("blog.rust-lang.org") .uri("blog.rust-lang.org")