rust_webx_host/
diagnostics.rs1use rust_webx_core::route::diagnostics::{
4 duplicate_handlers, orphan_handlers, orphan_route_details, route_snapshots,
5};
6use rust_webx_core::route::scan::{RouteDispatch, RouteEntry};
7
8pub fn assert_route_configuration_valid() {
15 let orphan_route_rows = orphan_route_details();
16 let missing_routes = orphan_handlers();
17
18 let mut missing_dispatch: Vec<(&'static str, &'static str, &'static str)> = Vec::new();
19 let cache = rust_webx_core::route::scan::HandlerCache::build();
20 let dispatch_types: std::collections::HashSet<&'static str> = inventory::iter::<RouteDispatch>()
21 .map(|d| d.handler_type)
22 .collect();
23
24 for entry in inventory::iter::<RouteEntry> {
25 if cache.get(entry.handler_type).is_some() && !dispatch_types.contains(entry.handler_type) {
26 missing_dispatch.push((
27 entry.method.as_str(),
28 entry.path,
29 entry.handler_type,
30 ));
31 }
32 }
33
34 if orphan_route_rows.is_empty() && missing_routes.is_empty() && missing_dispatch.is_empty() {
35 return;
36 }
37
38 let mut message = String::from("Route configuration errors:\n");
39 if !orphan_route_rows.is_empty() {
40 message.push_str("\nRoutes without #[handler]:\n");
41 message.push_str(
42 " Fix: add #[handler] or #[handler(inject)] impl IRequestHandler<Request, Response>\n",
43 );
44 for route in &orphan_route_rows {
45 message.push_str(&format!(
46 " - {} {} ({})\n",
47 route.method, route.path, route.request_type
48 ));
49 }
50 }
51 if !missing_routes.is_empty() {
52 message.push_str("\nHandlers without route:\n");
53 message.push_str(
54 " Fix: add #[get]/#[post]/... impl IRequest<R> for the request type, or remove the orphan #[handler]\n",
55 );
56 for req in &missing_routes {
57 message.push_str(&format!(" - {req}\n"));
58 }
59 }
60 if !missing_dispatch.is_empty() {
61 message.push_str("\nRoutes with handler but no RouteDispatch:\n");
62 message.push_str(
63 " Fix: ensure the request type has a route macro (#[get]/#[post]/...) on its IRequest impl\n",
64 );
65 for (method, path, req) in &missing_dispatch {
66 message.push_str(&format!(" - {method} {path} ({req})\n"));
67 }
68 }
69 message.push_str("\nRun: cargo run -p <host-crate> -- --doctor");
70 panic!("{message}");
71}
72
73pub fn log_startup_diagnostics() {
75 let routes = route_snapshots();
76 tracing::info!(count = routes.len(), "Registered HTTP routes");
77 for route in &routes {
78 if route.has_handler {
79 tracing::debug!(
80 method = %route.method,
81 path = route.path,
82 request = route.request_type,
83 response = route.response_type,
84 "route"
85 );
86 } else {
87 tracing::warn!(
88 method = %route.method,
89 path = route.path,
90 request = route.request_type,
91 "route has no #[handler] registration — add #[handler] on IRequestHandler impl"
92 );
93 }
94 }
95
96 for handler in orphan_handlers() {
97 tracing::warn!(
98 request = handler,
99 "handler has no matching route — add #[get]/#[post] on IRequest impl or remove #[handler]"
100 );
101 }
102
103 for (handler, count) in duplicate_handlers() {
104 tracing::warn!(
105 request = handler,
106 count,
107 "duplicate #[handler] registration; last inventory entry wins"
108 );
109 }
110}