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;
12
13#[doc(hidden)]
14pub fn build_plugin_route(
15    container: &'static FrozenDiContainer,
16    route: &PluginRoute,
17    globals: &'static [&'static dyn crate::web::interceptors::Interceptor],
18    filters: &'static [&'static dyn crate::web::boundary::BoundaryFilter],
19) -> MethodRouter {
20    let filter = MethodFilter::try_from(axum::http::Method::from(route.method))
21        .expect("supported HTTP method");
22    let chain = crate::web::interceptors::compose_chain(globals, route.handler.clone());
23    // Plugin paths are mounted verbatim (no params), so the path doubles as
24    // the metrics route-pattern label with no cardinality risk.
25    let pattern = route.path;
26
27    let handler = move |req: axum::extract::Request| {
28        let chain = chain.clone();
29        async move {
30            let (parts, body) = req.into_parts();
31            crate::web::boundary::run_entry(
32                parts,
33                body,
34                Default::default(),
35                container,
36                pattern,
37                None,
38                filters,
39                &chain,
40            )
41            .await
42        }
43    };
44
45    on(filter, handler)
46}