use std::collections::HashMap;
use std::sync::Arc;
use arc_swap::ArcSwap;
use axum::http::Method;
use axum::routing::any;
use crate::core::engine::FrozenDiContainer;
use crate::core::plugins::PluginHandler;
use crate::http::Response;
use crate::web::boundary::BoundaryFilter;
use crate::web::context::RequestContext;
pub const DYNAMIC_PREFIX: &str = "/_plugins";
type Table = HashMap<(Method, String), PluginHandler>;
pub struct DynamicRouteTable {
table: ArcSwap<Table>,
}
impl DynamicRouteTable {
pub(crate) fn new() -> Self {
Self {
table: ArcSwap::from_pointee(Table::new()),
}
}
pub fn mount<F, Fut>(&self, method: Method, path: &str, handler: F) -> Option<PluginHandler>
where
F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Response> + Send + 'static,
{
let full = format!("{DYNAMIC_PREFIX}{path}");
let h: PluginHandler = Arc::new(move |ctx| Box::pin(handler(ctx)));
let key = (method, full);
let mut replaced = None;
self.table.rcu(|cur| {
let mut next: Table = (**cur).clone();
replaced = next.insert(key.clone(), h.clone());
next
});
replaced
}
pub fn unmount(&self, method: Method, path: &str) -> bool {
let key = (method, format!("{DYNAMIC_PREFIX}{path}"));
let mut removed = false;
self.table.rcu(|cur| {
let mut next: Table = (**cur).clone();
removed = next.remove(&key).is_some();
next
});
removed
}
fn lookup(&self, method: &Method, path: &str) -> Option<PluginHandler> {
self.table
.load()
.get(&(method.clone(), path.to_owned()))
.cloned()
}
}
pub(crate) fn dynamic_dispatch_route(
container: &'static FrozenDiContainer,
globals: &'static [&'static dyn crate::web::interceptors::Interceptor],
filters: &'static [&'static dyn BoundaryFilter],
) -> axum::routing::MethodRouter {
let handler = move |req: axum::extract::Request| async move {
let (parts, body) = req.into_parts();
let table = container.get::<DynamicRouteTable>();
let Some(h) = table.lookup(&parts.method, parts.uri.path()) else {
return Response::builder()
.status(404)
.body(axum::body::Body::from("dynamic route not found"))
.expect("static 404");
};
let chain = crate::web::interceptors::compose_chain(globals, h);
crate::web::boundary::run_entry(
parts,
body,
Default::default(),
container,
"/_plugins/*",
None,
filters,
&chain,
)
.await
};
any(handler)
}