Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0d87cc5
feat(resources-introspection): add support for resource metadata retr…
dertin Mar 3, 2025
041322e
misc: remove debug print
dertin Mar 3, 2025
819ee93
style: cargo fmt
dertin Mar 3, 2025
91fa813
fix(guards): replace take_guards with get_guards to prevent guard rem…
dertin Mar 3, 2025
176ea5d
ci: downgrade for msrv litemap to version 0.7.4 in justfile
dertin Mar 3, 2025
acd7c58
chore: update changelog and fix docs for CI
dertin Mar 3, 2025
c4be199
ci: downgrade for msrv zerofrom to version 0.1.5 in justfile
dertin Mar 3, 2025
a79dc9d
refactor: improve thread safety and add unit tests for introspection …
dertin Mar 4, 2025
2bb774a
fix(introspection): add conditional arbiter creation for io-uring sup…
dertin Mar 4, 2025
3b99f86
fix(introspection): add conditional arbiter creation for io-uring sup…
dertin Mar 4, 2025
7821ba9
Merge branch 'introspection' of https://github.com/dertin/actix-web i…
dertin Mar 4, 2025
0548b12
Merge branch 'introspection' of https://github.com/dertin/actix-web i…
dertin Mar 4, 2025
ee76215
Merge branch 'introspection' of https://github.com/dertin/actix-web i…
dertin Mar 4, 2025
ae08dcf
refactor(introspection): add GuardDetail enum and remove downcast_ref…
dertin Mar 5, 2025
aebab17
refactor(introspection): add GuardDetail enum and remove downcast_ref…
dertin Mar 5, 2025
a449695
Merge branch 'introspection' of https://github.com/dertin/actix-web i…
dertin Mar 5, 2025
3506512
Merge branch 'master' into introspection
dertin Mar 10, 2025
57b5937
Merge branch 'master' into introspection
dertin Mar 21, 2025
585552f
Merge branch 'master' into introspection
dertin Apr 2, 2025
c809ee8
Merge branch 'master' into introspection
dertin Apr 10, 2025
013a8ec
Merge branch 'master' into introspection
dertin Apr 22, 2025
d501102
feat(introspection): rename feature from `resources-introspection` to…
devdertin May 12, 2025
dcad1d9
Merge branch 'master' into introspection
devdertin May 12, 2025
2f64cdb
fix Cargo.lock
devdertin May 12, 2025
0a9f6c1
feat(introspection): enhance introspection feature with detailed rout…
devdertin May 19, 2025
c8a7271
optimize debug log and apply clippy/fmt suggestions
devdertin May 19, 2025
360baa3
Merge branch 'master' into introspection
dertin May 19, 2025
23fed22
feat(introspection): enhance introspection handlers for JSON and plai…
devdertin May 20, 2025
d1706dc
Merge branch 'introspection' of github.com:dertin/actix-web into intr…
devdertin May 20, 2025
7296f6f
Merge branch 'master' into introspection
dertin May 27, 2025
2b52a60
Merge branch 'master' into introspection
dertin Jun 10, 2025
7ff7768
feat(introspection): implement experimental introspection feature wit…
dertin Jun 11, 2025
dfb4aa3
Merge branch 'master' into introspection
dertin Jun 17, 2025
f3b64b0
Merge branch 'master' into introspection
dertin Jul 23, 2025
a3e428f
Merge branch 'main' into introspection
dertin Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions actix-web/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Add `resources-introspection` feature for retrieving configured route paths and HTTP methods.
- Implement `Responder` for `Result<(), E: Into<Error>>`. Returning `Ok(())` responds with HTTP 204 No Content.
- On Windows, an error is now returned from `HttpServer::bind()` (or TLS variants) when binding to a socket that's already in use.
- Update `brotli` dependency to `7`.
Expand Down
3 changes: 3 additions & 0 deletions actix-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ compat = [
# Opt-out forwards-compatibility for handler visibility inheritance fix.
compat-routing-macros-force-pub = ["actix-web-codegen?/compat-routing-macros-force-pub"]

# Enabling the retrieval of metadata for initialized resources, including path and HTTP method.
resources-introspection = []

[dependencies]
actix-codec = "0.5"
actix-macros = { version = "0.2.3", optional = true }
Expand Down
26 changes: 26 additions & 0 deletions actix-web/src/app_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,34 @@ where

let (config, services) = config.into_services();

#[cfg(feature = "resources-introspection")]
let mut rdef_methods: Vec<(String, Vec<String>)> = Vec::new();

// complete pipeline creation.
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
default,
services: services
.into_iter()
.map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested);

#[cfg(feature = "resources-introspection")]
{
let http_methods: Vec<String> =
guards.as_ref().map_or_else(Vec::new, |g| {
g.iter()
.flat_map(|g| {
crate::guard::HttpMethodsExtractor::extract_http_methods(
&**g,
)
})
.collect::<Vec<_>>()
});

rdef_methods
.push((rdef.pattern().unwrap_or_default().to_string(), http_methods));
}

(rdef, srv, RefCell::new(guards))
})
.collect::<Vec<_>>()
Expand All @@ -105,6 +126,11 @@ where
let rmap = Rc::new(rmap);
ResourceMap::finish(&rmap);

#[cfg(feature = "resources-introspection")]
{
crate::introspection::process_introspection(Rc::clone(&rmap), rdef_methods);
}

// construct all async data factory futures
let factory_futs = join_all(self.async_data_factories.iter().map(|f| f()));

Expand Down
102 changes: 94 additions & 8 deletions actix-web/src/guard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//! 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
//! 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
//! services multiple times (which might have different combining behavior than you want).
//!
Expand Down Expand Up @@ -50,6 +50,7 @@
//! [`Route`]: crate::Route::guard()

use std::{
any::Any,
cell::{Ref, RefMut},
rc::Rc,
};
Expand Down Expand Up @@ -121,7 +122,7 @@ impl<'a> GuardContext<'a> {
/// Interface for routing guards.
///
/// 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.
fn check(&self, ctx: &GuardContext<'_>) -> bool;
}
Expand All @@ -146,7 +147,7 @@ impl Guard for Rc<dyn Guard> {
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
F: Fn(&GuardContext<'_>) -> bool,
F: Fn(&GuardContext<'_>) -> bool + 'static,
{
FnGuard(f)
}
Expand All @@ -155,7 +156,7 @@ struct FnGuard<F: Fn(&GuardContext<'_>) -> bool>(F);

impl<F> Guard for FnGuard<F>
where
F: Fn(&GuardContext<'_>) -> bool,
F: Fn(&GuardContext<'_>) -> bool + 'static,
{
fn check(&self, ctx: &GuardContext<'_>) -> bool {
(self.0)(ctx)
Expand All @@ -164,7 +165,7 @@ where

impl<F> Guard for F
where
F: Fn(&GuardContext<'_>) -> bool,
F: for<'a> Fn(&'a GuardContext<'a>) -> bool + 'static,
{
fn check(&self, ctx: &GuardContext<'_>) -> bool {
(self)(ctx)
Expand Down Expand Up @@ -195,7 +196,7 @@ pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
///
/// That is, only one contained guard needs to match in order for the aggregate guard to match.
///
/// Construct an `AnyGuard` using [`Any`].
/// Construct an `AnyGuard` using [`Any()`].
pub struct AnyGuard {
guards: Vec<Box<dyn Guard>>,
}
Expand Down Expand Up @@ -284,7 +285,7 @@ impl Guard for AllGuard {
/// .guard(guard::Not(guard::Get()))
/// .to(|| HttpResponse::Ok());
/// ```
pub struct Not<G>(pub G);
pub struct Not<G: 'static>(pub G);

impl<G: Guard> Guard for Not<G> {
#[inline]
Expand Down Expand Up @@ -322,6 +323,81 @@ impl Guard for MethodGuard {
}
}

#[cfg(feature = "resources-introspection")]
pub trait HttpMethodsExtractor {
fn extract_http_methods(&self) -> Vec<String>;
}

#[cfg(feature = "resources-introspection")]
impl HttpMethodsExtractor for dyn Guard {
fn extract_http_methods(&self) -> Vec<String> {
if let Some(method_guard) = self.as_any().downcast_ref::<MethodGuard>() {
vec![method_guard.0.to_string()]
} else if let Some(any_guard) = self.as_any().downcast_ref::<AnyGuard>() {
any_guard
.guards
.iter()
.flat_map(|g| g.extract_http_methods())
.collect()
} 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 {
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();

write!(f, "[{}]", methods.join(", "))
}
}

macro_rules! method_guard {
($method_fn:ident, $method_const:ident) => {
#[doc = concat!("Creates a guard that matches the `", stringify!($method_const), "` request method.")]
Expand Down Expand Up @@ -384,6 +460,16 @@ impl Guard for HeaderGuard {
}
}

pub trait AsAny {
fn as_any(&self) -> &dyn Any;
}

impl<T: Any> AsAny for T {
fn as_any(&self) -> &dyn Any {
self
}
}

#[cfg(test)]
mod tests {
use actix_http::Method;
Expand Down Expand Up @@ -495,7 +581,7 @@ mod tests {
#[test]
fn function_guard() {
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()
.uri("blog.rust-lang.org")
Expand Down
Loading
Loading