use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::Error;
use crate::command::{CommandRequest, CommandResult};
type BoxedDispatch = Pin<Box<dyn Future<Output = Result<CommandResult, Error>> + Send + 'static>>;
type BoxedShutdown = Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'static>>;
#[must_use = "dispatch futures do nothing unless awaited"]
pub(crate) struct DispatchFuture(BoxedDispatch);
impl DispatchFuture {
pub(crate) fn new(
future: impl Future<Output = Result<CommandResult, Error>> + Send + 'static,
) -> Self {
Self(Box::pin(future))
}
}
impl Future for DispatchFuture {
type Output = Result<CommandResult, Error>;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(context)
}
}
pub(crate) struct DispatchSpan {
#[cfg(feature = "tracing")]
span: tracing::Span,
}
impl DispatchSpan {
pub(crate) fn new(request: &CommandRequest, transport: &'static str) -> Self {
#[cfg(not(feature = "tracing"))]
let _ = (request, transport);
Self {
#[cfg(feature = "tracing")]
span: tracing::debug_span!(
"tmux_command",
request_id = request.request_id().get(),
subcommand = request.summary().subcommand(),
transport,
outcome = tracing::field::Empty,
error_kind = tracing::field::Empty,
),
}
}
#[cfg_attr(
not(feature = "tracing"),
allow(clippy::unused_self, reason = "the span is compiled out")
)]
pub(crate) fn run(
self,
future: impl Future<Output = Result<CommandResult, Error>> + Send + 'static,
) -> DispatchFuture {
#[cfg(feature = "tracing")]
{
use tracing::Instrument as _;
let mut outcome = OutcomeRecorder {
span: self.span.clone(),
recorded: false,
};
DispatchFuture::new(
async move {
let result = future.await;
outcome.record(&result);
result
}
.instrument(self.span),
)
}
#[cfg(not(feature = "tracing"))]
DispatchFuture::new(future)
}
}
#[cfg(feature = "tracing")]
struct OutcomeRecorder {
span: tracing::Span,
recorded: bool,
}
#[cfg(feature = "tracing")]
impl OutcomeRecorder {
fn record(&mut self, result: &Result<CommandResult, Error>) {
match result {
Ok(result) if result.success() => self.span.record("outcome", "success"),
Ok(_) => self.span.record("outcome", "exit"),
Err(error) => self
.span
.record("outcome", "error")
.record("error_kind", tracing::field::debug(error.kind())),
};
self.recorded = true;
}
}
#[cfg(feature = "tracing")]
impl Drop for OutcomeRecorder {
fn drop(&mut self) {
if !self.recorded {
self.span.record("outcome", "cancelled");
}
}
}
#[must_use = "shutdown futures do nothing unless awaited"]
pub(crate) struct ShutdownFuture(BoxedShutdown);
impl ShutdownFuture {
pub(crate) fn new(future: impl Future<Output = Result<(), Error>> + Send + 'static) -> Self {
Self(Box::pin(future))
}
}
impl Future for ShutdownFuture {
type Output = Result<(), Error>;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(context)
}
}
pub(crate) trait Executor: Send + Sync + 'static {
fn execute(&self, request: CommandRequest) -> DispatchFuture;
fn shutdown(&self) -> ShutdownFuture;
#[cfg(feature = "control-mode")]
fn renders_control_line(&self) -> bool {
false
}
}