use axum::body::Body;
use axum::extract::{RawPathParams, Request, State};
use axum::http::request::Parts;
use axum::response::Response;
use axum::routing::{on, MethodFilter, MethodRouter};
use smallvec::SmallVec;
use smol_str::SmolStr;
use crate::auth::extract::extract_auth;
use crate::core::engine::{FrozenDiContainer, RouteDescriptor, RouteSpec};
use crate::observability::lean_telemetry::on_request_start;
use crate::observability::propagation::extract_trace_context;
use crate::web::context::RequestContext;
const MAX_BODY: usize = 8 * 1024 * 1024;
pub(crate) struct InFlightGuard;
impl InFlightGuard {
#[inline]
pub(crate) fn new() -> Self {
metrics::gauge!("http_requests_in_flight").increment(1.0);
Self
}
}
impl Drop for InFlightGuard {
#[inline]
fn drop(&mut self) {
metrics::gauge!("http_requests_in_flight").decrement(1.0);
}
}
pub(crate) async fn assemble_context(
parts: Parts,
body: Body,
params: SmallVec<[(SmolStr, SmolStr); 4]>,
container: &'static FrozenDiContainer,
route_pattern: &'static str,
route_spec: Option<&'static RouteSpec>,
) -> RequestContext {
let bytes = axum::body::to_bytes(body, MAX_BODY)
.await
.unwrap_or_default();
let trace = extract_trace_context(&parts.headers);
let auth = extract_auth(&parts.headers, container).await;
let tenant = container
.try_get::<crate::web::tenant::TenantRegistry>()
.and_then(|tr| tr.resolve(&parts.headers));
RequestContext::__new(
parts.method,
SmolStr::new(parts.uri.path()),
SmolStr::new(parts.uri.query().unwrap_or("")),
params,
parts.headers,
bytes,
trace.trace_id,
trace.span_id,
trace.parent_span_id,
container,
route_pattern,
route_spec,
)
.__with_claims(auth.claims)
.__with_session(auth.session)
.__with_tenant(tenant)
}
pub fn adapt(rt: &'static RouteDescriptor) -> MethodRouter<&'static FrozenDiContainer> {
let filter = MethodFilter::try_from(axum::http::Method::from(rt.method))
.expect("Arcly: unsupported HTTP method");
let handler = move |State(container): State<&'static FrozenDiContainer>,
raw_params: RawPathParams,
req: Request| async move {
let params: SmallVec<[(SmolStr, SmolStr); 4]> = raw_params
.iter()
.map(|(k, v)| (SmolStr::new(k), SmolStr::new(v)))
.collect();
let (parts, body) = req.into_parts();
let ctx = assemble_context(parts, body, params, container, rt.path, Some(rt.spec)).await;
let _guard = on_request_start();
let _in_flight = InFlightGuard::new();
let resp: Response = (rt.handler)(ctx).await;
drop(_guard);
let (mut p, b) = resp.into_parts();
if !rt.spec.sunset.is_empty() {
p.headers
.insert("deprecation", axum::http::HeaderValue::from_static("true"));
if let Ok(v) = axum::http::HeaderValue::from_str(rt.spec.sunset) {
p.headers.insert("sunset", v);
}
}
Response::from_parts(p, Body::new(b))
};
on(filter, handler)
}