use crate::app_router::bundle_layer_plugin::BundleLayerPlugin;
use crate::prelude::*;
use axum::extract::FromRequestParts;
use beet_core::prelude::*;
pub trait RouterPlugin: Sized {
type State: DerivedAppState;
type Meta: 'static + Send + Sync + Clone;
fn layer<L>(self, layer: L) -> BundleLayerPlugin<L, Self>
where
Self: Sized,
{
BundleLayerPlugin::new(layer, self)
}
fn is_static(&self) -> bool;
fn routes(&self) -> Vec<RouteInfo>;
fn meta(&self) -> Vec<Self::Meta>;
fn route_metas(&self) -> Vec<(RouteInfo, Self::Meta)> {
self.routes()
.into_iter()
.zip(self.meta().into_iter())
.collect()
}
fn add_routes(&self, router: Router<Self::State>) -> Router<Self::State> {
self.add_routes_with(router, self)
}
fn add_routes_with(
&self,
router: Router<Self::State>,
plugin: &impl RouterPlugin<State = Self::State, Meta = Self::Meta>,
) -> Router<Self::State>;
fn add_bundle_route<M, H>(
&self,
router: Router<Self::State>,
route_info: RouteInfo,
handler: H,
_meta: Self::Meta,
) -> Router<Self::State>
where
H: BundleRoute<M, State = Self::State>,
H::Extractors: 'static + Send + Sync + FromRequestParts<Self::State>,
{
self.add_route(router, route_info, handler)
}
fn add_route<M>(
&self,
router: Router<Self::State>,
route_info: RouteInfo,
handler: impl IntoBeetRoute<M, State = Self::State>,
) -> Router<Self::State> {
handler.add_beet_route(router, route_info)
}
}
pub trait IntoRoutePlugins<S, M> {
fn add_to_router(self, router: &mut AppRouter<S>);
}
impl<T: RouterPlugin> IntoRoutePlugins<T::State, T> for T {
fn add_to_router(self, app_router: &mut AppRouter<T::State>) {
if self.is_static() {
app_router.static_routes.extend(self.routes());
app_router.router = self.add_routes_with(
app_router.router.clone(),
&ClientIslandRouterPlugin::<T>::default(),
);
}
app_router.router = self.add_routes(app_router.router.clone());
}
}
use variadics_please::all_tuples;
pub struct RouterPluginsTupleMarker;
macro_rules! impl_router_plugins_tuples {
($(#[$meta:meta])* $(($param: ident, $plugins: ident)),*) => {
$(#[$meta])*
impl<S,$($param, $plugins),*> IntoRoutePlugins<S,(RouterPluginsTupleMarker, $($param,)*)> for ($($plugins,)*)
where
$($plugins: IntoRoutePlugins<S,$param>),*
{
#[expect(
clippy::allow_attributes,
reason = "This is inside a macro, and as such, may not trigger in all cases."
)]
#[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
#[allow(unused_variables, reason = "`app` is unused when implemented for the unit type `()`.")]
#[track_caller]
fn add_to_router(self, router: &mut AppRouter<S>) {
let ($($plugins,)*) = self;
$($plugins.add_to_router(router);)*
}
}
}
}
all_tuples!(impl_router_plugins_tuples, 1, 15, M, T);
#[cfg(test)]
mod test {
use crate::prelude::*;
struct Plugin1;
impl RouterPlugin for Plugin1 {
type State = AppRouterState;
type Meta = ();
fn is_static(&self) -> bool { unimplemented!() }
fn routes(&self) -> Vec<RouteInfo> { unimplemented!() }
fn meta(&self) -> Vec<Self::Meta> { unimplemented!() }
fn add_routes_with(
&self,
_: Router<Self::State>,
_: &impl RouterPlugin<State = Self::State, Meta = Self::Meta>,
) -> Router<Self::State> {
unimplemented!()
}
}
struct Plugin2;
impl RouterPlugin for Plugin2 {
type State = AppRouterState;
type Meta = ();
fn is_static(&self) -> bool { unimplemented!() }
fn routes(&self) -> Vec<RouteInfo> { unimplemented!() }
fn meta(&self) -> Vec<Self::Meta> { unimplemented!() }
fn add_routes_with(
&self,
_: Router<Self::State>,
_: &impl RouterPlugin<State = Self::State, Meta = Self::Meta>,
) -> Router<Self::State> {
unimplemented!()
}
}
#[test]
fn works() {
fn foo<M>(_: impl IntoRoutePlugins<AppRouterState, M>) {}
foo((Plugin1, Plugin2));
}
}