use std::future::Future;
use std::pin::Pin;
use serde_json::Value;
use tokio::io::{AsyncBufReadExt, BufReader, Lines};
use crate::app_server_transport::{self, AppServerTransportError, write_json_line};
pub(super) type AppServerTransportFuture<'scope, T> =
Pin<Box<dyn Future<Output = T> + Send + 'scope>>;
#[cfg_attr(any(test, feature = "test-utils"), mockall::automock)]
pub(crate) trait AppServerRuntimeTransport: Send {
fn write_json_line(
&mut self,
payload: Value,
) -> AppServerTransportFuture<'_, Result<(), AppServerTransportError>>;
fn wait_for_response_line(
&mut self,
response_id: String,
) -> AppServerTransportFuture<'_, Result<String, AppServerTransportError>>;
fn next_stdout(
&mut self,
) -> AppServerTransportFuture<'_, Result<Option<String>, AppServerTransportError>>;
}
pub(super) struct AppServerStdioTransport {
read_stdout_context: &'static str,
stdin: Option<tokio::process::ChildStdin>,
stdin_unavailable_context: &'static str,
stdout_lines: Lines<BufReader<tokio::process::ChildStdout>>,
}
impl AppServerStdioTransport {
pub(super) fn new(
stdin: tokio::process::ChildStdin,
stdout: tokio::process::ChildStdout,
stdin_unavailable_context: &'static str,
read_stdout_context: &'static str,
) -> Self {
Self {
read_stdout_context,
stdin: Some(stdin),
stdin_unavailable_context,
stdout_lines: BufReader::new(stdout).lines(),
}
}
pub(super) fn close_stdin(&mut self) {
drop(self.stdin.take());
}
}
impl AppServerRuntimeTransport for AppServerStdioTransport {
fn write_json_line(
&mut self,
payload: Value,
) -> AppServerTransportFuture<'_, Result<(), AppServerTransportError>> {
let stdin_unavailable_context = self.stdin_unavailable_context;
Box::pin(async move {
let stdin = self
.stdin
.as_mut()
.ok_or_else(|| AppServerTransportError::Io {
context: stdin_unavailable_context.to_string(),
source: std::io::Error::new(std::io::ErrorKind::NotConnected, "stdin closed"),
})?;
write_json_line(stdin, &payload).await
})
}
fn wait_for_response_line(
&mut self,
response_id: String,
) -> AppServerTransportFuture<'_, Result<String, AppServerTransportError>> {
Box::pin(async move {
app_server_transport::wait_for_response_line(&mut self.stdout_lines, &response_id).await
})
}
fn next_stdout(
&mut self,
) -> AppServerTransportFuture<'_, Result<Option<String>, AppServerTransportError>> {
let read_stdout_context = self.read_stdout_context;
Box::pin(async move {
self.stdout_lines
.next_line()
.await
.map_err(|source| AppServerTransportError::Io {
context: read_stdout_context.to_string(),
source,
})
})
}
}