use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::{
command::CommandError, core::capability::RequestScoped, query::QueryParams,
request::RequestContext, server::MykoServerContext,
};
#[derive(Clone)]
pub struct CommandContext {
pub req: Arc<RequestContext>,
pub command_id: Arc<str>,
server_ctx: Arc<MykoServerContext>,
}
impl CommandContext {
#[must_use]
pub const fn new(
command_id: Arc<str>,
req: Arc<RequestContext>,
server_ctx: Arc<MykoServerContext>,
) -> Self {
Self {
req,
command_id,
server_ctx,
}
}
#[must_use]
pub fn created_at(&self) -> &str {
&self.req.created_at
}
pub fn exec_query_first<Q>(&self, query: Q) -> Result<Option<Arc<Q::Item>>, CommandError>
where
Q: QueryParams,
Q::Item: DeserializeOwned + std::fmt::Debug + Send + Sync + Clone + 'static,
{
{
Ok(self
.server_ctx
.query_snapshot(query, self.req.clone())
.into_iter()
.next())
}
}
pub fn exec_query<Q>(&self, query: Q) -> Result<Vec<Arc<Q::Item>>, CommandError>
where
Q: QueryParams,
Q::Item: DeserializeOwned + std::fmt::Debug + Send + Sync + Clone + 'static,
{
{
Ok(self
.server_ctx
.query_snapshot(query, self.req.clone())
.into_iter()
.collect())
}
}
pub fn exec_report<R>(
&self,
report: R,
) -> Result<<R as crate::report::ReportHandler>::Output, CommandError>
where
R: crate::report::ReportParams + Clone,
{
{
use hyphae::Gettable;
Ok(self
.server_ctx
.report(report, self.req.clone())
.get()
.as_ref()
.clone())
}
}
}
impl crate::core::capability::sealed::Sealed for CommandContext {}
impl crate::core::capability::RequestScoped for CommandContext {
fn __request(&self) -> &Arc<RequestContext> {
&self.req
}
}
impl crate::core::capability::ServerScoped for CommandContext {
fn __server_ctx(&self) -> &Arc<MykoServerContext> {
&self.server_ctx
}
}
impl crate::core::capability::EventPublishing for CommandContext {
fn __command_id(&self) -> &Arc<str> {
&self.command_id
}
}
impl crate::core::capability::CommandSending for CommandContext {
fn __command_ctx(&self) -> CommandContext {
self.clone()
}
}
pub trait CommandHandler: crate::command::CommandParams {
#[allow(clippy::unreachable)]
fn execute(self, _ctx: CommandContext) -> Result<Self::Result, CommandError> {
unreachable!("command handlers execute on the server")
}
}
pub trait DynCommandExecutor: Send + Sync + 'static {
fn command_id(&self) -> &'static str;
fn execute_from_value(
&self,
command: Value,
ctx: CommandContext,
) -> Result<Value, CommandError>;
}
pub struct CommandExecutorAdapter<C: CommandHandler> {
_phantom: std::marker::PhantomData<C>,
}
impl<C: CommandHandler> CommandExecutorAdapter<C> {
#[must_use]
pub const fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
impl<C: CommandHandler> Default for CommandExecutorAdapter<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: CommandHandler> DynCommandExecutor for CommandExecutorAdapter<C> {
fn command_id(&self) -> &'static str {
C::command_id_static()
}
fn execute_from_value(
&self,
mut command: Value,
ctx: CommandContext,
) -> Result<Value, CommandError> {
if let Some(params) = command.as_object_mut() {
params.remove("tx");
}
let cmd: C = serde_json::from_value(command).map_err(|e| {
CommandError::new(
ctx.tx(),
C::command_id_static(),
format!("Failed to deserialize command: {e}"),
)
})?;
let _span = tracing::trace_span!("myko.command", cmd = C::command_id_static()).entered();
crate::server::dispatch_metrics::record_command(C::command_id_static(), "external");
let result = cmd.execute(ctx)?;
serde_json::to_value(result).map_err(|e| {
CommandError::new(
String::new(),
C::command_id_static(),
format!("Failed to serialize result: {e}"),
)
})
}
}
pub type CommandExecutorFactory = fn() -> Box<dyn DynCommandExecutor>;
pub struct CommandHandlerRegistration {
pub command_id: &'static str,
pub factory: CommandExecutorFactory,
}
inventory::collect!(CommandHandlerRegistration);
#[macro_export]
macro_rules! register_command_handler {
($cmd:ty) => {
$crate::inventory::submit! {
$crate::command::CommandHandlerRegistration {
command_id: <$cmd as $crate::command::CommandIdStatic>::COMMAND_ID,
factory: || Box::new($crate::command::CommandExecutorAdapter::<$cmd>::new()),
}
}
};
}