arcature 2026.2.0

Arcature application framework: a high-level Application facade over the certified Arcature subsystems, with the low-level Axum/Tower escape hatch preserved.
Documentation
//! The matched-route enrichment adapter โ€” a `from_fn` middleware that bridges
//! axum's `MatchedPath` to the observe crate's `MatchedRoute`.
//!
//! This is the smallest backward-compatible improvement to the observe
//! integration (engine spec ยง8): the observe crate records matched route
//! *templates* (e.g. `/users/{id}`), not concrete paths (e.g. `/users/928137`).
//! The observe crate's `HttpLayer` reads `MatchedRoute` from request
//! extensions, but axum populates `MatchedPath` (a different type). This
//! adapter copies the template from `MatchedPath` โ†’ `MatchedRoute` so
//! `HttpLayer` sees it.
//!
//! Applied via `Router::route_layer` (runs only on matched routes, after
//! `MatchedPath` is set by axum's route matcher). Positioned as the
//! outermost route_layer so it runs before `HttpLayer` reads the extension.
//!
//! Gated by the `observe` feature: needs `axum::extract::MatchedPath` (enabled
//! by `axum/matched-path` in the observe feature) and
//! `arcature_observe::MatchedRoute`.

use crate::axum::extract::MatchedPath;
use crate::axum::middleware::Next;
use crate::axum::response::Response;

/// Read `MatchedPath` from request extensions and insert a `MatchedRoute` with
/// the same template, so `HttpLayer` records the route template (not the
/// concrete path) in its `HttpRecord`.
///
/// When `MatchedPath` is absent (e.g. on a fallback route โ€” which should not
/// happen inside a `route_layer`, but defense-in-depth), the request passes
/// through unchanged.
pub async fn set_matched_route(mut req: crate::axum::extract::Request, next: Next) -> Response {
    // Read the matched-path template *before* mutably borrowing the extensions
    // (cannot have `&extensions` and `&mut extensions` alive at once). Clone the
    // `&str` into a `String` so the immutable borrow ends here.
    let template = req
        .extensions()
        .get::<MatchedPath>()
        .map(|path| path.as_str().to_owned());
    if let Some(template) = template {
        req.extensions_mut()
            .insert(crate::observe::MatchedRoute::new(template));
    }
    next.run(req).await
}