mirror of https://github.com/fafhrd91/actix-web
feat(introspection): enhance introspection feature with detailed route registration and full path tracking
This commit is contained in:
parent
2f64cdb60a
commit
0a9f6c1955
|
@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b"
|
checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -31,7 +31,7 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
@ -53,7 +53,7 @@ dependencies = [
|
||||||
"actix-test",
|
"actix-test",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -83,7 +83,7 @@ dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytestring",
|
"bytestring",
|
||||||
|
@ -723,7 +723,7 @@ version = "0.69.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
@ -748,9 +748,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.0"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
|
@ -817,9 +817,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.22"
|
version = "1.2.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
|
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1299,9 +1299,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.11"
|
version = "0.3.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
@ -2122,7 +2122,7 @@ version = "0.10.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
|
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2448,7 +2448,7 @@ version = "0.5.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2488,9 +2488,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "resolv-conf"
|
name = "resolv-conf"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302"
|
checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
|
@ -2539,7 +2539,7 @@ version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
|
@ -2552,7 +2552,7 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.9.4",
|
"linux-raw-sys 0.9.4",
|
||||||
|
@ -2734,7 +2734,7 @@ version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -3846,7 +3846,7 @@ version = "0.39.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,10 +1,72 @@
|
||||||
// NOTE: This is a work-in-progress example being used to test the new implementation
|
// Example showcasing the experimental introspection feature.
|
||||||
// of the experimental introspection feature.
|
// Run with: `cargo run --features experimental-introspection --example introspection`
|
||||||
// `cargo run --features experimental-introspection --example introspection`
|
|
||||||
|
|
||||||
use actix_web::{dev::Service, guard, web, App, HttpResponse, HttpServer, Responder};
|
use actix_web::{dev::Service, guard, web, App, HttpResponse, HttpServer, Responder};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
// Custom guard that checks if the Content-Type header is present.
|
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
#[actix_web::get("/introspection")]
|
||||||
|
async fn introspection_handler() -> impl Responder {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use actix_web::introspection::{get_registry, initialize_registry};
|
||||||
|
|
||||||
|
initialize_registry();
|
||||||
|
let registry = get_registry();
|
||||||
|
let node = registry.lock().unwrap();
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
if node.children.is_empty() {
|
||||||
|
writeln!(buf, "No routes registered or introspection tree is empty.").unwrap();
|
||||||
|
} else {
|
||||||
|
fn write_display(
|
||||||
|
node: &actix_web::introspection::IntrospectionNode,
|
||||||
|
parent_path: &str,
|
||||||
|
buf: &mut String,
|
||||||
|
) {
|
||||||
|
let full_path = if parent_path.is_empty() {
|
||||||
|
node.pattern.clone()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
parent_path.trim_end_matches('/'),
|
||||||
|
node.pattern.trim_start_matches('/')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if !node.methods.is_empty() || !node.guards.is_empty() {
|
||||||
|
let methods = if node.methods.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!("Methods: {:?}", node.methods)
|
||||||
|
};
|
||||||
|
|
||||||
|
let method_strings: Vec<String> =
|
||||||
|
node.methods.iter().map(|m| m.to_string()).collect();
|
||||||
|
|
||||||
|
let filtered_guards: Vec<_> = node
|
||||||
|
.guards
|
||||||
|
.iter()
|
||||||
|
.filter(|guard| !method_strings.contains(&guard.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let guards = if filtered_guards.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!("Guards: {:?}", filtered_guards)
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = writeln!(buf, "{} {} {}", full_path, methods, guards);
|
||||||
|
}
|
||||||
|
for child in &node.children {
|
||||||
|
write_display(child, &full_path, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_display(&node, "/", &mut buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().content_type("text/plain").body(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom guard to check if the Content-Type header is present.
|
||||||
struct ContentTypeGuard;
|
struct ContentTypeGuard;
|
||||||
|
|
||||||
impl guard::Guard for ContentTypeGuard {
|
impl guard::Guard for ContentTypeGuard {
|
||||||
|
@ -24,54 +86,62 @@ struct UserInfo {
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
// Initialize logging
|
||||||
|
env_logger::Builder::new()
|
||||||
|
.filter_level(log::LevelFilter::Debug)
|
||||||
|
.init();
|
||||||
|
|
||||||
let server = HttpServer::new(|| {
|
let server = HttpServer::new(|| {
|
||||||
let app = App::new()
|
let mut app = App::new()
|
||||||
|
// API endpoints under /api
|
||||||
.service(
|
.service(
|
||||||
web::scope("/api")
|
web::scope("/api")
|
||||||
|
// Endpoints under /api/v1
|
||||||
.service(
|
.service(
|
||||||
web::scope("/v1")
|
web::scope("/v1")
|
||||||
// GET /api/v1/item/{id}: returns the item id from the path.
|
.service(get_item) // GET /api/v1/item/{id}
|
||||||
.service(get_item)
|
.service(post_user_info) // POST /api/v1/info
|
||||||
// POST /api/v1/info: accepts JSON and returns user info.
|
|
||||||
.service(post_user_info)
|
|
||||||
// /api/v1/guarded: only accessible if Content-Type header is present.
|
|
||||||
.route(
|
.route(
|
||||||
"/guarded",
|
"/guarded",
|
||||||
web::route().guard(ContentTypeGuard).to(guarded_handler),
|
web::route().guard(ContentTypeGuard).to(guarded_handler), // /api/v1/guarded
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// API scope /api/v2: additional endpoint.
|
// Endpoints under /api/v2
|
||||||
.service(web::scope("/v2").route("/hello", web::get().to(hello_v2))),
|
.service(web::scope("/v2").route("/hello", web::get().to(hello_v2))), // GET /api/v2/hello
|
||||||
)
|
)
|
||||||
// Scope /v1 outside /api: exposes only GET /v1/item/{id}.
|
// Endpoints under /v1 (outside /api)
|
||||||
.service(web::scope("/v1").service(get_item))
|
.service(web::scope("/v1").service(get_item)) // GET /v1/item/{id}
|
||||||
// Scope /admin: admin endpoints with different HTTP methods.
|
// Admin endpoints under /admin
|
||||||
.service(
|
.service(
|
||||||
web::scope("/admin")
|
web::scope("/admin")
|
||||||
.route("/dashboard", web::get().to(admin_dashboard))
|
.route("/dashboard", web::get().to(admin_dashboard)) // GET /admin/dashboard
|
||||||
// Single route handling multiple methods using separate handlers.
|
|
||||||
.service(
|
.service(
|
||||||
web::resource("/settings")
|
web::resource("/settings")
|
||||||
.route(web::get().to(get_settings))
|
.route(web::get().to(get_settings)) // GET /admin/settings
|
||||||
.route(web::post().to(update_settings)),
|
.route(web::post().to(update_settings)), // POST /admin/settings
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// Root resource: supports GET and POST on "/".
|
// Root endpoints
|
||||||
.service(
|
.service(
|
||||||
web::resource("/")
|
web::resource("/")
|
||||||
.route(web::get().to(root_index))
|
.route(web::get().to(root_index)) // GET /
|
||||||
.route(web::post().to(root_index)),
|
.route(web::post().to(root_index)), // POST /
|
||||||
)
|
)
|
||||||
// Additional endpoints configured in a separate function.
|
// Endpoints under /bar
|
||||||
.configure(extra_endpoints)
|
.service(web::scope("/bar").configure(extra_endpoints)) // /bar/extra/ping, /bar/extra/multi, etc.
|
||||||
// Endpoint that rejects GET on /not_guard (allows other methods).
|
// Endpoints under /foo
|
||||||
|
.service(web::scope("/foo").configure(other_endpoints)) // /foo/extra/ping with POST and DELETE
|
||||||
|
// Additional endpoints under /extra
|
||||||
|
.configure(extra_endpoints) // /extra/ping, /extra/multi, etc.
|
||||||
|
.configure(other_endpoints)
|
||||||
|
// Endpoint that rejects GET on /not_guard (allows other methods)
|
||||||
.route(
|
.route(
|
||||||
"/not_guard",
|
"/not_guard",
|
||||||
web::route()
|
web::route()
|
||||||
.guard(guard::Not(guard::Get()))
|
.guard(guard::Not(guard::Get()))
|
||||||
.to(HttpResponse::MethodNotAllowed),
|
.to(HttpResponse::MethodNotAllowed),
|
||||||
)
|
)
|
||||||
// Endpoint that requires GET, content-type: plain/text header, and/or POST on /all_guard.
|
// Endpoint that requires GET with header or POST on /all_guard
|
||||||
.route(
|
.route(
|
||||||
"/all_guard",
|
"/all_guard",
|
||||||
web::route()
|
web::route()
|
||||||
|
@ -83,29 +153,27 @@ async fn main() -> std::io::Result<()> {
|
||||||
.to(HttpResponse::MethodNotAllowed),
|
.to(HttpResponse::MethodNotAllowed),
|
||||||
);
|
);
|
||||||
|
|
||||||
/*#[cfg(feature = "experimental-introspection")]
|
// Register the introspection handler if the feature is enabled.
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
{
|
{
|
||||||
actix_web::introspection::introspect();
|
app = app.service(introspection_handler); // GET /introspection
|
||||||
}*/
|
}
|
||||||
// TODO: Enable introspection without the feature flag.
|
|
||||||
app
|
app
|
||||||
})
|
})
|
||||||
.workers(5)
|
.workers(1)
|
||||||
.bind("127.0.0.1:8080")?;
|
.bind("127.0.0.1:8080")?;
|
||||||
|
|
||||||
server.run().await
|
server.run().await
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/v1/item/{id} and GET /v1/item/{id}
|
// GET /api/v1/item/{id} and GET /v1/item/{id}
|
||||||
// Returns a message with the provided id.
|
#[actix_web::get("/item/{id}")]
|
||||||
#[actix_web::get("/item/{id:\\d+}")]
|
|
||||||
async fn get_item(path: web::Path<u32>) -> impl Responder {
|
async fn get_item(path: web::Path<u32>) -> impl Responder {
|
||||||
let id = path.into_inner();
|
let id = path.into_inner();
|
||||||
HttpResponse::Ok().body(format!("Requested item with id: {}", id))
|
HttpResponse::Ok().body(format!("Requested item with id: {}", id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/v1/info
|
// POST /api/v1/info
|
||||||
// Expects JSON and responds with the received user info.
|
|
||||||
#[actix_web::post("/info")]
|
#[actix_web::post("/info")]
|
||||||
async fn post_user_info(info: web::Json<UserInfo>) -> impl Responder {
|
async fn post_user_info(info: web::Json<UserInfo>) -> impl Responder {
|
||||||
HttpResponse::Ok().json(format!(
|
HttpResponse::Ok().json(format!(
|
||||||
|
@ -115,63 +183,54 @@ async fn post_user_info(info: web::Json<UserInfo>) -> impl Responder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /api/v1/guarded
|
// /api/v1/guarded
|
||||||
// Uses a custom guard that requires the Content-Type header.
|
|
||||||
async fn guarded_handler() -> impl Responder {
|
async fn guarded_handler() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Passed the Content-Type guard!")
|
HttpResponse::Ok().body("Passed the Content-Type guard!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/v2/hello
|
// GET /api/v2/hello
|
||||||
// Simple greeting endpoint.
|
|
||||||
async fn hello_v2() -> impl Responder {
|
async fn hello_v2() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Hello from API v2!")
|
HttpResponse::Ok().body("Hello from API v2!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/dashboard
|
// GET /admin/dashboard
|
||||||
// Returns a message for the admin dashboard.
|
|
||||||
async fn admin_dashboard() -> impl Responder {
|
async fn admin_dashboard() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Welcome to the Admin Dashboard!")
|
HttpResponse::Ok().body("Welcome to the Admin Dashboard!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/settings
|
// GET /admin/settings
|
||||||
// Returns the current admin settings.
|
|
||||||
async fn get_settings() -> impl Responder {
|
async fn get_settings() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Current settings: ...")
|
HttpResponse::Ok().body("Current settings: ...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/settings
|
// POST /admin/settings
|
||||||
// Updates the admin settings.
|
|
||||||
async fn update_settings() -> impl Responder {
|
async fn update_settings() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Settings have been updated!")
|
HttpResponse::Ok().body("Settings have been updated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET and POST on /
|
// GET and POST on /
|
||||||
// Generic root endpoint.
|
|
||||||
async fn root_index() -> impl Responder {
|
async fn root_index() -> impl Responder {
|
||||||
HttpResponse::Ok().body("Welcome to the Root Endpoint!")
|
HttpResponse::Ok().body("Welcome to the Root Endpoint!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional endpoints configured in a separate function.
|
// Additional endpoints for /extra
|
||||||
fn extra_endpoints(cfg: &mut web::ServiceConfig) {
|
fn extra_endpoints(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("/extra")
|
web::scope("/extra")
|
||||||
// GET /extra/ping: simple ping endpoint.
|
|
||||||
.route(
|
.route(
|
||||||
"/ping",
|
"/ping",
|
||||||
web::get().to(|| async { HttpResponse::Ok().body("pong") }),
|
web::get().to(|| async { HttpResponse::Ok().body("pong") }), // GET /extra/ping
|
||||||
)
|
)
|
||||||
// /extra/multi: resource that supports GET and POST.
|
|
||||||
.service(
|
.service(
|
||||||
web::resource("/multi")
|
web::resource("/multi")
|
||||||
.route(
|
.route(
|
||||||
web::get().to(|| async {
|
web::get().to(|| async {
|
||||||
HttpResponse::Ok().body("GET response from /extra/multi")
|
HttpResponse::Ok().body("GET response from /extra/multi")
|
||||||
}),
|
}),
|
||||||
)
|
) // GET /extra/multi
|
||||||
.route(web::post().to(|| async {
|
.route(web::post().to(|| async {
|
||||||
HttpResponse::Ok().body("POST response from /extra/multi")
|
HttpResponse::Ok().body("POST response from /extra/multi")
|
||||||
})),
|
})), // POST /extra/multi
|
||||||
)
|
)
|
||||||
// /extra/{entities_id}/secure: nested scope with GET and POST, prints the received id.
|
|
||||||
.service(
|
.service(
|
||||||
web::scope("{entities_id:\\d+}")
|
web::scope("{entities_id:\\d+}")
|
||||||
.service(
|
.service(
|
||||||
|
@ -181,15 +240,14 @@ fn extra_endpoints(cfg: &mut web::ServiceConfig) {
|
||||||
web::get().to(|| async {
|
web::get().to(|| async {
|
||||||
HttpResponse::Ok().body("GET response from /extra/secure")
|
HttpResponse::Ok().body("GET response from /extra/secure")
|
||||||
}),
|
}),
|
||||||
)
|
) // GET /extra/{entities_id}/secure/
|
||||||
.route(
|
.route(
|
||||||
"",
|
"/post",
|
||||||
web::post().to(|| async {
|
web::post().to(|| async {
|
||||||
HttpResponse::Ok().body("POST response from /extra/secure")
|
HttpResponse::Ok().body("POST response from /extra/secure")
|
||||||
}),
|
}),
|
||||||
),
|
), // POST /extra/{entities_id}/secure/post
|
||||||
)
|
)
|
||||||
// Middleware that prints the id received in the route.
|
|
||||||
.wrap_fn(|req, srv| {
|
.wrap_fn(|req, srv| {
|
||||||
println!(
|
println!(
|
||||||
"Request to /extra/secure with id: {}",
|
"Request to /extra/secure with id: {}",
|
||||||
|
@ -204,3 +262,18 @@ fn extra_endpoints(cfg: &mut web::ServiceConfig) {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional endpoints for /foo
|
||||||
|
fn other_endpoints(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(
|
||||||
|
web::scope("/extra")
|
||||||
|
.route(
|
||||||
|
"/ping",
|
||||||
|
web::post().to(|| async { HttpResponse::Ok().body("post from /extra/ping") }), // POST /foo/extra/ping
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/ping",
|
||||||
|
web::delete().to(|| async { HttpResponse::Ok().body("delete from /extra/ping") }), // DELETE /foo/extra/ping
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -89,35 +89,6 @@ where
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mut rdef, srv, guards, nested)| {
|
.map(|(mut rdef, srv, guards, nested)| {
|
||||||
rmap.add(&mut rdef, nested);
|
rmap.add(&mut rdef, nested);
|
||||||
#[cfg(feature = "experimental-introspection")]
|
|
||||||
{
|
|
||||||
use std::borrow::Borrow;
|
|
||||||
let pat = rdef.pattern().unwrap_or("").to_string();
|
|
||||||
let mut methods = Vec::new();
|
|
||||||
let mut guard_names = Vec::new();
|
|
||||||
if let Some(gs) = guards.borrow().as_ref() {
|
|
||||||
for g in gs.iter() {
|
|
||||||
let name = g.name().to_string();
|
|
||||||
if !guard_names.contains(&name) {
|
|
||||||
guard_names.push(name.clone());
|
|
||||||
}
|
|
||||||
if let Some(details) = g.details() {
|
|
||||||
for d in details {
|
|
||||||
if let crate::guard::GuardDetail::HttpMethods(v) = d {
|
|
||||||
for s in v {
|
|
||||||
if let Ok(m) = s.parse() {
|
|
||||||
if !methods.contains(&m) {
|
|
||||||
methods.push(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::introspection::register_pattern_detail(pat, methods, guard_names);
|
|
||||||
}
|
|
||||||
(rdef, srv, RefCell::new(guards))
|
(rdef, srv, RefCell::new(guards))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|
|
@ -30,6 +30,8 @@ pub struct AppService {
|
||||||
Option<Guards>,
|
Option<Guards>,
|
||||||
Option<Rc<ResourceMap>>,
|
Option<Rc<ResourceMap>>,
|
||||||
)>,
|
)>,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub current_prefix: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppService {
|
impl AppService {
|
||||||
|
@ -40,6 +42,8 @@ impl AppService {
|
||||||
default,
|
default,
|
||||||
root: true,
|
root: true,
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
current_prefix: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +75,8 @@ impl AppService {
|
||||||
default: Rc::clone(&self.default),
|
default: Rc::clone(&self.default),
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
root: false,
|
root: false,
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
current_prefix: self.current_prefix.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +107,67 @@ impl AppService {
|
||||||
InitError = (),
|
InitError = (),
|
||||||
> + 'static,
|
> + 'static,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
{
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
use crate::introspection;
|
||||||
|
|
||||||
|
// Build the full path for introspection
|
||||||
|
let pat = rdef.pattern().unwrap_or("").to_string();
|
||||||
|
|
||||||
|
let full_path = if self.current_prefix.is_empty() {
|
||||||
|
pat.clone()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
self.current_prefix.trim_end_matches('/'),
|
||||||
|
pat.trim_start_matches('/')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract methods and guards for introspection
|
||||||
|
let guard_list: &[Box<dyn Guard>] = guards.borrow().as_ref().map_or(&[], |v| &v[..]);
|
||||||
|
let methods = guard_list
|
||||||
|
.iter()
|
||||||
|
.flat_map(|g| g.details().unwrap_or_default())
|
||||||
|
.flat_map(|d| {
|
||||||
|
if let crate::guard::GuardDetail::HttpMethods(v) = d {
|
||||||
|
v.into_iter()
|
||||||
|
.filter_map(|s| s.parse().ok())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let guard_names = guard_list
|
||||||
|
.iter()
|
||||||
|
.map(|g| g.name().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Determine if the registered service is a resource
|
||||||
|
let is_resource = rdef.pattern().is_some();
|
||||||
|
introspection::register_pattern_detail(full_path, methods, guard_names, is_resource);
|
||||||
|
}
|
||||||
|
|
||||||
self.services
|
self.services
|
||||||
.push((rdef, boxed::factory(factory.into_factory()), guards, nested));
|
.push((rdef, boxed::factory(factory.into_factory()), guards, nested));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the current path prefix.
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub(crate) fn update_prefix(&mut self, prefix: &str) {
|
||||||
|
self.current_prefix = if self.current_prefix.is_empty() {
|
||||||
|
prefix.to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
self.current_prefix.trim_end_matches('/'),
|
||||||
|
prefix.trim_start_matches('/')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application connection config.
|
/// Application connection config.
|
||||||
|
|
|
@ -15,7 +15,13 @@ static DESIGNATED_THREAD: OnceLock<thread::ThreadId> = OnceLock::new();
|
||||||
static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
pub fn initialize_registry() {
|
pub fn initialize_registry() {
|
||||||
REGISTRY.get_or_init(|| Mutex::new(IntrospectionNode::new(ResourceType::App, "".into())));
|
REGISTRY.get_or_init(|| {
|
||||||
|
Mutex::new(IntrospectionNode::new(
|
||||||
|
ResourceType::App,
|
||||||
|
"".into(),
|
||||||
|
"".into(),
|
||||||
|
))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_registry() -> &'static Mutex<IntrospectionNode> {
|
pub fn get_registry() -> &'static Mutex<IntrospectionNode> {
|
||||||
|
@ -36,6 +42,7 @@ pub fn get_detail_registry() -> &'static Mutex<HashMap<String, RouteDetail>> {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -48,35 +55,48 @@ pub enum ResourceType {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IntrospectionNode {
|
pub struct IntrospectionNode {
|
||||||
pub kind: ResourceType,
|
pub kind: ResourceType,
|
||||||
pub pattern: String,
|
pub pattern: String, // Local pattern
|
||||||
|
pub full_path: String, // Full path
|
||||||
pub methods: Vec<Method>,
|
pub methods: Vec<Method>,
|
||||||
pub guards: Vec<String>,
|
pub guards: Vec<String>,
|
||||||
pub children: Vec<IntrospectionNode>,
|
pub children: Vec<IntrospectionNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntrospectionNode {
|
impl IntrospectionNode {
|
||||||
pub fn new(kind: ResourceType, pattern: String) -> Self {
|
pub fn new(kind: ResourceType, pattern: String, full_path: String) -> Self {
|
||||||
IntrospectionNode {
|
IntrospectionNode {
|
||||||
kind,
|
kind,
|
||||||
pattern,
|
pattern,
|
||||||
|
full_path,
|
||||||
methods: Vec::new(),
|
methods: Vec::new(),
|
||||||
guards: Vec::new(),
|
guards: Vec::new(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display(&self, indent: usize, parent_path: &str) {
|
pub fn display(&self, indent: usize) -> String {
|
||||||
let full_path = if parent_path.is_empty() {
|
let mut result = String::new();
|
||||||
self.pattern.clone()
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{}/{}",
|
|
||||||
parent_path.trim_end_matches('/'),
|
|
||||||
self.pattern.trim_start_matches('/')
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.methods.is_empty() || !self.guards.is_empty() {
|
// Helper function to determine if a node should be highlighted
|
||||||
|
let should_highlight =
|
||||||
|
|methods: &Vec<Method>, guards: &Vec<String>| !methods.is_empty() || !guards.is_empty();
|
||||||
|
|
||||||
|
// Add the full path for all nodes
|
||||||
|
if !self.full_path.is_empty() {
|
||||||
|
if should_highlight(&self.methods, &self.guards) {
|
||||||
|
// Highlight full_path with yellow if it has methods or guards
|
||||||
|
result.push_str(&format!(
|
||||||
|
"{}\x1b[1;33m{}\x1b[0m",
|
||||||
|
" ".repeat(indent),
|
||||||
|
self.full_path
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
result.push_str(&format!("{}{}", " ".repeat(indent), self.full_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add methods and guards for resource nodes
|
||||||
|
if let ResourceType::Resource = self.kind {
|
||||||
let methods = if self.methods.is_empty() {
|
let methods = if self.methods.is_empty() {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,39 +108,18 @@ impl IntrospectionNode {
|
||||||
format!(" Guards: {:?}", self.guards)
|
format!(" Guards: {:?}", self.guards)
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{}{}{}{}", " ".repeat(indent), full_path, methods, guards);
|
// Highlight final endpoints with ANSI codes for bold and green color
|
||||||
|
result.push_str(&format!("\x1b[1;32m{}{}\x1b[0m\n", methods, guards));
|
||||||
|
} else {
|
||||||
|
// For non-resource nodes, just add a newline
|
||||||
|
result.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
child.display(indent, &full_path);
|
result.push_str(&child.display(indent + 2)); // Increase indent for children
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_tree(node: &mut IntrospectionNode, rmap: &ResourceMap) {
|
result
|
||||||
initialize_detail_registry();
|
|
||||||
let detail_registry = get_detail_registry();
|
|
||||||
if let Some(ref children) = rmap.nodes {
|
|
||||||
for child_rc in children {
|
|
||||||
let child = child_rc;
|
|
||||||
let pat = child.pattern.pattern().unwrap_or("").to_string();
|
|
||||||
let kind = if child.nodes.is_some() {
|
|
||||||
ResourceType::Scope
|
|
||||||
} else {
|
|
||||||
ResourceType::Resource
|
|
||||||
};
|
|
||||||
let mut new_node = IntrospectionNode::new(kind, pat.clone());
|
|
||||||
|
|
||||||
if let ResourceType::Resource = new_node.kind {
|
|
||||||
if let Some(d) = detail_registry.lock().unwrap().get(&pat) {
|
|
||||||
new_node.methods = d.methods.clone();
|
|
||||||
new_node.guards = d.guards.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_tree(&mut new_node, child);
|
|
||||||
node.children.push(new_node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,19 +133,70 @@ fn is_designated_thread() -> bool {
|
||||||
*DESIGNATED_THREAD.get().unwrap() == current_id
|
*DESIGNATED_THREAD.get().unwrap() == current_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_rmap(rmap: &ResourceMap) {
|
pub fn register_rmap(_rmap: &ResourceMap) {
|
||||||
if !is_designated_thread() {
|
if !is_designated_thread() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize_registry();
|
initialize_registry();
|
||||||
let mut root = IntrospectionNode::new(ResourceType::App, "".into());
|
initialize_detail_registry();
|
||||||
build_tree(&mut root, rmap);
|
|
||||||
|
let detail_registry = get_detail_registry().lock().unwrap();
|
||||||
|
let mut root = IntrospectionNode::new(ResourceType::App, "".into(), "".into());
|
||||||
|
|
||||||
|
// Build the introspection tree directly from the detail registry
|
||||||
|
for (full_path, _detail) in detail_registry.iter() {
|
||||||
|
let parts: Vec<&str> = full_path.split('/').collect();
|
||||||
|
let mut current_node = &mut root;
|
||||||
|
|
||||||
|
for (i, part) in parts.iter().enumerate() {
|
||||||
|
// Find the index of the existing child
|
||||||
|
let existing_child_index = current_node
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.position(|n| n.pattern == *part);
|
||||||
|
|
||||||
|
let child_index = if let Some(child_index) = existing_child_index {
|
||||||
|
child_index
|
||||||
|
} else {
|
||||||
|
// If it doesn't exist, create a new node and get its index
|
||||||
|
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
|
||||||
|
.get(&child_full_path)
|
||||||
|
.is_some_and(|d| d.is_resource)
|
||||||
|
{
|
||||||
|
ResourceType::Resource
|
||||||
|
} else {
|
||||||
|
ResourceType::Scope
|
||||||
|
};
|
||||||
|
let new_node = IntrospectionNode::new(kind, part.to_string(), child_full_path);
|
||||||
|
current_node.children.push(new_node);
|
||||||
|
current_node.children.len() - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a mutable reference to the child node
|
||||||
|
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 Some(detail) = detail_registry.get(¤t_node.full_path) {
|
||||||
|
update_unique(&mut current_node.methods, &detail.methods);
|
||||||
|
update_unique(&mut current_node.guards, &detail.guards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*get_registry().lock().unwrap() = root;
|
*get_registry().lock().unwrap() = root;
|
||||||
|
|
||||||
// WIP. Display the introspection tree
|
// Display the introspection tree
|
||||||
let reg = get_registry().lock().unwrap();
|
let registry = get_registry().lock().unwrap();
|
||||||
reg.display(0, "");
|
let tree_representation = registry.display(0);
|
||||||
|
log::debug!(
|
||||||
|
"Introspection Tree:\n{}",
|
||||||
|
tree_representation.trim_matches('\n').to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
||||||
|
@ -157,16 +207,29 @@ fn update_unique<T: Clone + PartialEq>(existing: &mut Vec<T>, new_items: &[T]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_pattern_detail(pattern: String, methods: Vec<Method>, guards: Vec<String>) {
|
pub fn register_pattern_detail(
|
||||||
|
full_path: String,
|
||||||
|
methods: Vec<Method>,
|
||||||
|
guards: Vec<String>,
|
||||||
|
is_resource: bool,
|
||||||
|
) {
|
||||||
if !is_designated_thread() {
|
if !is_designated_thread() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
initialize_detail_registry();
|
initialize_detail_registry();
|
||||||
let mut reg = get_detail_registry().lock().unwrap();
|
let mut reg = get_detail_registry().lock().unwrap();
|
||||||
reg.entry(pattern)
|
reg.entry(full_path)
|
||||||
.and_modify(|d| {
|
.and_modify(|d| {
|
||||||
update_unique(&mut d.methods, &methods);
|
update_unique(&mut d.methods, &methods);
|
||||||
update_unique(&mut d.guards, &guards);
|
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 });
|
.or_insert(RouteDetail {
|
||||||
|
methods,
|
||||||
|
guards,
|
||||||
|
is_resource,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,31 +430,53 @@ where
|
||||||
} else {
|
} else {
|
||||||
ResourceDef::new(self.rdef.clone())
|
ResourceDef::new(self.rdef.clone())
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
{
|
||||||
|
use crate::{http::Method, introspection};
|
||||||
|
|
||||||
|
let guards_routes = routes.iter().map(|r| r.guards()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let pat = rdef.pattern().unwrap_or("").to_string();
|
||||||
|
let full_path = if config.current_prefix.is_empty() {
|
||||||
|
pat.clone()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
config.current_prefix.trim_end_matches('/'),
|
||||||
|
pat.trim_start_matches('/')
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
for route_guards in guards_routes {
|
||||||
|
// Log the guards and methods for introspection
|
||||||
|
let guard_names = route_guards.iter().map(|g| g.name()).collect::<Vec<_>>();
|
||||||
|
let methods = route_guards
|
||||||
|
.iter()
|
||||||
|
.flat_map(|g| g.details().unwrap_or_default())
|
||||||
|
.flat_map(|d| {
|
||||||
|
if let crate::guard::GuardDetail::HttpMethods(v) = d {
|
||||||
|
v.into_iter()
|
||||||
|
.filter_map(|s| s.parse::<Method>().ok())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
introspection::register_pattern_detail(
|
||||||
|
full_path.clone(),
|
||||||
|
methods,
|
||||||
|
guard_names,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref name) = self.name {
|
if let Some(ref name) = self.name {
|
||||||
rdef.set_name(name);
|
rdef.set_name(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-introspection")]
|
|
||||||
{
|
|
||||||
let pat = rdef.pattern().unwrap_or("").to_string();
|
|
||||||
let mut methods = Vec::new();
|
|
||||||
let mut guard_names = Vec::new();
|
|
||||||
for route in &routes {
|
|
||||||
if let Some(m) = route.get_method() {
|
|
||||||
if !methods.contains(&m) {
|
|
||||||
methods.push(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name in route.guard_names() {
|
|
||||||
if !guard_names.contains(&name) {
|
|
||||||
guard_names.push(name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::introspection::register_pattern_detail(pat, methods, guard_names);
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
*self.factory_ref.borrow_mut() = Some(ResourceFactory {
|
||||||
routes,
|
routes,
|
||||||
default: self.default,
|
default: self.default,
|
||||||
|
|
|
@ -15,13 +15,16 @@ const AVG_PATH_LEN: usize = 24;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ResourceMap {
|
pub struct ResourceMap {
|
||||||
pub(crate) pattern: ResourceDef,
|
pattern: ResourceDef,
|
||||||
|
|
||||||
/// Named resources within the tree or, for external resources, it points to isolated nodes
|
/// Named resources within the tree or, for external resources, it points to isolated nodes
|
||||||
/// outside the tree.
|
/// outside the tree.
|
||||||
named: FoldHashMap<String, Rc<ResourceMap>>,
|
named: FoldHashMap<String, Rc<ResourceMap>>,
|
||||||
|
|
||||||
parent: RefCell<Weak<ResourceMap>>,
|
parent: RefCell<Weak<ResourceMap>>,
|
||||||
|
|
||||||
/// Must be `None` for "edge" nodes.
|
/// Must be `None` for "edge" nodes.
|
||||||
pub(crate) nodes: Option<Vec<Rc<ResourceMap>>>,
|
nodes: Option<Vec<Rc<ResourceMap>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceMap {
|
impl ResourceMap {
|
||||||
|
|
|
@ -65,11 +65,6 @@ impl Route {
|
||||||
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
|
||||||
mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
mem::take(Rc::get_mut(&mut self.guards).unwrap())
|
||||||
}
|
}
|
||||||
/// Get the names of all guards applied to this route.
|
|
||||||
#[cfg(feature = "experimental-introspection")]
|
|
||||||
pub fn guard_names(&self) -> Vec<String> {
|
|
||||||
self.guards.iter().map(|g| g.name()).collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceFactory<ServiceRequest> for Route {
|
impl ServiceFactory<ServiceRequest> for Route {
|
||||||
|
@ -145,23 +140,6 @@ impl Route {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-introspection")]
|
|
||||||
/// Get the first HTTP method guard applied to this route (if any).
|
|
||||||
/// WIP.
|
|
||||||
pub(crate) fn get_method(&self) -> Option<Method> {
|
|
||||||
self.guards.iter().find_map(|g| {
|
|
||||||
g.details().and_then(|details| {
|
|
||||||
details.into_iter().find_map(|d| {
|
|
||||||
if let crate::guard::GuardDetail::HttpMethods(mut m) = d {
|
|
||||||
m.pop().and_then(|s| s.parse().ok())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add guard to the route.
|
/// Add guard to the route.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -181,7 +159,8 @@ impl Route {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn guards(&self) -> &Vec<Box<dyn Guard>> {
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
pub(crate) fn guards(&self) -> &Vec<Box<dyn Guard>> {
|
||||||
&self.guards
|
&self.guards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -384,6 +384,11 @@ where
|
||||||
|
|
||||||
// register nested services
|
// register nested services
|
||||||
let mut cfg = config.clone_config();
|
let mut cfg = config.clone_config();
|
||||||
|
|
||||||
|
// Update the prefix for the nested scope
|
||||||
|
#[cfg(feature = "experimental-introspection")]
|
||||||
|
cfg.update_prefix(&self.rdef);
|
||||||
|
|
||||||
self.services
|
self.services
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|mut srv| srv.register(&mut cfg));
|
.for_each(|mut srv| srv.register(&mut cfg));
|
||||||
|
@ -404,41 +409,6 @@ where
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mut rdef, srv, guards, nested)| {
|
.map(|(mut rdef, srv, guards, nested)| {
|
||||||
rmap.add(&mut rdef, nested);
|
rmap.add(&mut rdef, nested);
|
||||||
|
|
||||||
#[cfg(feature = "experimental-introspection")]
|
|
||||||
{
|
|
||||||
use std::borrow::Borrow;
|
|
||||||
|
|
||||||
// Get the pattern stored in ResourceMap
|
|
||||||
let pat = rdef.pattern().unwrap_or("").to_string();
|
|
||||||
let guard_list: &[Box<dyn Guard>] =
|
|
||||||
guards.borrow().as_ref().map_or(&[], |v| &v[..]);
|
|
||||||
|
|
||||||
// Extract HTTP methods from guards
|
|
||||||
let methods = guard_list
|
|
||||||
.iter()
|
|
||||||
.flat_map(|g| g.details().unwrap_or_default())
|
|
||||||
.flat_map(|d| {
|
|
||||||
if let crate::guard::GuardDetail::HttpMethods(v) = d {
|
|
||||||
v.into_iter()
|
|
||||||
.filter_map(|s| s.parse().ok())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Extract guard names
|
|
||||||
let guard_names = guard_list
|
|
||||||
.iter()
|
|
||||||
.map(|g| g.name().to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Register route details for introspection
|
|
||||||
crate::introspection::register_pattern_detail(pat, methods, guard_names);
|
|
||||||
}
|
|
||||||
|
|
||||||
(rdef, srv, RefCell::new(guards))
|
(rdef, srv, RefCell::new(guards))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|
Loading…
Reference in New Issue