put all the items behind feature gate

This commit is contained in:
Yuki Okushi 2026-02-11 09:07:13 +09:00
parent 9a0190a1cc
commit 1facfec04b
5 changed files with 87 additions and 88 deletions

View File

@ -165,10 +165,7 @@ impl AppService {
}
})
.collect::<Vec<_>>();
let guard_names = guard_list
.iter()
.map(|g| g.name().to_string())
.collect::<Vec<_>>();
let guard_names = guard_list.iter().map(|g| g.name()).collect::<Vec<_>>();
let guard_details = crate::introspection::guard_reports_from_iter(guard_list.iter());
let is_resource = nested.is_none();

View File

@ -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::<Self>().to_string()
if self.match_star_star {
format!("Acceptable({}, match_star_star=true)", self.mime)
} else {
format!("Acceptable({})", self.mime)
}
}
fn details(&self) -> Option<Vec<GuardDetail>> {
#[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<Vec<super::GuardDetail>> {
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!(

View File

@ -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::<Self>().to_string()
if let Some(ref scheme) = self.scheme {
format!("Host({}, scheme={})", self.host, scheme)
} else {
format!("Host({})", self.host)
}
}
fn details(&self) -> Option<Vec<GuardDetail>> {
#[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<Vec<super::GuardDetail>> {
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)");

View File

@ -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::<Self>().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<Vec<GuardDetail>> {
None
}
@ -153,9 +157,13 @@ impl Guard for Rc<dyn Guard> {
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<Vec<GuardDetail>> {
(**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<Vec<GuardDetail>> {
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<Vec<GuardDetail>> {
Some(
self.guards
@ -356,18 +372,15 @@ impl<G: Guard> Guard for Not<G> {
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<Vec<GuardDetail>> {
#[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<Vec<GuardDetail>> {
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<Vec<GuardDetail>> {
Some(vec![GuardDetail::Headers(vec![(
self.0.to_string(),

View File

@ -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<Method>,
guards: Vec<String>,
guard_details: Vec<GuardReport>,
@ -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<Method>,
guards: Vec<String>,
@ -53,7 +55,7 @@ pub struct RouteInfo {
}
impl RouteInfo {
pub fn new(
pub(crate) fn new(
full_path: String,
methods: Vec<Method>,
guards: Vec<String>,
@ -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<String>,
/// 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<String>,
/// Structured guard details, when available.
///
@ -201,7 +206,10 @@ pub struct IntrospectionReportItem {
pub patterns: Vec<String>,
/// 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<IntrospectionReportItem> {
/// Collects route details during app configuration.
#[derive(Clone, Default)]
pub struct IntrospectionCollector {
pub(crate) struct IntrospectionCollector {
details: BTreeMap<String, RouteDetail>,
registrations: Vec<Registration>,
externals: Vec<ExternalResourceReportItem>,
@ -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<usize>) {
pub(crate) fn register_route(&mut self, info: RouteInfo, scope_id: Option<usize>) {
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);