Skip to main content

rust_webx_core/route/
diagnostics.rs

1//! Compile-time route and handler diagnostics.
2
3use std::collections::HashSet;
4
5use crate::route::scan::{HandlerCache, HandlerRegistration, RouteEntry};
6
7/// Snapshot of a registered HTTP route.
8#[derive(Debug, Clone)]
9pub struct RouteSnapshot {
10    pub method: String,
11    pub path: &'static str,
12    pub request_type: &'static str,
13    pub response_type: &'static str,
14    pub has_handler: bool,
15}
16
17/// Collect all inventory routes and whether a matching `#[handler]` exists.
18pub fn route_snapshots() -> Vec<RouteSnapshot> {
19    let cache = HandlerCache::build();
20    let mut routes: Vec<RouteSnapshot> = inventory::iter::<RouteEntry>()
21        .map(|entry| RouteSnapshot {
22            method: entry.method.as_str().to_string(),
23            path: entry.path,
24            request_type: entry.handler_type,
25            response_type: entry.rsp_type,
26            has_handler: cache.get(entry.handler_type).is_some(),
27        })
28        .collect();
29    routes.sort_by(|a, b| a.path.cmp(b.path).then(a.method.cmp(&b.method)));
30    routes
31}
32
33/// Request types with a route but no registered handler.
34pub fn orphan_routes() -> Vec<&'static str> {
35    route_snapshots()
36        .into_iter()
37        .filter(|r| !r.has_handler)
38        .map(|r| r.request_type)
39        .collect()
40}
41
42/// Request types registered via `#[handler]` without a matching route.
43pub fn orphan_handlers() -> Vec<&'static str> {
44    let routed: HashSet<&'static str> = inventory::iter::<RouteEntry>()
45        .map(|e| e.handler_type)
46        .collect();
47
48    let mut orphans = Vec::new();
49    for reg in inventory::iter::<HandlerRegistration>() {
50        if !routed.contains(reg.req_type_name) {
51            orphans.push(reg.req_type_name);
52        }
53    }
54    orphans.sort_unstable();
55    orphans
56}
57
58/// Human-readable route/handler diagnostic report (no tracing dependency).
59pub fn format_route_diagnostics() -> String {
60    use std::fmt::Write;
61
62    let routes = route_snapshots();
63    let mut out = format!("Registered HTTP routes: {}\n", routes.len());
64
65    for route in &routes {
66        let status = if route.has_handler { "ok" } else { "ORPHAN" };
67        let _ = writeln!(
68            out,
69            "  [{status}] {} {}  {} => {}",
70            route.method, route.path, route.request_type, route.response_type
71        );
72    }
73
74    let missing_handlers = orphan_routes();
75    if !missing_handlers.is_empty() {
76        let _ = writeln!(out, "\nRoutes without #[handler] ({}):", missing_handlers.len());
77        for req in missing_handlers {
78            let _ = writeln!(out, "  - {req}");
79        }
80    }
81
82    let missing_routes = orphan_handlers();
83    if !missing_routes.is_empty() {
84        let _ = writeln!(out, "\nHandlers without route ({}):", missing_routes.len());
85        for req in missing_routes {
86            let _ = writeln!(out, "  - {req}");
87        }
88    }
89
90    out
91}