mirror of https://github.com/fafhrd91/actix-web
feat(introspection): implement experimental introspection feature with multiple App instances
This commit is contained in:
parent
2b52a60bc2
commit
7ff7768dc4
|
@ -32,20 +32,20 @@ async fn main() -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /introspection for JSON response
|
// GET /introspection for JSON response
|
||||||
async fn introspection_handler_json() -> impl Responder {
|
async fn introspection_handler_json(
|
||||||
use actix_web::introspection::introspection_report_as_json;
|
tree: web::Data<actix_web::introspection::IntrospectionTree>,
|
||||||
|
) -> impl Responder {
|
||||||
let report = introspection_report_as_json();
|
let report = tree.report_as_json();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(report)
|
.body(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /introspection for plain text response
|
// GET /introspection for plain text response
|
||||||
async fn introspection_handler_text() -> impl Responder {
|
async fn introspection_handler_text(
|
||||||
use actix_web::introspection::introspection_report_as_text;
|
tree: web::Data<actix_web::introspection::IntrospectionTree>,
|
||||||
|
) -> impl Responder {
|
||||||
let report = introspection_report_as_text();
|
let report = tree.report_as_text();
|
||||||
HttpResponse::Ok().content_type("text/plain").body(report)
|
HttpResponse::Ok().content_type("text/plain").body(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
.to(HttpResponse::MethodNotAllowed),
|
.to(HttpResponse::MethodNotAllowed),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.workers(1)
|
.workers(5)
|
||||||
.bind("127.0.0.1:8080")?;
|
.bind("127.0.0.1:8080")?;
|
||||||
|
|
||||||
server.run().await
|
server.run().await
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Example showcasing the experimental introspection feature with multiple App instances.
|
||||||
|
// Run with: `cargo run --features experimental-introspection --example introspection_multi_servers`
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
{
|
||||||
|
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
|
||||||
|
use futures_util::future;
|
||||||
|
|
||||||
|
async fn introspection_handler(
|
||||||
|
tree: web::Data<actix_web::introspection::IntrospectionTree>,
|
||||||
|
) -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("text/plain")
|
||||||
|
.body(tree.report_as_text())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("Hello from app")
|
||||||
|
}
|
||||||
|
|
||||||
|
let srv1 = HttpServer::new(|| {
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/a").route(web::get().to(index)))
|
||||||
|
.service(
|
||||||
|
web::resource("/introspection").route(web::get().to(introspection_handler)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.workers(8)
|
||||||
|
.bind("127.0.0.1:8081")?
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let srv2 = HttpServer::new(|| {
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/b").route(web::get().to(index)))
|
||||||
|
.service(
|
||||||
|
web::resource("/introspection").route(web::get().to(introspection_handler)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.workers(3)
|
||||||
|
.bind("127.0.0.1:8082")?
|
||||||
|
.run();
|
||||||
|
|
||||||
|
future::try_join(srv1, srv2).await?;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "experimental-introspection"))]
|
||||||
|
{
|
||||||
|
eprintln!("This example requires the 'experimental-introspection' feature to be enabled.");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -30,6 +30,8 @@ pub struct App<T> {
|
||||||
data_factories: Vec<FnDataFactory>,
|
data_factories: Vec<FnDataFactory>,
|
||||||
external: Vec<ResourceDef>,
|
external: Vec<ResourceDef>,
|
||||||
extensions: Extensions,
|
extensions: Extensions,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: Rc<RefCell<crate::introspection::IntrospectionCollector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<AppEntry> {
|
impl App<AppEntry> {
|
||||||
|
@ -46,6 +48,10 @@ impl App<AppEntry> {
|
||||||
factory_ref,
|
factory_ref,
|
||||||
external: Vec::new(),
|
external: Vec::new(),
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: Rc::new(RefCell::new(
|
||||||
|
crate::introspection::IntrospectionCollector::new(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,6 +372,8 @@ where
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
external: self.external,
|
external: self.external,
|
||||||
extensions: self.extensions,
|
extensions: self.extensions,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: self.introspector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,6 +437,8 @@ where
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
external: self.external,
|
external: self.external,
|
||||||
extensions: self.extensions,
|
extensions: self.extensions,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: self.introspector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,6 +463,8 @@ where
|
||||||
default: self.default,
|
default: self.default,
|
||||||
factory_ref: self.factory_ref,
|
factory_ref: self.factory_ref,
|
||||||
extensions: RefCell::new(Some(self.extensions)),
|
extensions: RefCell::new(Some(self.extensions)),
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: Rc::clone(&self.introspector),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ where
|
||||||
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
|
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||||
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||||
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
pub(crate) external: RefCell<Vec<ResourceDef>>,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub(crate) introspector: Rc<RefCell<crate::introspection::IntrospectionCollector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> ServiceFactory<Request> for AppInit<T, B>
|
impl<T, B> ServiceFactory<Request> for AppInit<T, B>
|
||||||
|
@ -72,6 +74,10 @@ where
|
||||||
|
|
||||||
// create App config to pass to child services
|
// create App config to pass to child services
|
||||||
let mut config = AppService::new(config, Rc::clone(&default));
|
let mut config = AppService::new(config, Rc::clone(&default));
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
{
|
||||||
|
config.introspector = Rc::clone(&self.introspector);
|
||||||
|
}
|
||||||
|
|
||||||
// register services
|
// register services
|
||||||
mem::take(&mut *self.services.borrow_mut())
|
mem::take(&mut *self.services.borrow_mut())
|
||||||
|
@ -80,6 +86,9 @@ where
|
||||||
|
|
||||||
let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
|
let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
|
||||||
|
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
let (config, services, _) = config.into_services();
|
||||||
|
#[cfg(not(feature = "experimental-introspection"))]
|
||||||
let (config, services) = config.into_services();
|
let (config, services) = config.into_services();
|
||||||
|
|
||||||
// complete pipeline creation.
|
// complete pipeline creation.
|
||||||
|
@ -110,6 +119,8 @@ where
|
||||||
|
|
||||||
// construct app service and middleware service factory future.
|
// construct app service and middleware service factory future.
|
||||||
let endpoint_fut = self.endpoint.new_service(());
|
let endpoint_fut = self.endpoint.new_service(());
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
let introspector = Rc::clone(&self.introspector);
|
||||||
|
|
||||||
// take extensions or create new one as app data container.
|
// take extensions or create new one as app data container.
|
||||||
let mut app_data = self.extensions.borrow_mut().take().unwrap_or_default();
|
let mut app_data = self.extensions.borrow_mut().take().unwrap_or_default();
|
||||||
|
@ -132,7 +143,8 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "experimental-introspection")]
|
#[cfg(feature = "experimental-introspection")]
|
||||||
{
|
{
|
||||||
crate::introspection::finalize_registry();
|
let tree = introspector.borrow_mut().finalize();
|
||||||
|
app_data.insert(crate::web::Data::new(tree));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(AppInitService {
|
Ok(AppInitService {
|
||||||
|
|
|
@ -32,6 +32,9 @@ pub struct AppService {
|
||||||
)>,
|
)>,
|
||||||
#[cfg(feature = "experimental-introspection")]
|
#[cfg(feature = "experimental-introspection")]
|
||||||
pub current_prefix: String,
|
pub current_prefix: String,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub(crate) introspector:
|
||||||
|
std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
|
@ -44,6 +47,10 @@ impl AppService {
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
#[cfg(feature = "experimental-introspection")]
|
#[cfg(feature = "experimental-introspection")]
|
||||||
current_prefix: "".to_string(),
|
current_prefix: "".to_string(),
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: std::rc::Rc::new(std::cell::RefCell::new(
|
||||||
|
crate::introspection::IntrospectionCollector::new(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +60,24 @@ impl AppService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub(crate) fn into_services(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
AppConfig,
|
||||||
|
Vec<(
|
||||||
|
ResourceDef,
|
||||||
|
BoxedHttpServiceFactory,
|
||||||
|
Option<Guards>,
|
||||||
|
Option<Rc<ResourceMap>>,
|
||||||
|
)>,
|
||||||
|
std::rc::Rc<std::cell::RefCell<crate::introspection::IntrospectionCollector>>,
|
||||||
|
) {
|
||||||
|
(self.config, self.services, self.introspector)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
#[cfg(not(feature = "experimental-introspection"))]
|
||||||
pub(crate) fn into_services(
|
pub(crate) fn into_services(
|
||||||
self,
|
self,
|
||||||
) -> (
|
) -> (
|
||||||
|
@ -77,6 +102,8 @@ impl AppService {
|
||||||
root: false,
|
root: false,
|
||||||
#[cfg(feature = "experimental-introspection")]
|
#[cfg(feature = "experimental-introspection")]
|
||||||
current_prefix: self.current_prefix.clone(),
|
current_prefix: self.current_prefix.clone(),
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
introspector: std::rc::Rc::clone(&self.introspector),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,8 +138,6 @@ impl AppService {
|
||||||
{
|
{
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use crate::introspection;
|
|
||||||
|
|
||||||
// Build the full path for introspection
|
// Build the full path for introspection
|
||||||
let pat = rdef.pattern().unwrap_or("").to_string();
|
let pat = rdef.pattern().unwrap_or("").to_string();
|
||||||
|
|
||||||
|
@ -148,7 +173,12 @@ impl AppService {
|
||||||
|
|
||||||
// Determine if the registered service is a resource
|
// Determine if the registered service is a resource
|
||||||
let is_resource = rdef.pattern().is_some();
|
let is_resource = rdef.pattern().is_some();
|
||||||
introspection::register_pattern_detail(full_path, methods, guard_names, is_resource);
|
self.introspector.borrow_mut().register_pattern_detail(
|
||||||
|
full_path,
|
||||||
|
methods,
|
||||||
|
guard_names,
|
||||||
|
is_resource,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.services
|
self.services
|
||||||
|
|
|
@ -1,61 +1,14 @@
|
||||||
use std::{
|
use std::{collections::HashMap, fmt::Write as FmtWrite};
|
||||||
collections::HashMap,
|
|
||||||
fmt::Write as FmtWrite,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Mutex, OnceLock,
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::http::Method;
|
use crate::http::Method;
|
||||||
|
|
||||||
static REGISTRY: OnceLock<Mutex<IntrospectionNode>> = OnceLock::new();
|
|
||||||
static DETAIL_REGISTRY: OnceLock<Mutex<HashMap<String, RouteDetail>>> = OnceLock::new();
|
|
||||||
static DESIGNATED_THREAD: OnceLock<thread::ThreadId> = OnceLock::new();
|
|
||||||
static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
|
||||||
|
|
||||||
fn initialize_registry() {
|
|
||||||
REGISTRY.get_or_init(|| {
|
|
||||||
Mutex::new(IntrospectionNode::new(
|
|
||||||
ResourceType::App,
|
|
||||||
"".into(),
|
|
||||||
"".into(),
|
|
||||||
))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_registry() -> &'static Mutex<IntrospectionNode> {
|
|
||||||
REGISTRY.get().expect("Registry not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize_detail_registry() {
|
|
||||||
DETAIL_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_detail_registry() -> &'static Mutex<HashMap<String, RouteDetail>> {
|
|
||||||
DETAIL_REGISTRY
|
|
||||||
.get()
|
|
||||||
.expect("Detail registry not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_designated_thread() -> bool {
|
|
||||||
let current_id = thread::current().id();
|
|
||||||
DESIGNATED_THREAD.get_or_init(|| {
|
|
||||||
IS_INITIALIZED.store(true, Ordering::SeqCst);
|
|
||||||
current_id // Assign the first thread that calls this function
|
|
||||||
});
|
|
||||||
|
|
||||||
*DESIGNATED_THREAD.get().unwrap() == current_id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RouteDetail {
|
pub struct RouteDetail {
|
||||||
methods: Vec<Method>,
|
methods: Vec<Method>,
|
||||||
guards: Vec<String>,
|
guards: Vec<String>,
|
||||||
is_resource: bool, // Indicates if this detail is for a final resource endpoint
|
is_resource: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -74,6 +27,7 @@ pub struct IntrospectionNode {
|
||||||
pub guards: Vec<String>,
|
pub guards: Vec<String>,
|
||||||
pub children: Vec<IntrospectionNode>,
|
pub children: Vec<IntrospectionNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct IntrospectionReportItem {
|
pub struct IntrospectionReportItem {
|
||||||
pub full_path: String,
|
pub full_path: String,
|
||||||
|
@ -112,16 +66,10 @@ impl From<&IntrospectionNode> for Vec<IntrospectionReportItem> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !node.methods.is_empty() || !node.guards.is_empty() {
|
if !node.methods.is_empty() || !node.guards.is_empty() {
|
||||||
// Filter guards that are already represented in methods
|
|
||||||
let filtered_guards: Vec<String> = node
|
let filtered_guards: Vec<String> = node
|
||||||
.guards
|
.guards
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|guard| {
|
.filter(|guard| !node.methods.iter().any(|m| m.to_string() == **guard))
|
||||||
!node
|
|
||||||
.methods
|
|
||||||
.iter()
|
|
||||||
.any(|method| method.to_string() == **guard)
|
|
||||||
})
|
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -143,35 +91,59 @@ impl From<&IntrospectionNode> for Vec<IntrospectionReportItem> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn finalize_registry() {
|
#[derive(Clone, Default)]
|
||||||
if !is_designated_thread() {
|
pub struct IntrospectionCollector {
|
||||||
return;
|
details: HashMap<String, RouteDetail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize_registry();
|
impl IntrospectionCollector {
|
||||||
initialize_detail_registry();
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
details: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let detail_registry = get_detail_registry().lock().unwrap();
|
pub fn register_pattern_detail(
|
||||||
|
&mut self,
|
||||||
|
full_path: String,
|
||||||
|
methods: Vec<Method>,
|
||||||
|
guards: Vec<String>,
|
||||||
|
is_resource: bool,
|
||||||
|
) {
|
||||||
|
self.details
|
||||||
|
.entry(full_path)
|
||||||
|
.and_modify(|d| {
|
||||||
|
update_unique(&mut d.methods, &methods);
|
||||||
|
update_unique(&mut d.guards, &guards);
|
||||||
|
if !d.is_resource && is_resource {
|
||||||
|
d.is_resource = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_insert(RouteDetail {
|
||||||
|
methods,
|
||||||
|
guards,
|
||||||
|
is_resource,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&mut self) -> IntrospectionTree {
|
||||||
|
let detail_registry = std::mem::take(&mut self.details);
|
||||||
let mut root = IntrospectionNode::new(ResourceType::App, "".into(), "".into());
|
let mut root = IntrospectionNode::new(ResourceType::App, "".into(), "".into());
|
||||||
|
|
||||||
// Build the introspection tree directly from the detail registry
|
for (full_path, _) in detail_registry.iter() {
|
||||||
for (full_path, _detail) in detail_registry.iter() {
|
|
||||||
let parts: Vec<&str> = full_path.split('/').collect();
|
let parts: Vec<&str> = full_path.split('/').collect();
|
||||||
let mut current_node = &mut root;
|
let mut current_node = &mut root;
|
||||||
|
|
||||||
for (i, part) in parts.iter().enumerate() {
|
for (i, part) in parts.iter().enumerate() {
|
||||||
// Find the index of the existing child
|
|
||||||
let existing_child_index = current_node
|
let existing_child_index = current_node
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
.position(|n| n.pattern == *part);
|
.position(|n| n.pattern == *part);
|
||||||
|
|
||||||
let child_index = if let Some(child_index) = existing_child_index {
|
let child_index = if let Some(idx) = existing_child_index {
|
||||||
child_index
|
idx
|
||||||
} else {
|
} else {
|
||||||
// If it doesn't exist, create a new node and get its index
|
|
||||||
let child_full_path = parts[..=i].join("/");
|
let child_full_path = parts[..=i].join("/");
|
||||||
// Determine the kind based on whether this path exists as a resource in the detail registry
|
|
||||||
let kind = if detail_registry
|
let kind = if detail_registry
|
||||||
.get(&child_full_path)
|
.get(&child_full_path)
|
||||||
.is_some_and(|d| d.is_resource)
|
.is_some_and(|d| d.is_resource)
|
||||||
|
@ -185,10 +157,8 @@ pub(crate) fn finalize_registry() {
|
||||||
current_node.children.len() - 1
|
current_node.children.len() - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get a mutable reference to the child node
|
|
||||||
current_node = &mut current_node.children[child_index];
|
current_node = &mut current_node.children[child_index];
|
||||||
|
|
||||||
// If this node is marked as a resource, update its methods and guards
|
|
||||||
if let ResourceType::Resource = current_node.kind {
|
if let ResourceType::Resource = current_node.kind {
|
||||||
if let Some(detail) = detail_registry.get(¤t_node.full_path) {
|
if let Some(detail) = detail_registry.get(¤t_node.full_path) {
|
||||||
update_unique(&mut current_node.methods, &detail.methods);
|
update_unique(&mut current_node.methods, &detail.methods);
|
||||||
|
@ -198,48 +168,18 @@ pub(crate) fn finalize_registry() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*get_registry().lock().unwrap() = root;
|
IntrospectionTree { root }
|
||||||
}
|
|
||||||
|
|
||||||
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
|
||||||
for item in new_items {
|
|
||||||
if !existing.contains(item) {
|
|
||||||
existing.push(item.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn register_pattern_detail(
|
#[derive(Clone)]
|
||||||
full_path: String,
|
pub struct IntrospectionTree {
|
||||||
methods: Vec<Method>,
|
pub root: IntrospectionNode,
|
||||||
guards: Vec<String>,
|
|
||||||
is_resource: bool,
|
|
||||||
) {
|
|
||||||
if !is_designated_thread() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initialize_detail_registry();
|
|
||||||
let mut reg = get_detail_registry().lock().unwrap();
|
|
||||||
reg.entry(full_path)
|
|
||||||
.and_modify(|d| {
|
|
||||||
update_unique(&mut d.methods, &methods);
|
|
||||||
update_unique(&mut d.guards, &guards);
|
|
||||||
// If the existing entry was not a resource but the new one is, update the kind
|
|
||||||
if !d.is_resource && is_resource {
|
|
||||||
d.is_resource = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.or_insert(RouteDetail {
|
|
||||||
methods,
|
|
||||||
guards,
|
|
||||||
is_resource,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn introspection_report_as_text() -> String {
|
impl IntrospectionTree {
|
||||||
let registry = get_registry();
|
pub fn report_as_text(&self) -> String {
|
||||||
let node = registry.lock().unwrap();
|
let report_items: Vec<IntrospectionReportItem> = (&self.root).into();
|
||||||
let report_items: Vec<IntrospectionReportItem> = (&*node).into();
|
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
for item in report_items {
|
for item in report_items {
|
||||||
|
@ -254,10 +194,16 @@ pub fn introspection_report_as_text() -> String {
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn introspection_report_as_json() -> String {
|
pub fn report_as_json(&self) -> String {
|
||||||
let registry = get_registry();
|
let report_items: Vec<IntrospectionReportItem> = (&self.root).into();
|
||||||
let node = registry.lock().unwrap();
|
|
||||||
let report_items: Vec<IntrospectionReportItem> = (&*node).into();
|
|
||||||
|
|
||||||
serde_json::to_string_pretty(&report_items).unwrap()
|
serde_json::to_string_pretty(&report_items).unwrap()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
||||||
|
for item in new_items {
|
||||||
|
if !existing.contains(item) {
|
||||||
|
existing.push(item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -432,7 +432,7 @@ where
|
||||||
};
|
};
|
||||||
#[cfg(feature = "experimental-introspection")]
|
#[cfg(feature = "experimental-introspection")]
|
||||||
{
|
{
|
||||||
use crate::{http::Method, introspection};
|
use crate::http::Method;
|
||||||
|
|
||||||
let guards_routes = routes.iter().map(|r| r.guards()).collect::<Vec<_>>();
|
let guards_routes = routes.iter().map(|r| r.guards()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ where
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
introspection::register_pattern_detail(
|
config.introspector.borrow_mut().register_pattern_detail(
|
||||||
full_path.clone(),
|
full_path.clone(),
|
||||||
methods,
|
methods,
|
||||||
guard_names,
|
guard_names,
|
||||||
|
|
Loading…
Reference in New Issue