Skip to main content

arcly_http/web/
plugin_routes.rs

1//! Mounts plugin-registered routes as axum handlers.
2//!
3//! Lives in the `web` layer (not `core`) so the DI engine and plugin
4//! lifecycle stay HTTP-agnostic. Reuses `boundary::assemble_context` —
5//! plugin routes get exactly the same body cap, trace propagation, and
6//! credential extraction as macro routes, from the same code path.
7
8use axum::routing::{on, MethodFilter, MethodRouter};
9
10use crate::core::engine::FrozenDiContainer;
11use crate::core::plugins::PluginRoute;
12use crate::observability::lean_telemetry::on_request_start;
13use crate::web::boundary::{assemble_context, InFlightGuard};
14
15#[doc(hidden)]
16pub fn build_plugin_route(
17    container: &'static FrozenDiContainer,
18    route: &PluginRoute,
19) -> MethodRouter {
20    let filter = MethodFilter::try_from(axum::http::Method::from(route.method))
21        .expect("supported HTTP method");
22    let h = route.handler.clone();
23
24    let handler = move |req: axum::extract::Request| {
25        let h = h.clone();
26        async move {
27            let (parts, body) = req.into_parts();
28            // Plugin routes have no matched pattern (mounted verbatim) and no
29            // static RouteSpec — metrics label by empty pattern, not raw path.
30            let ctx = assemble_context(parts, body, Default::default(), container, "", None).await;
31
32            let _guard = on_request_start();
33            let _in_flight = InFlightGuard::new();
34            h(ctx).await
35        }
36    };
37
38    on(filter, handler)
39}