mod engine;
mod ordering;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use axum::Router;
use crate::BoxError;
use crate::BoxFuture;
use crate::config::AppConfig;
use crate::pipeline::MiddlewareSlot;
pub use engine::{DEFAULT_REQUEST_BODY_LIMIT, GasketApp, GasketAppBuilder};
pub use ordering::{PluginOrdering, topological_sort};
pub type ActionResult = Result<Box<dyn std::any::Any + Send>, BoxError>;
pub type BoxAction = Arc<dyn Fn(ActionArgs) -> BoxFuture<'static, ActionResult> + Send + Sync>;
pub type ActionArgs = Vec<Box<dyn std::any::Any + Send>>;
pub type BoxRouterLayer = Box<dyn FnOnce(Router) -> Router + Send>;
pub struct RouterTransform {
layer: BoxRouterLayer,
}
impl RouterTransform {
#[must_use]
pub fn new(layer: impl FnOnce(Router) -> Router + Send + 'static) -> Self {
Self {
layer: Box::new(layer),
}
}
pub fn apply(self, router: Router) -> Router {
(self.layer)(router)
}
}
impl std::fmt::Debug for RouterTransform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouterTransform").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RouteGroup {
Bare,
Public,
Protected,
}
#[non_exhaustive]
pub struct TaggedLayer {
pub slot: MiddlewareSlot,
pub layer: RouterTransform,
}
impl TaggedLayer {
pub fn new(
slot: MiddlewareSlot,
layer: impl FnOnce(Router) -> Router + Send + 'static,
) -> Self {
Self {
slot,
layer: RouterTransform::new(layer),
}
}
}
impl std::fmt::Debug for TaggedLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaggedLayer")
.field("slot", &self.slot)
.finish_non_exhaustive()
}
}
#[non_exhaustive]
pub struct TaggedRoute {
pub group: RouteGroup,
pub router: Router,
}
impl TaggedRoute {
#[must_use]
pub const fn new(group: RouteGroup, router: Router) -> Self {
Self { group, router }
}
}
impl std::fmt::Debug for TaggedRoute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaggedRoute")
.field("group", &self.group)
.finish_non_exhaustive()
}
}
pub struct InitContext {
actions: HashMap<String, BoxAction>,
}
impl InitContext {
#[must_use]
pub fn new() -> Self {
Self {
actions: HashMap::new(),
}
}
pub fn register_action(&mut self, name: &str, action: BoxAction) -> Result<(), BoxError> {
if self.actions.contains_key(name) {
return Err(format!("Action '{name}' already registered by another plugin").into());
}
self.actions.insert(name.to_string(), action);
Ok(())
}
pub fn register_action_fn<Function, FutureOutput, Output>(
&mut self,
name: &str,
action: Function,
) -> Result<(), BoxError>
where
Function: Fn(ActionArgs) -> FutureOutput + Send + Sync + 'static,
FutureOutput: Future<Output = Result<Output, BoxError>> + Send + 'static,
Output: std::any::Any + Send + 'static,
{
let action = Arc::new(action);
self.register_action(
name,
Arc::new(move |args| {
let action = Arc::clone(&action);
Box::pin(async move {
let result = action(args).await?;
let result: Box<dyn std::any::Any + Send> = Box::new(result);
Ok(result)
})
}),
)
}
pub(crate) fn into_actions(self) -> HashMap<String, BoxAction> {
self.actions
}
}
impl Default for InitContext {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for InitContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InitContext")
.field("actions", &self.actions.keys().collect::<Vec<_>>())
.finish()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct PrepareContext {
pub config: AppConfig,
pub extensions: http::Extensions,
}
impl PrepareContext {
#[must_use]
pub const fn new(config: AppConfig, extensions: http::Extensions) -> Self {
Self { config, extensions }
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct LayerContext {
pub config: AppConfig,
pub extensions: http::Extensions,
}
impl LayerContext {
#[must_use]
pub const fn new(config: AppConfig, extensions: http::Extensions) -> Self {
Self { config, extensions }
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct RouteContext {
pub config: AppConfig,
pub extensions: http::Extensions,
}
impl RouteContext {
#[must_use]
pub const fn new(config: AppConfig, extensions: http::Extensions) -> Self {
Self { config, extensions }
}
}
#[non_exhaustive]
pub struct ReadyContext {
pub config: AppConfig,
pub extensions: http::Extensions,
pub local_addr: std::net::SocketAddr,
}
impl ReadyContext {
#[must_use]
pub const fn new(
config: AppConfig,
extensions: http::Extensions,
local_addr: std::net::SocketAddr,
) -> Self {
Self {
config,
extensions,
local_addr,
}
}
}
impl std::fmt::Debug for ReadyContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReadyContext")
.field("config", &self.config)
.field("local_addr", &self.local_addr)
.finish_non_exhaustive()
}
}
#[non_exhaustive]
pub struct ShutdownContext {
pub extensions: http::Extensions,
}
impl ShutdownContext {
#[must_use]
pub const fn new(extensions: http::Extensions) -> Self {
Self { extensions }
}
}
impl std::fmt::Debug for ShutdownContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShutdownContext").finish_non_exhaustive()
}
}
pub trait Plugin: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn ordering(&self) -> PluginOrdering {
PluginOrdering::default()
}
fn dependencies(&self) -> Vec<&str> {
Vec::new()
}
fn init(&self, _ctx: &mut InitContext) {}
fn configure(&self, config: AppConfig) -> AppConfig {
config
}
fn prepare<'ctx>(
&'ctx self,
_ctx: &'ctx mut PrepareContext,
) -> impl Future<Output = Result<(), BoxError>> + Send + 'ctx {
async { Ok(()) }
}
fn ready<'ctx>(
&'ctx self,
_ctx: &'ctx ReadyContext,
) -> impl Future<Output = Result<(), BoxError>> + Send + 'ctx {
async { Ok(()) }
}
fn shutdown<'ctx>(
&'ctx self,
_ctx: &'ctx ShutdownContext,
) -> impl Future<Output = Result<(), BoxError>> + Send + 'ctx {
async { Ok(()) }
}
fn layers(&self, _ctx: &LayerContext) -> Vec<TaggedLayer> {
Vec::new()
}
fn routes(&self, _ctx: &RouteContext) -> Vec<TaggedRoute> {
Vec::new()
}
}
trait ErasedPlugin: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn ordering(&self) -> PluginOrdering;
fn dependencies(&self) -> Vec<&str>;
fn init(&self, ctx: &mut InitContext);
fn configure(&self, config: AppConfig) -> AppConfig;
fn prepare<'ctx>(
&'ctx self,
ctx: &'ctx mut PrepareContext,
) -> BoxFuture<'ctx, Result<(), BoxError>>;
fn ready<'ctx>(&'ctx self, ctx: &'ctx ReadyContext) -> BoxFuture<'ctx, Result<(), BoxError>>;
fn shutdown<'ctx>(
&'ctx self,
ctx: &'ctx ShutdownContext,
) -> BoxFuture<'ctx, Result<(), BoxError>>;
fn layers(&self, ctx: &LayerContext) -> Vec<TaggedLayer>;
fn routes(&self, ctx: &RouteContext) -> Vec<TaggedRoute>;
}
impl<T> ErasedPlugin for T
where
T: Plugin,
{
fn name(&self) -> &'static str {
Plugin::name(self)
}
fn ordering(&self) -> PluginOrdering {
Plugin::ordering(self)
}
fn dependencies(&self) -> Vec<&str> {
Plugin::dependencies(self)
}
fn init(&self, ctx: &mut InitContext) {
Plugin::init(self, ctx);
}
fn configure(&self, config: AppConfig) -> AppConfig {
Plugin::configure(self, config)
}
fn prepare<'ctx>(
&'ctx self,
ctx: &'ctx mut PrepareContext,
) -> BoxFuture<'ctx, Result<(), BoxError>> {
Box::pin(Plugin::prepare(self, ctx))
}
fn ready<'ctx>(&'ctx self, ctx: &'ctx ReadyContext) -> BoxFuture<'ctx, Result<(), BoxError>> {
Box::pin(Plugin::ready(self, ctx))
}
fn shutdown<'ctx>(
&'ctx self,
ctx: &'ctx ShutdownContext,
) -> BoxFuture<'ctx, Result<(), BoxError>> {
Box::pin(Plugin::shutdown(self, ctx))
}
fn layers(&self, ctx: &LayerContext) -> Vec<TaggedLayer> {
Plugin::layers(self, ctx)
}
fn routes(&self, ctx: &RouteContext) -> Vec<TaggedRoute> {
Plugin::routes(self, ctx)
}
}
pub struct PluginHandle {
inner: Box<dyn ErasedPlugin>,
}
impl PluginHandle {
pub fn new(plugin: impl Plugin) -> Self {
Self {
inner: Box::new(plugin),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
self.inner.name()
}
pub(crate) fn ordering(&self) -> PluginOrdering {
self.inner.ordering()
}
pub(crate) fn dependencies(&self) -> Vec<&str> {
self.inner.dependencies()
}
pub(crate) fn init(&self, ctx: &mut InitContext) {
self.inner.init(ctx);
}
pub(crate) fn configure(&self, config: AppConfig) -> AppConfig {
self.inner.configure(config)
}
pub(crate) fn prepare<'ctx>(
&'ctx self,
ctx: &'ctx mut PrepareContext,
) -> BoxFuture<'ctx, Result<(), BoxError>> {
self.inner.prepare(ctx)
}
pub(crate) fn ready<'ctx>(
&'ctx self,
ctx: &'ctx ReadyContext,
) -> BoxFuture<'ctx, Result<(), BoxError>> {
self.inner.ready(ctx)
}
pub(crate) fn shutdown<'ctx>(
&'ctx self,
ctx: &'ctx ShutdownContext,
) -> BoxFuture<'ctx, Result<(), BoxError>> {
self.inner.shutdown(ctx)
}
pub(crate) fn layers(&self, ctx: &LayerContext) -> Vec<TaggedLayer> {
self.inner.layers(ctx)
}
pub(crate) fn routes(&self, ctx: &RouteContext) -> Vec<TaggedRoute> {
self.inner.routes(ctx)
}
}
impl std::fmt::Debug for PluginHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("PluginHandle").field(&self.name()).finish()
}
}
pub type BoxPlugin = PluginHandle;