use anyhow::Context;
use std::collections::VecDeque;
use tokio::process::{Child, ChildStdin, ChildStdout};
use tokio_util::codec::{FramedRead, LinesCodec};
use tokio_util::sync::CancellationToken;
mod client_trait;
mod dispatch;
mod io;
mod process_impl;
mod spawn;
pub use client_trait::{InMemoryWireClient, WireClient};
pub use dispatch::{process_messages, WireMessage, WireResponse};
const LEGACY_NO_HANDSHAKE_PROTOCOL_VERSION: &str = "legacy/no-handshake";
pub(crate) const MAX_WIRE_LINE_LENGTH: usize = 16 * 1024 * 1024;
#[derive(Debug)]
pub struct ProcessWireClient {
pub(crate) child: Child,
pub(crate) stdin: ChildStdin,
pub(crate) stdout_reader: FramedRead<ChildStdout, LinesCodec>,
pub(crate) pending_messages: VecDeque<WireMessage>,
pub(crate) request_id_counter: u64,
pub(crate) handshake_done: bool,
pub(crate) stderr_handle: Option<tokio::task::JoinHandle<()>>,
pub(crate) cancel_token: CancellationToken,
}
impl Drop for ProcessWireClient {
fn drop(&mut self) {
self.cancel_token.cancel();
if let Some(handle) = self.stderr_handle.take() {
handle.abort();
}
}
}
#[cfg(test)]
mod tests;
pub(crate) fn wire_message_id(msg: &WireMessage) -> Option<&str> {
match msg {
WireMessage::SuccessResponse(resp) => Some(resp.id.as_str()),
WireMessage::ErrorResponse(resp) => Some(resp.id.as_str()),
WireMessage::Request(req) => Some(req.id.as_str()),
WireMessage::Event(_) => None,
}
}
pub(crate) fn decode_response<T: serde::de::DeserializeOwned>(
msg: WireMessage,
expected_id: &str,
) -> anyhow::Result<T> {
match msg {
WireMessage::SuccessResponse(resp) if resp.id == expected_id => {
serde_json::from_value(resp.result).context("Failed to decode response result")
}
WireMessage::ErrorResponse(resp) if resp.id == expected_id => bail_wire_error(resp),
other => anyhow::bail!(
"Buffered wire message did not match expected response id {expected_id}: {other:?}"
),
}
}
pub(crate) fn bail_wire_error<T>(
resp: crate::wire::protocol::JsonRpcErrorResponse,
) -> anyhow::Result<T> {
anyhow::bail!(
"Wire request failed: {} (code: {})",
resp.error.message,
resp.error.code
)
}