use std::fmt;
use futures::future::BoxFuture;
use crate::http::Response;
use crate::core::engine::{FrozenDiContainer, HttpMethod};
use crate::web::context::RequestContext;
#[derive(Debug)]
pub struct PluginError {
pub plugin: &'static str,
pub stage: PluginStage,
pub source: Box<dyn std::error::Error + Send + Sync>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginStage {
Init,
Start,
Shutdown,
}
impl PluginError {
pub fn new<E: Into<Box<dyn std::error::Error + Send + Sync>>>(
plugin: &'static str,
stage: PluginStage,
source: E,
) -> Self {
Self {
plugin,
stage,
source: source.into(),
}
}
}
impl fmt::Display for PluginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"plugin `{}` failed at {:?}: {}",
self.plugin, self.stage, self.source
)
}
}
impl std::error::Error for PluginError {}
pub trait ArclyPlugin: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn on_init<'a>(
&'a mut self,
ctx: &'a mut ArclyPluginContext,
) -> BoxFuture<'a, Result<(), PluginError>> {
let _ = ctx;
Box::pin(async { Ok(()) })
}
fn on_draining<'a>(
&'a self,
container: &'static FrozenDiContainer,
) -> BoxFuture<'a, Result<(), PluginError>> {
let _ = container;
Box::pin(async { Ok(()) })
}
fn on_start<'a>(
&'a self,
container: &'static FrozenDiContainer,
) -> BoxFuture<'a, Result<(), PluginError>> {
let _ = container;
Box::pin(async { Ok(()) })
}
fn on_shutdown<'a>(
&'a self,
container: &'static FrozenDiContainer,
) -> BoxFuture<'a, Result<(), PluginError>> {
let _ = container;
Box::pin(async { Ok(()) })
}
}
pub type PluginHandler =
std::sync::Arc<dyn Fn(RequestContext) -> BoxFuture<'static, Response> + Send + Sync>;
#[doc(hidden)]
pub struct PluginRoute {
pub method: HttpMethod,
pub path: &'static str,
pub handler: PluginHandler,
pub plugin: &'static str,
}
pub(crate) type OpenApiMutator = Box<dyn FnOnce(&mut serde_json::Value) + Send + Sync>;
pub(crate) type PendingProvider =
Box<dyn FnOnce(&mut crate::core::engine::DiContainerBuilder) + Send>;
pub struct ArclyPluginContext {
pub(crate) extra_routes: Vec<PluginRoute>,
pub(crate) openapi_mutators: Vec<OpenApiMutator>,
pub(crate) global_interceptors: Vec<&'static dyn crate::web::interceptors::Interceptor>,
pub(crate) boundary_filters: Vec<&'static dyn crate::web::boundary::BoundaryFilter>,
pub(crate) pending_providers: Vec<PendingProvider>,
pub(crate) current_plugin: &'static str,
}
impl ArclyPluginContext {
#[doc(hidden)]
pub fn new() -> Self {
Self {
extra_routes: Vec::new(),
openapi_mutators: Vec::new(),
global_interceptors: Vec::new(),
boundary_filters: Vec::new(),
pending_providers: Vec::new(),
current_plugin: "ArclyPluginContext",
}
}
pub fn provide<T: Send + Sync + 'static>(&mut self, value: T) {
self.pending_providers.push(Box::new(move |b| {
b.register(value);
}));
}
pub fn override_provider<T: Send + Sync + 'static>(&mut self, value: T) {
self.pending_providers.push(Box::new(move |b| {
b.register_override(value);
}));
}
pub fn add_interceptor(&mut self, ic: impl crate::web::interceptors::Interceptor) {
self.global_interceptors.push(Box::leak(Box::new(ic)));
}
pub fn add_boundary_filter(&mut self, f: impl crate::web::boundary::BoundaryFilter) {
self.boundary_filters.push(Box::leak(Box::new(f)));
}
pub fn register_boundary_filter(
&mut self,
f: &'static dyn crate::web::boundary::BoundaryFilter,
) {
self.boundary_filters.push(f);
}
pub fn add_route<F, Fut>(&mut self, method: HttpMethod, path: impl Into<String>, handler: F)
where
F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Response> + Send + 'static,
{
let arc: PluginHandler = std::sync::Arc::new(move |ctx| Box::pin(handler(ctx)));
self.extra_routes.push(PluginRoute {
method,
path: Box::leak(path.into().into_boxed_str()),
handler: arc,
plugin: self.current_plugin,
});
}
pub fn add_get<F, Fut>(&mut self, path: impl Into<String>, handler: F)
where
F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Response> + Send + 'static,
{
self.add_route(HttpMethod::GET, path, handler);
}
pub fn modify_openapi<F>(&mut self, f: F)
where
F: FnOnce(&mut serde_json::Value) + Send + Sync + 'static,
{
self.openapi_mutators.push(Box::new(f));
}
pub fn require_env(&self, key: &str) -> Result<String, PluginError> {
std::env::var(key).map_err(|_| {
PluginError::new(
self.current_plugin,
PluginStage::Init,
format!("required env var `{key}` is missing or not valid UTF-8"),
)
})
}
pub fn env_or(&self, key: &str, default: impl Into<String>) -> String {
std::env::var(key).unwrap_or_else(|_| default.into())
}
pub fn register_global_interceptor(
&mut self,
ic: &'static dyn crate::web::interceptors::Interceptor,
) {
self.global_interceptors.push(ic);
}
}
impl Default for ArclyPluginContext {
fn default() -> Self {
Self::new()
}
}
pub use crate::web::plugin_routes::build_plugin_route;