From 1facfec04bda43cd5b8428b52412bf8cc0c39ccf Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 11 Feb 2026 09:07:13 +0900 Subject: [PATCH] put all the items behind feature gate --- actix-web/src/config.rs | 5 +--- actix-web/src/guard/acceptable.rs | 44 +++++++++++----------------- actix-web/src/guard/host.rs | 48 ++++++++++++------------------- actix-web/src/guard/mod.rs | 37 ++++++++++++++++++------ actix-web/src/introspection.rs | 41 ++++++++++++++------------ 5 files changed, 87 insertions(+), 88 deletions(-) diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index 7ba47b9dc..a7611da4f 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -165,10 +165,7 @@ impl AppService { } }) .collect::>(); - let guard_names = guard_list - .iter() - .map(|g| g.name().to_string()) - .collect::>(); + let guard_names = guard_list.iter().map(|g| g.name()).collect::>(); let guard_details = crate::introspection::guard_reports_from_iter(guard_list.iter()); let is_resource = nested.is_none(); diff --git a/actix-web/src/guard/acceptable.rs b/actix-web/src/guard/acceptable.rs index de17fe6fa..dd7b6f105 100644 --- a/actix-web/src/guard/acceptable.rs +++ b/actix-web/src/guard/acceptable.rs @@ -1,4 +1,4 @@ -use super::{Guard, GuardContext, GuardDetail}; +use super::{Guard, GuardContext}; use crate::http::header::Accept; /// A guard that verifies that an `Accept` header is present and it contains a compatible MIME type. @@ -64,35 +64,25 @@ impl Guard for Acceptable { false } + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { - #[cfg(feature = "experimental-introspection")] - { - if self.match_star_star { - format!("Acceptable({}, match_star_star=true)", self.mime) - } else { - format!("Acceptable({})", self.mime) - } - } - #[cfg(not(feature = "experimental-introspection"))] - { - std::any::type_name::().to_string() + if self.match_star_star { + format!("Acceptable({}, match_star_star=true)", self.mime) + } else { + format!("Acceptable({})", self.mime) } } - fn details(&self) -> Option> { - #[cfg(feature = "experimental-introspection")] - { - let mut details = Vec::new(); - details.push(GuardDetail::Generic(format!("mime={}", self.mime))); - if self.match_star_star { - details.push(GuardDetail::Generic("match_star_star=true".to_string())); - } - Some(details) - } - #[cfg(not(feature = "experimental-introspection"))] - { - None + #[cfg(feature = "experimental-introspection")] + fn details(&self) -> Option> { + let mut details = Vec::new(); + details.push(super::GuardDetail::Generic(format!("mime={}", self.mime))); + if self.match_star_star { + details.push(super::GuardDetail::Generic( + "match_star_star=true".to_string(), + )); } + Some(details) } } @@ -135,12 +125,12 @@ mod tests { let details = guard.details().expect("missing guard details"); assert!(details.iter().any(|detail| match detail { - GuardDetail::Generic(value) => value == "match_star_star=true", + crate::guard::GuardDetail::Generic(value) => value == "match_star_star=true", _ => false, })); let expected = format!("mime={}", mime::APPLICATION_JSON); assert!(details.iter().any(|detail| match detail { - GuardDetail::Generic(value) => value == &expected, + crate::guard::GuardDetail::Generic(value) => value == &expected, _ => false, })); assert_eq!( diff --git a/actix-web/src/guard/host.rs b/actix-web/src/guard/host.rs index 54cec7522..6867c51f1 100644 --- a/actix-web/src/guard/host.rs +++ b/actix-web/src/guard/host.rs @@ -1,6 +1,6 @@ use actix_http::{header, uri::Uri, RequestHead, Version}; -use super::{Guard, GuardContext, GuardDetail}; +use super::{Guard, GuardContext}; /// Creates a guard that matches requests targeting a specific host. /// @@ -118,39 +118,27 @@ impl Guard for HostGuard { true } + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { - #[cfg(feature = "experimental-introspection")] - { - if let Some(ref scheme) = self.scheme { - format!("Host({}, scheme={})", self.host, scheme) - } else { - format!("Host({})", self.host) - } - } - #[cfg(not(feature = "experimental-introspection"))] - { - std::any::type_name::().to_string() + if let Some(ref scheme) = self.scheme { + format!("Host({}, scheme={})", self.host, scheme) + } else { + format!("Host({})", self.host) } } - fn details(&self) -> Option> { - #[cfg(feature = "experimental-introspection")] - { - let mut details = vec![GuardDetail::Headers(vec![( - "host".to_string(), - self.host.clone(), - )])]; + #[cfg(feature = "experimental-introspection")] + fn details(&self) -> Option> { + let mut details = vec![super::GuardDetail::Headers(vec![( + "host".to_string(), + self.host.clone(), + )])]; - if let Some(ref scheme) = self.scheme { - details.push(GuardDetail::Generic(format!("scheme={scheme}"))); - } + if let Some(ref scheme) = self.scheme { + details.push(super::GuardDetail::Generic(format!("scheme={scheme}"))); + } - Some(details) - } - #[cfg(not(feature = "experimental-introspection"))] - { - None - } + Some(details) } } @@ -282,13 +270,13 @@ mod tests { let details = host.details().expect("missing guard details"); assert!(details.iter().any(|detail| match detail { - GuardDetail::Headers(headers) => headers + crate::guard::GuardDetail::Headers(headers) => headers .iter() .any(|(name, value)| name == "host" && value == "example.com"), _ => false, })); assert!(details.iter().any(|detail| match detail { - GuardDetail::Generic(value) => value == "scheme=https", + crate::guard::GuardDetail::Generic(value) => value == "scheme=https", _ => false, })); assert_eq!(host.name(), "Host(example.com, scheme=https)"); diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 038ba6590..54d6fcf2d 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -67,6 +67,8 @@ pub use self::{ }; /// Enum to encapsulate various introspection details of a guard. +#[cfg(feature = "experimental-introspection")] +#[non_exhaustive] #[derive(Debug, Clone)] pub enum GuardDetail { /// Detail associated with explicit HTTP method guards. @@ -137,6 +139,7 @@ pub trait Guard { fn check(&self, ctx: &GuardContext<'_>) -> bool; /// Returns a nominal representation of the guard. + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { std::any::type_name::().to_string() } @@ -144,6 +147,7 @@ pub trait Guard { /// Returns detailed introspection information, when available. /// /// This is best-effort and may omit complex guard logic. + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { None } @@ -153,9 +157,13 @@ impl Guard for Rc { fn check(&self, ctx: &GuardContext<'_>) -> bool { (**self).check(ctx) } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { (**self).name() } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { (**self).details() } @@ -248,6 +256,8 @@ impl Guard for AnyGuard { false } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { format!( "AnyGuard({})", @@ -258,6 +268,8 @@ impl Guard for AnyGuard { .join(", ") ) } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { Some( self.guards @@ -318,6 +330,8 @@ impl Guard for AllGuard { true } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { format!( "AllGuard({})", @@ -328,6 +342,8 @@ impl Guard for AllGuard { .join(", ") ) } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { Some( self.guards @@ -356,18 +372,15 @@ impl Guard for Not { fn check(&self, ctx: &GuardContext<'_>) -> bool { !self.0.check(ctx) } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { format!("Not({})", self.0.name()) } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { - #[cfg(feature = "experimental-introspection")] - { - Some(vec![GuardDetail::Generic(self.name())]) - } - #[cfg(not(feature = "experimental-introspection"))] - { - None - } + Some(vec![GuardDetail::Generic(self.name())]) } } @@ -398,9 +411,13 @@ impl Guard for MethodGuard { ctx.head().method == self.0 } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { self.0.to_string() } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { Some(vec![GuardDetail::HttpMethods(vec![self.0.to_string()])]) } @@ -466,9 +483,13 @@ impl Guard for HeaderGuard { false } + + #[cfg(feature = "experimental-introspection")] fn name(&self) -> String { format!("Header({}, {})", self.0, self.1.to_str().unwrap_or("")) } + + #[cfg(feature = "experimental-introspection")] fn details(&self) -> Option> { Some(vec![GuardDetail::Headers(vec![( self.0.to_string(), diff --git a/actix-web/src/introspection.rs b/actix-web/src/introspection.rs index 89690edf1..ef8e46edf 100644 --- a/actix-web/src/introspection.rs +++ b/actix-web/src/introspection.rs @@ -12,7 +12,9 @@ //! //! Notes: //! - Method lists are best-effort and derived only from explicit method guards; an empty list means -//! the route matches any method. +//! no explicit method guards were observed for the node. +//! - Guard and method lists are aggregated per `full_path` and do not preserve per-route +//! correlations when multiple routes/services share the same path. //! - Reachability hints are best-effort and should be treated as diagnostics, not a hard guarantee. //! //! This feature is intended for local/non-production use. Avoid exposing introspection endpoints @@ -32,7 +34,7 @@ use crate::{ }; #[derive(Clone)] -pub struct RouteDetail { +struct RouteDetail { methods: Vec, guards: Vec, guard_details: Vec, @@ -43,7 +45,7 @@ pub struct RouteDetail { /// Input data for registering routes with the introspector. #[derive(Clone)] -pub struct RouteInfo { +pub(crate) struct RouteInfo { full_path: String, methods: Vec, guards: Vec, @@ -53,7 +55,7 @@ pub struct RouteInfo { } impl RouteInfo { - pub fn new( + pub(crate) fn new( full_path: String, methods: Vec, guards: Vec, @@ -182,9 +184,12 @@ pub struct IntrospectionReportItem { pub full_path: String, /// Methods derived from explicit method guards. /// - /// An empty list indicates the route matches any method. + /// An empty list indicates no explicit method guards were observed for the node. pub methods: Vec, /// Guard names attached to the route. + /// + /// This is aggregated per `full_path` and does not necessarily represent a single matching + /// condition when multiple routes/services share the same path. pub guards: Vec, /// Structured guard details, when available. /// @@ -201,7 +206,10 @@ pub struct IntrospectionReportItem { pub patterns: Vec, /// The type of node represented by the report item. pub resource_type: String, - /// Depth within the scope tree (root = 0). + /// Depth within this report tree (root = 0). + /// + /// This currently corresponds to the number of path segments (for example, `/foo` has depth 1 + /// and `/foo/bar` has depth 2). pub scope_depth: usize, /// True if the route might be unreachable at runtime. #[serde(skip_serializing_if = "is_false")] @@ -276,7 +284,7 @@ impl From<&IntrospectionNode> for Vec { /// Collects route details during app configuration. #[derive(Clone, Default)] -pub struct IntrospectionCollector { +pub(crate) struct IntrospectionCollector { details: BTreeMap, registrations: Vec, externals: Vec, @@ -286,7 +294,7 @@ pub struct IntrospectionCollector { impl IntrospectionCollector { /// Creates a new, empty collector. - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { details: BTreeMap::new(), registrations: Vec::new(), @@ -296,13 +304,13 @@ impl IntrospectionCollector { } } - pub fn next_scope_id(&mut self) -> usize { + pub(crate) fn next_scope_id(&mut self) -> usize { let scope_id = self.next_scope_id; self.next_scope_id += 1; scope_id } - pub fn register_service( + pub(crate) fn register_service( &mut self, info: RouteInfo, is_resource: bool, @@ -327,7 +335,7 @@ impl IntrospectionCollector { self.next_registration_order += 1; } - pub fn register_route(&mut self, info: RouteInfo, scope_id: Option) { + pub(crate) fn register_route(&mut self, info: RouteInfo, scope_id: Option) { let full_path = normalize_path(&info.full_path); self.register_pattern_detail(&full_path, &info, true); @@ -345,7 +353,7 @@ impl IntrospectionCollector { self.next_registration_order += 1; } - pub fn register_external(&mut self, rdef: &ResourceDef, origin_scope: &str) { + pub(crate) fn register_external(&mut self, rdef: &ResourceDef, origin_scope: &str) { let report = external_report_from_rdef(rdef, origin_scope); if let Some(name) = report.name.as_deref() { @@ -365,12 +373,7 @@ impl IntrospectionCollector { } /// Registers details for a route pattern. - pub fn register_pattern_detail( - &mut self, - full_path: &str, - info: &RouteInfo, - is_resource: bool, - ) { + fn register_pattern_detail(&mut self, full_path: &str, info: &RouteInfo, is_resource: bool) { let full_path = normalize_path(full_path); self.details @@ -398,7 +401,7 @@ impl IntrospectionCollector { } /// Produces the finalized introspection tree. - pub fn finalize(&mut self) -> IntrospectionTree { + pub(crate) fn finalize(&mut self) -> IntrospectionTree { let detail_registry = std::mem::take(&mut self.details); let registrations = std::mem::take(&mut self.registrations); let externals = std::mem::take(&mut self.externals);