use std::sync::Arc;
use crate::{
Site,
callables::{self},
commands::{self},
embed, emitters,
services::{Agent, Service, ServiceBuildContext, ServiceHandler},
signals::{self, SignalConf},
};
use super::{Bundle, BundleError};
pub(super) enum BundlePartInner {
Route(axum::routing::MethodRouter<Site>, crate::callables::Operation),
Emitter(emitters::Emitter),
Task(crate::tasks::TaskService),
Signal(signals::Signaller),
Error(BundleError),
AssetDir(embed::Dir),
Command(commands::Command),
Service(ServiceHandler),
}
pub struct BundlePart {
pub(super) part: BundlePartInner,
pub(super) operation: Option<crate::callables::Operation>,
}
impl BundlePart {
pub fn patch(mut self, f: callables::PatchOp) -> Self {
if let Some(op) = &mut self.operation {
f.apply(op);
} else if let BundlePartInner::Route(_, ref mut op) = self.part {
f.apply(op);
}
self
}
}
impl Bundle {
pub(super) fn add_part(mut self, part: BundlePart) -> Self {
if !matches!(&part.part, BundlePartInner::Route(..)) {
if let Some(mut op) = part.operation {
op.bundle_id = Some(self.id);
self.ops.insert(op.id, op);
}
}
match part.part {
BundlePartInner::Route(router, mut op) => {
op.bundle_id = Some(self.id);
self = self.register_route(router, op);
}
BundlePartInner::Emitter(em) => {
if let Err(e) = self.emitters.register(em) {
self.errors.push(BundleError::Emitter(Arc::new(e)));
}
}
BundlePartInner::Signal(sig) => {
self.signals.register(sig);
}
BundlePartInner::Error(e) => {
self.errors.push(e);
}
BundlePartInner::AssetDir(d) => {
self.asset_dirs.push(d);
}
BundlePartInner::Service(entry) => {
if let Err(e) = self.services.register(entry) {
self.errors.push(BundleError::Service(Arc::new(e)));
}
}
BundlePartInner::Task(ts) => {
if let Err(e) = self.tasks.register(ts) {
self.errors.push(BundleError::Task(Arc::new(e)));
}
}
BundlePartInner::Command(cmd) => {
if let Err(e) = self.commands.register(cmd) {
self.errors.push(BundleError::Command(Arc::new(e)));
}
}
}
self
}
pub(super) fn register_route(
mut self,
router: axum::routing::MethodRouter<Site>,
op: crate::callables::Operation,
) -> Self {
self.inner_router = self.inner_router.route(op.path.as_ref(), router);
let id = op.id;
let name = op.name.clone();
self.ops.insert(id, op);
self.name_index.insert(name, id);
self
}
}
pub fn route<H, T, Args>(handler: H, meta: crate::routes::RouteConf) -> BundlePart
where
H: axum::handler::Handler<T, Site>
+ callables::Specable<Args>
+ Clone
+ Send
+ Sync
+ 'static,
T: 'static,
Args: callables::IntoArgSpecs + 'static,
{
let spec = callables::CallSpec::new(&handler);
let mut op =
crate::callables::Operation::from_specs(crate::callables::OperationKind::Route, &spec);
op.path = meta.path.clone().into();
op.name = meta.name.clone().into();
op.methods = meta.methods.clone().into();
op = op.with_conf(&meta);
let router = axum::routing::on(meta.methods.into(), handler);
BundlePart {
operation: None,
part: BundlePartInner::Route(router, op),
}
}
pub fn cron<O, H, Args>(handler: H, options: emitters::CronConf) -> BundlePart
where
O: callables::Payloadable,
Args: callables::FromContext<emitters::EmitterContext>
+ callables::IntoArgSpecs
+ Send
+ 'static,
H: callables::Specable<Args, Output = callables::Payload<O>> + Send + Sync + 'static,
{
match emitters::cron(handler, options) {
Ok(em) => BundlePart {
operation: Some(em.operation()),
part: BundlePartInner::Emitter(em),
},
Err(e) => BundlePart {
operation: None,
part: BundlePartInner::Error(BundleError::Emitter(Arc::new(e))),
},
}
}
pub fn periodic<O, H, Args>(handler: H, options: emitters::PeriodicConf) -> BundlePart
where
O: callables::Payloadable,
Args: callables::FromContext<emitters::EmitterContext>
+ callables::IntoArgSpecs
+ Send
+ 'static,
H: callables::Specable<Args, Output = callables::Payload<O>> + Send + Sync + 'static,
{
match emitters::periodic(handler, options) {
Ok(em) => BundlePart {
operation: Some(em.operation()),
part: BundlePartInner::Emitter(em),
},
Err(e) => BundlePart {
operation: None,
part: BundlePartInner::Error(BundleError::Emitter(Arc::new(e))),
},
}
}
pub fn pgnotify<O, H, Args>(handler: H, options: emitters::PgNotifyConf) -> BundlePart
where
O: callables::Payloadable,
Args: callables::FromContext<emitters::EmitterContext>
+ callables::IntoArgSpecs
+ Send
+ 'static,
H: callables::Specable<Args, Output = callables::Payload<O>> + Send + Sync + 'static,
{
match emitters::pgnotify(handler, options) {
Ok(em) => BundlePart {
operation: Some(em.operation()),
part: BundlePartInner::Emitter(em),
},
Err(e) => BundlePart {
operation: None,
part: BundlePartInner::Error(BundleError::Emitter(Arc::new(e))),
},
}
}
pub fn signal<T, H, Args>(handler: H, options: SignalConf) -> BundlePart
where
T: callables::Payloadable,
H: callables::Specable<Args, Output = ()> + Send + Sync + 'static,
Args: callables::FromContext<signals::SignalContext>
+ callables::IntoArgSpecs
+ callables::HasPayload<T>
+ Send
+ 'static,
{
let sig = crate::signals::signal::<T, H, Args>(handler, options);
let op = sig.operation();
BundlePart {
operation: Some(op),
part: BundlePartInner::Signal(sig),
}
}
pub fn service<T, H, Args>(handler: H) -> BundlePart
where
T: Service,
H: callables::Specable<Args, Output = Agent<T>> + Send + Sync + 'static,
Args: callables::FromContext<ServiceBuildContext> + callables::IntoArgSpecs + Send + 'static,
{
let entry = ServiceHandler::new(handler);
let op = entry.operation();
BundlePart {
part: BundlePartInner::Service(entry),
operation: Some(op),
}
}
pub fn command<T, H, Args>(handler: H, conf: commands::CommandConf) -> BundlePart
where
T: callables::Payloadable,
H: callables::Specable<Args, Output = Result<(), commands::CommandError>>
+ Send
+ Sync
+ 'static,
Args: callables::FromContext<commands::CommandContext>
+ callables::IntoArgSpecs
+ callables::HasPayload<T>
+ Send
+ 'static,
{
let cmd = commands::command(handler, conf);
let op = cmd.operation();
BundlePart {
part: BundlePartInner::Command(cmd),
operation: Some(op),
}
}
pub fn asset_dir(dir: embed::Dir) -> BundlePart {
BundlePart {
operation: None,
part: BundlePartInner::AssetDir(dir),
}
}
pub fn bundle(parts: impl IntoIterator<Item = BundlePart>) -> Bundle {
parts.into_iter().fold(Bundle::new(), Bundle::add_part)
}