use agent_client_protocol_schema::v1::{JsonRpcMessage as VersionedJsonRpcMessage, SessionId};
use serde::{Deserialize, Serialize};
use std::any::TypeId;
use std::collections::HashMap;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::panic::Location;
use std::pin::pin;
use std::sync::{
Arc, Mutex, Weak,
atomic::{AtomicBool, AtomicU8, Ordering},
};
use uuid::Uuid;
use futures::FutureExt;
use futures::channel::{mpsc, oneshot};
use futures::future::{self, BoxFuture, Either};
use futures::{AsyncRead, AsyncWrite, StreamExt};
pub(crate) mod close;
mod dynamic_handler;
pub(crate) mod handlers;
mod incoming_actor;
mod outgoing_actor;
#[cfg(test)]
mod prepared_request_tests;
pub(crate) mod protocol;
mod protocol_compat;
mod raw_error;
pub(crate) mod raw_id;
pub(crate) mod run;
mod task_actor;
mod termination;
mod transport_actor;
pub(crate) mod v1_connection;
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) mod v2_connection;
use crate::jsonrpc::close::{ChainedClose, CloseCallback};
pub use crate::jsonrpc::close::{HandleConnectionClose, NullClose};
use crate::jsonrpc::dynamic_handler::DynamicHandlerMessage;
pub use crate::jsonrpc::handlers::NullHandler;
use crate::jsonrpc::handlers::{ChainedHandler, NamedHandler};
use crate::jsonrpc::handlers::{MessageHandler, NotificationHandler, RequestHandler};
use crate::jsonrpc::outgoing_actor::{OutgoingMessageTx, send_raw_message};
use crate::jsonrpc::protocol::HandleId;
#[cfg(feature = "unstable_protocol_v2")]
pub use crate::jsonrpc::protocol::V2;
#[cfg(feature = "unstable_mcp_over_acp")]
use crate::Client;
pub use crate::jsonrpc::protocol::{Protocol, Raw, V1};
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) use crate::jsonrpc::protocol_compat::normalize_newer_v2_initialize_request;
pub(crate) use crate::jsonrpc::protocol_compat::normalize_v1_initialize_request;
#[cfg(feature = "unstable_protocol_v2")]
pub use crate::jsonrpc::protocol_compat::{
InitializationVersionMismatch, InitializeOutcome, SelectedInitializeResponse,
};
use crate::jsonrpc::protocol_compat::{ProtocolCompat, ProtocolMode};
pub use crate::jsonrpc::raw_error::{RawJsonRpcError, RawJsonRpcResponse};
pub use crate::jsonrpc::raw_id::RawJsonRpcId;
use crate::jsonrpc::run::SpawnedRun;
use crate::jsonrpc::run::{ChainRun, NullRun, RunWithConnectionTo};
use crate::jsonrpc::task_actor::{Task, TaskTx};
pub use crate::jsonrpc::termination::{
CONNECTION_TERMINATED_REASON, ConnectionTermination, is_connection_terminated,
};
use crate::jsonrpc::termination::{
TerminationSource, TerminationSources, connection_terminated_error,
};
#[cfg(feature = "unstable_mcp_over_acp")]
use crate::mcp_server::McpServer;
use crate::role::HasPeer;
use crate::role::Role;
use crate::{Agent, ConnectTo, RoleId};
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(
clippy::exhaustive_enums,
reason = "JSON-RPC defines exactly these message kinds"
)]
pub enum RawJsonRpcMessage {
Request(RawJsonRpcRequest),
Notification(RawJsonRpcNotification),
Response(RawJsonRpcResponse),
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum TransportFrame {
Single(RawJsonRpcMessage),
Malformed {
raw: String,
error: crate::Error,
},
}
pub(crate) fn is_response_only_shape(value: &serde_json::Value) -> bool {
value.as_object().is_some_and(|object| {
!object.contains_key("method")
&& (object.contains_key("result") || object.contains_key("error"))
})
}
pub(crate) fn raw_is_response_only_shape(raw: &str) -> bool {
serde_json::from_str(raw).is_ok_and(|value| is_response_only_shape(&value))
}
impl TransportFrame {
fn inspect_messages(
&self,
observer: &mut impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error>,
) -> Result<(), crate::Error> {
match self {
Self::Single(message) => observer(message),
Self::Malformed { .. } => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(
clippy::exhaustive_structs,
reason = "JSON-RPC defines exactly these request members"
)]
pub struct RawJsonRpcRequest {
pub id: RawJsonRpcId,
pub method: Arc<str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<RawJsonRpcParams>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(
clippy::exhaustive_structs,
reason = "JSON-RPC defines exactly these notification members"
)]
pub struct RawJsonRpcNotification {
pub method: Arc<str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<RawJsonRpcParams>,
}
impl From<RawJsonRpcRequest> for RawJsonRpcMessage {
fn from(request: RawJsonRpcRequest) -> Self {
Self::Request(request)
}
}
impl From<RawJsonRpcNotification> for RawJsonRpcMessage {
fn from(notification: RawJsonRpcNotification) -> Self {
Self::Notification(notification)
}
}
impl From<RawJsonRpcResponse> for RawJsonRpcMessage {
fn from(response: RawJsonRpcResponse) -> Self {
Self::Response(response)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(
clippy::exhaustive_enums,
reason = "JSON-RPC params are either an array or an object"
)]
pub enum RawJsonRpcParams {
Array(Vec<serde_json::Value>),
Object(serde_json::Map<String, serde_json::Value>),
}
impl RawJsonRpcParams {
pub fn from_value(value: serde_json::Value) -> Result<Option<Self>, crate::Error> {
match value {
serde_json::Value::Null => Ok(None),
serde_json::Value::Array(array) => Ok(Some(Self::Array(array))),
serde_json::Value::Object(object) => Ok(Some(Self::Object(object))),
_ => {
Err(crate::Error::invalid_params()
.data("JSON-RPC params must be an object or array"))
}
}
}
#[must_use]
pub fn into_value(self) -> serde_json::Value {
match self {
Self::Array(array) => serde_json::Value::Array(array),
Self::Object(object) => serde_json::Value::Object(object),
}
}
}
impl Serialize for RawJsonRpcParams {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Array(array) => array.serialize(serializer),
Self::Object(object) => object.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for RawJsonRpcParams {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::Array(array) => Ok(Self::Array(array)),
serde_json::Value::Object(object) => Ok(Self::Object(object)),
_ => Err(serde::de::Error::custom(
"JSON-RPC params must be an object or array",
)),
}
}
}
impl RawJsonRpcMessage {
pub fn request(
method: String,
params: serde_json::Value,
id: impl Into<RawJsonRpcId>,
) -> Result<Self, crate::Error> {
Ok(Self::Request(RawJsonRpcRequest {
id: id.into(),
method: Arc::from(method),
params: RawJsonRpcParams::from_value(params)?,
}))
}
pub fn notification(method: String, params: serde_json::Value) -> Result<Self, crate::Error> {
Ok(Self::Notification(RawJsonRpcNotification {
method: Arc::from(method),
params: RawJsonRpcParams::from_value(params)?,
}))
}
#[must_use]
pub fn response(
id: impl Into<RawJsonRpcId>,
response: Result<serde_json::Value, crate::Error>,
) -> Self {
Self::Response(RawJsonRpcResponse::new(
id,
response.map_err(|error| Box::new(error.into())),
))
}
#[must_use]
pub fn response_id(&self) -> Option<&RawJsonRpcId> {
match self {
Self::Response(
RawJsonRpcResponse::Result { id, .. } | RawJsonRpcResponse::Error { id, .. },
) => Some(id),
Self::Request(_) | Self::Notification(_) => None,
}
}
}
impl Serialize for RawJsonRpcMessage {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Request(request) => {
VersionedJsonRpcMessage::wrap(request.clone()).serialize(serializer)
}
Self::Notification(notification) => {
VersionedJsonRpcMessage::wrap(notification.clone()).serialize(serializer)
}
Self::Response(response) => {
VersionedJsonRpcMessage::wrap(response.clone()).serialize(serializer)
}
}
}
}
impl<'de> Deserialize<'de> for RawJsonRpcMessage {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let Some(object) = value.as_object() else {
return Err(serde::de::Error::custom("invalid JSON-RPC message"));
};
let has_method = object.contains_key("method");
let has_id = object.contains_key("id");
let has_result = object.contains_key("result");
let has_error = object.contains_key("error");
if has_method && !has_result && !has_error {
if has_id {
let request =
serde_json::from_value::<VersionedJsonRpcMessage<RawJsonRpcRequest>>(value)
.map_err(serde::de::Error::custom)?
.into_inner();
Ok(Self::Request(request))
} else {
let notification = serde_json::from_value::<
VersionedJsonRpcMessage<RawJsonRpcNotification>,
>(value)
.map_err(serde::de::Error::custom)?
.into_inner();
Ok(Self::Notification(notification))
}
} else if !has_method && has_id && has_result != has_error {
let response =
serde_json::from_value::<VersionedJsonRpcMessage<RawJsonRpcResponse>>(value)
.map_err(serde::de::Error::custom)?
.into_inner();
Ok(Self::Response(response))
} else {
Err(serde::de::Error::custom("invalid JSON-RPC message"))
}
}
}
fn params_from_transport(params: Option<RawJsonRpcParams>) -> serde_json::Value {
params.map_or(serde_json::Value::Null, RawJsonRpcParams::into_value)
}
#[allow(async_fn_in_trait)]
pub trait HandleDispatchFrom<Counterpart: Role>: Send {
fn handle_dispatch_from(
&mut self,
message: Dispatch,
connection: ConnectionTo<Counterpart>,
) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send;
fn describe_chain(&self) -> impl std::fmt::Debug;
}
impl<Counterpart: Role, H> HandleDispatchFrom<Counterpart> for &mut H
where
H: HandleDispatchFrom<Counterpart>,
{
fn handle_dispatch_from(
&mut self,
message: Dispatch,
cx: ConnectionTo<Counterpart>,
) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
H::handle_dispatch_from(self, message, cx)
}
fn describe_chain(&self) -> impl std::fmt::Debug {
H::describe_chain(self)
}
}
#[must_use]
#[derive(Debug)]
pub struct Builder<Host: Role, Handler = NullHandler, Runner = NullRun, Close = NullClose, P = Raw>
where
Handler: HandleDispatchFrom<Host::Counterpart>,
Runner: RunWithConnectionTo<Host::Counterpart>,
Close: HandleConnectionClose<Host::Counterpart>,
P: Protocol,
{
host: Host,
name: Option<String>,
handler: Handler,
runner: Runner,
protocol_mode: ProtocolMode,
on_close: Close,
protocol: PhantomData<fn() -> P>,
}
impl<Host: Role> Builder<Host, NullHandler, NullRun, NullClose> {
pub fn new(role: Host) -> Self {
Self {
host: role,
name: None,
handler: NullHandler,
runner: NullRun,
protocol_mode: ProtocolMode::disabled(),
on_close: NullClose,
protocol: PhantomData,
}
}
}
impl<Host: Role, Handler> Builder<Host, Handler, NullRun, NullClose>
where
Handler: HandleDispatchFrom<Host::Counterpart>,
{
pub fn new_with(role: Host, handler: Handler) -> Self {
Self {
host: role,
name: None,
handler,
runner: NullRun,
protocol_mode: ProtocolMode::disabled(),
on_close: NullClose,
protocol: PhantomData,
}
}
}
impl<
Host: Role,
Handler: HandleDispatchFrom<Host::Counterpart>,
Runner: RunWithConnectionTo<Host::Counterpart>,
Close: HandleConnectionClose<Host::Counterpart>,
P: Protocol,
> Builder<Host, Handler, Runner, Close, P>
{
pub fn name(mut self, name: impl ToString) -> Self {
self.name = Some(name.to_string());
self
}
pub(crate) fn into_protocol<Q: Protocol>(self) -> Builder<Host, Handler, Runner, Close, Q> {
Builder {
host: self.host,
name: self.name,
handler: self.handler,
runner: self.runner,
protocol_mode: self.protocol_mode,
on_close: self.on_close,
protocol: PhantomData,
}
}
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) fn v2_agent(self) -> Builder<Host, Handler, Runner, Close, V2> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v2_agent();
builder
}
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) fn v2_client(self) -> Builder<Host, Handler, Runner, Close, V2> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v2_client();
builder
}
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) fn v2_proxy(self) -> Builder<Host, Handler, Runner, Close, V2> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v2_proxy();
builder
}
pub(crate) fn v1_agent(self) -> Builder<Host, Handler, Runner, Close, V1> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v1_agent();
builder
}
pub(crate) fn v1_client(self) -> Builder<Host, Handler, Runner, Close, V1> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v1_client();
builder
}
pub(crate) fn v1_proxy(self) -> Builder<Host, Handler, Runner, Close, V1> {
let mut builder = self.into_protocol();
builder.protocol_mode = ProtocolMode::v1_proxy();
builder
}
fn note_request_handler<Req: JsonRpcRequest>(mut self) -> Self {
const UNRELATED_METHOD: &str = "\0acp-sdk/not-a-method";
if TypeId::of::<Host>() == TypeId::of::<Agent>()
&& Req::matches_method("initialize")
&& !Req::matches_method(UNRELATED_METHOD)
{
self.protocol_mode = self.protocol_mode.require_initialize();
}
self
}
pub fn with_connection_builder(
self,
other: Builder<
Host,
impl HandleDispatchFrom<Host::Counterpart>,
impl RunWithConnectionTo<Host::Counterpart>,
impl HandleConnectionClose<Host::Counterpart>,
P,
>,
) -> Builder<
Host,
impl HandleDispatchFrom<Host::Counterpart>,
impl RunWithConnectionTo<Host::Counterpart>,
impl HandleConnectionClose<Host::Counterpart>,
P,
> {
let Builder {
name: other_name,
handler: other_handler,
runner: other_runner,
protocol_mode: other_protocol_mode,
on_close: other_on_close,
protocol: _,
host: _,
} = other;
Builder {
host: self.host,
name: self.name,
handler: ChainedHandler::new(
self.handler,
NamedHandler::new(other_name, other_handler),
),
runner: ChainRun::new(self.runner, other_runner),
protocol_mode: self.protocol_mode.merge(other_protocol_mode),
on_close: ChainedClose::new(self.on_close, other_on_close),
protocol: PhantomData,
}
}
pub fn with_handler(
self,
handler: impl HandleDispatchFrom<Host::Counterpart>,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P> {
Builder {
host: self.host,
name: self.name,
handler: ChainedHandler::new(self.handler, handler),
runner: self.runner,
protocol_mode: self.protocol_mode,
on_close: self.on_close,
protocol: PhantomData,
}
}
pub fn with_runner<Run1>(
self,
runner: Run1,
) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, P>
where
Run1: RunWithConnectionTo<Host::Counterpart>,
{
Builder {
host: self.host,
name: self.name,
handler: self.handler,
runner: ChainRun::new(self.runner, runner),
protocol_mode: self.protocol_mode,
on_close: self.on_close,
protocol: PhantomData,
}
}
#[track_caller]
pub fn with_spawned<F, Fut>(
self,
task: F,
) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, P>
where
F: FnOnce(P::Connection<Host::Counterpart>) -> Fut + Send,
Fut: Future<Output = Result<(), crate::Error>> + Send,
{
let location = Location::caller();
self.with_runner(SpawnedRun::<_, P>::new(location, task))
}
pub fn on_close<F, Fut>(
self,
callback: F,
) -> Builder<Host, Handler, Runner, impl HandleConnectionClose<Host::Counterpart>, P>
where
F: FnOnce(P::Connection<Host::Counterpart>) -> Fut + Send,
Fut: Future<Output = Result<(), crate::Error>> + Send,
{
Builder {
host: self.host,
name: self.name,
handler: self.handler,
runner: self.runner,
protocol_mode: self.protocol_mode,
on_close: ChainedClose::new(self.on_close, CloseCallback::<_, P>::new(callback)),
protocol: PhantomData,
}
}
pub fn on_receive_dispatch<Req, Notif, F, T, ToFut>(
self,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Host::Counterpart>,
Req: JsonRpcRequest,
Notif: JsonRpcNotification,
F: AsyncFnMut(
Dispatch<Req, Notif, P>,
P::Connection<Host::Counterpart>,
) -> Result<T, crate::Error>
+ Send,
T: IntoHandled<Dispatch<Req, Notif, P>>,
ToFut: Fn(
&mut F,
Dispatch<Req, Notif, P>,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = MessageHandler::<_, _, P, _, _, _, _>::new(
self.host.counterpart(),
self.host.counterpart(),
op,
to_future_hack,
);
self.note_request_handler::<Req>().with_handler(handler)
}
pub fn on_receive_request<Req: JsonRpcRequest, F, T, ToFut>(
self,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Host::Counterpart>,
F: AsyncFnMut(
Req,
Responder<Req::Response, P>,
P::Connection<Host::Counterpart>,
) -> Result<T, crate::Error>
+ Send,
T: IntoHandled<(Req, Responder<Req::Response, P>)>,
ToFut: Fn(
&mut F,
Req,
Responder<Req::Response, P>,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = RequestHandler::<_, _, P, _, _, _>::new(
self.host.counterpart(),
self.host.counterpart(),
op,
to_future_hack,
);
self.note_request_handler::<Req>().with_handler(handler)
}
pub fn on_receive_notification<Notif, F, T, ToFut>(
self,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Host::Counterpart>,
Notif: JsonRpcNotification,
F: AsyncFnMut(Notif, P::Connection<Host::Counterpart>) -> Result<T, crate::Error> + Send,
T: IntoHandled<(Notif, P::Connection<Host::Counterpart>)>,
ToFut: Fn(
&mut F,
Notif,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = NotificationHandler::<_, _, P, _, _, _>::new(
self.host.counterpart(),
self.host.counterpart(),
op,
to_future_hack,
);
self.with_handler(handler)
}
pub fn on_receive_dispatch_from<
Req: JsonRpcRequest,
Notif: JsonRpcNotification,
Peer: Role,
F,
T,
ToFut,
>(
self,
peer: Peer,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Peer>,
F: AsyncFnMut(
Dispatch<Req, Notif, P>,
P::Connection<Host::Counterpart>,
) -> Result<T, crate::Error>
+ Send,
T: IntoHandled<Dispatch<Req, Notif, P>>,
ToFut: Fn(
&mut F,
Dispatch<Req, Notif, P>,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = MessageHandler::<_, _, P, _, _, _, _>::new(
self.host.counterpart(),
peer,
op,
to_future_hack,
);
self.note_request_handler::<Req>().with_handler(handler)
}
pub fn on_receive_request_from<Req: JsonRpcRequest, Peer: Role, F, T, ToFut>(
self,
peer: Peer,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Peer>,
F: AsyncFnMut(
Req,
Responder<Req::Response, P>,
P::Connection<Host::Counterpart>,
) -> Result<T, crate::Error>
+ Send,
T: IntoHandled<(Req, Responder<Req::Response, P>)>,
ToFut: Fn(
&mut F,
Req,
Responder<Req::Response, P>,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = RequestHandler::<_, _, P, _, _, _>::new(
self.host.counterpart(),
peer,
op,
to_future_hack,
);
self.note_request_handler::<Req>().with_handler(handler)
}
pub fn on_receive_notification_from<Notif: JsonRpcNotification, Peer: Role, F, T, ToFut>(
self,
peer: Peer,
op: F,
to_future_hack: ToFut,
) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, P>
where
Host::Counterpart: HasPeer<Peer>,
F: AsyncFnMut(Notif, P::Connection<Host::Counterpart>) -> Result<T, crate::Error> + Send,
T: IntoHandled<(Notif, P::Connection<Host::Counterpart>)>,
ToFut: Fn(
&mut F,
Notif,
P::Connection<Host::Counterpart>,
) -> crate::BoxFuture<'_, Result<T, crate::Error>>
+ Send
+ Sync,
{
let handler = NotificationHandler::<_, _, P, _, _, _>::new(
self.host.counterpart(),
peer,
op,
to_future_hack,
);
self.with_handler(handler)
}
pub async fn connect_to(
self,
transport: impl ConnectTo<Host> + 'static,
) -> Result<(), crate::Error> {
let (_, future) = self.into_connection_and_future(transport, true, async move |cx| {
cx.incoming_closed().await;
Ok(())
});
future.await
}
pub async fn connect_with<R>(
self,
transport: impl ConnectTo<Host> + 'static,
main_fn: impl AsyncFnOnce(P::Connection<Host::Counterpart>) -> Result<R, crate::Error>,
) -> Result<R, crate::Error> {
let (_, future) =
self.into_connection_and_future(transport, false, async move |connection| {
main_fn(protocol::connection_from_raw::<P, _>(connection)).await
});
future.await
}
fn into_connection_and_future<R>(
self,
transport: impl ConnectTo<Host> + 'static,
wait_owned_transport: bool,
main_fn: impl AsyncFnOnce(ConnectionTo<Host::Counterpart>) -> Result<R, crate::Error>,
) -> (
ConnectionTo<Host::Counterpart>,
impl Future<Output = Result<R, crate::Error>>,
) {
let Self {
name,
handler,
runner,
host: me,
protocol_mode,
on_close,
protocol: _,
} = self;
let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
let (new_task_tx, new_task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
let (foreground_succeeded_tx, foreground_succeeded) = completion_signal();
let (foreground_done_tx, foreground_done) = completion_signal();
let pending_replies = PendingReplies::default();
let termination_sources = TerminationSources::default();
let transport_component = crate::DynConnectTo::new(transport);
let (transport_channel, mut transport_future) =
transport_component.into_channel_and_future();
let owned_transport = transport_future.is_some();
let transport_finish = transport_future
.as_mut()
.and_then(crate::ConnectionDriver::take_finish);
let (transport_completion_tx, transport_completion_rx) = oneshot::channel();
let transport_completion = transport_completion_rx
.map(|result| {
result.unwrap_or_else(|error| {
Err(crate::util::internal_error(format!(
"transport task dropped before reporting completion: {error}"
)))
})
})
.boxed()
.shared();
let connection = ConnectionTo::new(
me.counterpart(),
outgoing_tx,
new_task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
protocol_mode,
);
let transport_driver = if let Some(driver) = transport_future {
let termination_sources = termination_sources.clone();
async move {
let result = driver.await;
termination_sources.record(TerminationSource::Transport, &result);
drop(transport_completion_tx.send(result.clone()));
result
}
.boxed()
} else {
drop(transport_completion_tx.send(Ok(())));
future::ready(Ok(())).boxed()
};
let Channel {
rx: mut transport_incoming_rx,
tx: transport_outgoing_tx,
} = transport_channel;
let transport_incoming = futures::stream::poll_fn({
let mut completion = connection.transport_completion.clone();
let mut completed = false;
move |cx| {
if owned_transport
&& !completed
&& let std::task::Poll::Ready(Ok(())) =
std::pin::Pin::new(&mut completion).poll(cx)
{
transport_incoming_rx.close();
completed = true;
}
transport_incoming_rx.poll_next_unpin(cx)
}
});
let protocol_compat = ProtocolCompat::new(protocol_mode);
let termination_guard = TerminationGuard {
incoming_closed: connection.incoming_closed.clone(),
pending_replies: pending_replies.clone(),
};
let future = crate::util::instrument_with_connection_name(name, {
let connection = connection.clone();
async move {
let sources = &termination_sources;
let driver = async {
let background = async {
let incoming = {
let pending_replies = pending_replies.clone();
let protocol_compat = protocol_compat.clone();
async {
let mut transport_incoming = std::pin::pin!(transport_incoming);
let incoming = incoming_actor::incoming_protocol_actor(
me.counterpart(),
&connection,
transport_incoming.as_mut(),
dynamic_handler_rx,
pending_replies,
incoming_actor::IncomingHandlers::new(
handler,
on_close,
foreground_succeeded.clone(),
),
protocol_compat,
);
let result = run_incoming_until_foreground_succeeds(
incoming,
foreground_succeeded,
connection.incoming_closed.clone(),
)
.await;
sources.record(TerminationSource::Handler, &result);
if result.is_err() {
connection.request_shutdown();
}
result?;
while transport_incoming.next().await.is_some() {}
Ok(())
}
};
let other_actors = async {
let result = futures::try_join!(
transport_driver,
outgoing_actor::outgoing_protocol_actor(
outgoing_rx,
pending_replies,
transport_outgoing_tx,
protocol_compat,
foreground_done,
)
.inspect(
|result| sources.record(TerminationSource::Transport, result)
),
);
if result.is_err() {
connection.request_shutdown();
}
result?;
Ok(())
};
run_until_connection_close(
incoming,
other_actors,
connection.incoming_closed.clone(),
)
.await
};
run_until_connection_close(
finish_actor_error(background, &connection),
async {
let application = async {
futures::try_join!(
finish_actor_error(
task_actor::task_actor(new_task_rx, &connection)
.inspect(|result| sources
.record(TerminationSource::Handler, result),),
&connection,
),
finish_actor_error(
runner
.run_with_connection_to(connection.clone())
.inspect(|result| sources
.record(TerminationSource::Handler, result),),
&connection,
),
)?;
Ok(())
};
let result = run_until_connection_close(
application,
async {
let result = main_fn(connection.clone()).await;
sources.record(TerminationSource::Foreground, &result);
connection.request_shutdown();
if result.is_ok() {
let _ = foreground_succeeded_tx.send(());
}
connection.pending_replies.disarm_cancellations();
connection.wait_protected_operations().await;
result
},
connection.incoming_closed.clone(),
)
.await?;
let _ = foreground_done_tx.send(());
let drained = connection
.drain_outgoing(transport_finish, wait_owned_transport)
.await;
sources.record(TerminationSource::Transport, &drained);
drained?;
Ok(result)
},
connection.incoming_closed.clone(),
)
.await
};
let result = driver.await;
termination_guard.finish(match &result {
Ok(_) if connection.incoming_closed.is_closing() => {
ConnectionTermination::IncomingClosed
}
Ok(_) => ConnectionTermination::LocalShutdown,
Err(error) => sources.classify(error.clone()),
});
result
}
});
(connection, future)
}
}
async fn finish_actor_error<R: Role>(
actor: impl Future<Output = Result<(), crate::Error>>,
connection: &ConnectionTo<R>,
) -> Result<(), crate::Error> {
let result = actor.await;
if result.is_err() {
connection.request_shutdown();
if connection.incoming_closed.is_closing() {
connection.incoming_closed.closed().await;
}
connection.wait_protected_operations().await;
}
result
}
#[cfg(feature = "unstable_mcp_over_acp")]
impl<
Host: Role,
Handler: HandleDispatchFrom<Host::Counterpart>,
Runner: RunWithConnectionTo<Host::Counterpart>,
Close: HandleConnectionClose<Host::Counterpart>,
> Builder<Host, Handler, Runner, Close, V1>
{
pub fn with_mcp_server(
self,
mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
) -> Builder<
Host,
impl HandleDispatchFrom<Host::Counterpart>,
impl RunWithConnectionTo<Host::Counterpart>,
Close,
V1,
>
where
Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
{
let (handler, runner) = mcp_server.into_handler_and_runner();
self.with_handler(handler).with_runner(runner)
}
}
#[cfg(all(feature = "unstable_mcp_over_acp", feature = "unstable_protocol_v2"))]
impl<
Host: Role,
Handler: HandleDispatchFrom<Host::Counterpart>,
Runner: RunWithConnectionTo<Host::Counterpart>,
Close: HandleConnectionClose<Host::Counterpart>,
> Builder<Host, Handler, Runner, Close, V2>
{
pub fn with_mcp_server(
self,
mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
) -> Builder<
Host,
impl HandleDispatchFrom<Host::Counterpart>,
impl RunWithConnectionTo<Host::Counterpart>,
Close,
V2,
>
where
Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
{
let (handler, runner) = mcp_server.into_v2_handler_and_runner();
self.with_handler(handler).with_runner(runner)
}
}
impl<R, H, Run, Close, P> ConnectTo<R::Counterpart> for Builder<R, H, Run, Close, P>
where
R: Role,
H: HandleDispatchFrom<R::Counterpart> + 'static,
Run: RunWithConnectionTo<R::Counterpart> + 'static,
Close: HandleConnectionClose<R::Counterpart> + 'static,
P: Protocol,
{
async fn connect_to(self, client: impl ConnectTo<R>) -> Result<(), crate::Error> {
Builder::connect_to(self, client).await
}
}
pub(crate) struct ResponsePayload {
pub(crate) result: Result<serde_json::Value, crate::Error>,
pub(crate) ack_tx: Option<oneshot::Sender<()>>,
}
type ResponseRouteHook =
Box<dyn FnOnce(&str, &serde_json::Value) -> Result<(), crate::Error> + Send>;
struct RequestReadiness {
future: BoxFuture<'static, Result<(), crate::Error>>,
}
impl RequestReadiness {
fn new(future: impl Future<Output = Result<(), crate::Error>> + Send + 'static) -> Self {
Self {
future: future.boxed(),
}
}
}
impl Future for RequestReadiness {
type Output = Result<(), crate::Error>;
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
self.future.as_mut().poll(cx)
}
}
impl Debug for RequestReadiness {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("RequestReadiness")
.finish_non_exhaustive()
}
}
impl std::fmt::Debug for ResponsePayload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResponsePayload")
.field("result", &self.result)
.field("ack_tx", &self.ack_tx.as_ref().map(|_| "..."))
.finish()
}
}
#[derive(Clone, Debug, Default)]
struct ResponseOrdering {
ordered: Arc<AtomicBool>,
}
impl ResponseOrdering {
fn mark_ordered(&self) {
self.ordered.store(true, Ordering::Release);
}
fn is_ordered(&self) -> bool {
self.ordered.load(Ordering::Acquire)
}
}
struct PendingReply {
id: RawJsonRpcId,
method: String,
role_id: RoleId,
sender: oneshot::Sender<ResponsePayload>,
cancellation_disarm: SentRequestCancellationDisarm,
ordering: ResponseOrdering,
response_route_hook: Option<ResponseRouteHook>,
accepts_lower_initialize_version: bool,
}
impl PendingReply {
fn fail(self, error: crate::Error) {
self.cancellation_disarm.disarm();
if self
.sender
.send(ResponsePayload {
result: Err(error),
ack_tx: None,
})
.is_err()
{
tracing::trace!(method = %self.method, "Pending request was already dropped");
}
}
fn fail_incoming_closed(self) {
let error = incoming_transport_closed_error(&self.method);
self.fail(error);
}
}
#[derive(Default)]
struct PendingRepliesInner {
incoming_closed: bool,
termination: Option<ConnectionTermination>,
replies: HashMap<RawJsonRpcId, PendingReply>,
}
#[derive(Clone, Default)]
struct PendingReplies {
inner: Arc<Mutex<PendingRepliesInner>>,
}
impl PendingReplies {
fn registrar(&self) -> PendingRepliesRegistrar {
PendingRepliesRegistrar {
inner: Arc::downgrade(&self.inner),
}
}
fn contains(&self, id: &RawJsonRpcId) -> bool {
self.inner
.lock()
.expect("pending replies mutex poisoned")
.replies
.contains_key(id)
}
fn remove(&self, id: &RawJsonRpcId) -> Option<PendingReply> {
self.inner
.lock()
.expect("pending replies mutex poisoned")
.replies
.remove(id)
}
fn close_incoming(&self) -> usize {
let replies = {
let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
inner.incoming_closed = true;
std::mem::take(&mut inner.replies)
};
let count = replies.len();
for (_, reply) in replies {
reply.fail_incoming_closed();
}
count
}
fn terminate(&self, termination: &ConnectionTermination) -> usize {
let replies = {
let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
inner.termination = Some(termination.clone());
std::mem::take(&mut inner.replies)
};
let count = replies.len();
for (_, reply) in replies {
let error = connection_terminated_error(&reply.method, termination);
reply.fail(error);
}
count
}
}
#[derive(Clone)]
struct PendingRepliesRegistrar {
inner: Weak<Mutex<PendingRepliesInner>>,
}
impl PendingRepliesRegistrar {
fn disarm_cancellations(&self) {
if let Some(inner) = self.inner.upgrade() {
let inner = inner.lock().expect("pending replies mutex poisoned");
for reply in inner.replies.values() {
reply.cancellation_disarm.disarm();
}
}
}
fn subscribe(
&self,
id: RawJsonRpcId,
reply: PendingReply,
incoming_closed: &IncomingClosed,
) -> Result<(), crate::Error> {
let Some(inner) = self.inner.upgrade() else {
let error = if incoming_closed.is_closing() {
incoming_transport_closed_error(&reply.method)
} else if let Some(termination) = incoming_closed.termination() {
connection_terminated_error(&reply.method, &termination)
} else {
crate::util::internal_error(format!(
"failed to send outgoing request `{}`: connection is no longer running",
reply.method
))
};
reply.fail(error.clone());
return Err(error);
};
let result = {
let mut inner = inner.lock().expect("pending replies mutex poisoned");
if inner.incoming_closed {
Err((reply, None))
} else if let Some(termination) = &inner.termination {
Err((reply, Some(termination.clone())))
} else {
Ok(inner.replies.insert(id, reply))
}
};
match result {
Err((rejected, termination)) => {
let error = match termination {
Some(termination) => {
connection_terminated_error(&rejected.method, &termination)
}
None => incoming_transport_closed_error(&rejected.method),
};
rejected.fail(error.clone());
Err(error)
}
Ok(replaced) => {
if let Some(replaced) = replaced {
replaced.fail(
crate::Error::internal_error()
.data("outgoing request ID was reused before its response arrived"),
);
}
Ok(())
}
}
}
fn remove(&self, id: &RawJsonRpcId) -> Option<PendingReply> {
self.inner
.upgrade()?
.lock()
.expect("pending replies mutex poisoned")
.replies
.remove(id)
}
}
impl Debug for PendingRepliesRegistrar {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PendingRepliesRegistrar")
.field("is_connected", &(self.inner.strong_count() > 0))
.finish()
}
}
#[derive(Clone)]
pub struct RequestCancellation {
state: Arc<RequestCancellationState>,
}
struct RequestCancellationState {
cancelled: AtomicBool,
signal_tx: Mutex<Option<oneshot::Sender<()>>>,
signal_rx: future::Shared<BoxFuture<'static, ()>>,
}
impl RequestCancellation {
fn new() -> Self {
let (signal_tx, signal_rx) = oneshot::channel();
let signal_rx = signal_rx.map(|_| ()).boxed().shared();
Self {
state: Arc::new(RequestCancellationState {
cancelled: AtomicBool::new(false),
signal_tx: Mutex::new(Some(signal_tx)),
signal_rx,
}),
}
}
pub async fn cancelled(&self) {
self.state.signal_rx.clone().await;
}
pub async fn run_until_cancelled<T>(
&self,
future: impl std::future::Future<Output = Result<T, crate::Error>>,
) -> Result<T, crate::Error> {
if self.is_cancelled() {
return Err(crate::Error::request_cancelled());
}
match future::select(pin!(future), pin!(self.cancelled())).await {
Either::Left((result, _)) => result,
Either::Right(((), _)) => Err(crate::Error::request_cancelled()),
}
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.state.cancelled.load(Ordering::Acquire)
}
fn cancel(&self) {
if self.state.cancelled.swap(true, Ordering::AcqRel) {
return;
}
let signal_tx = self
.state
.signal_tx
.lock()
.expect("request cancellation signal mutex poisoned")
.take();
if let Some(signal_tx) = signal_tx {
let _ = signal_tx.send(());
}
}
}
impl Debug for RequestCancellation {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("RequestCancellation")
.field("is_cancelled", &self.is_cancelled())
.finish_non_exhaustive()
}
}
#[derive(Debug)]
enum RequestCancellationEntry {
Armed,
Cancelled,
Marker(RequestCancellation),
}
#[derive(Debug)]
struct RequestCancellationSlot {
generation: u64,
entry: RequestCancellationEntry,
}
#[derive(Debug, Default)]
struct RequestCancellationRegistryInner {
slots: HashMap<RawJsonRpcId, RequestCancellationSlot>,
next_generation: u64,
}
#[derive(Clone, Debug, Default)]
struct RequestCancellationRegistry {
inner: Arc<Mutex<RequestCancellationRegistryInner>>,
}
#[derive(Debug)]
struct ResponderCancellation {
id: RawJsonRpcId,
generation: u64,
registry: RequestCancellationRegistry,
}
impl RequestCancellationRegistry {
fn new() -> Self {
Self::default()
}
fn register(&self, id: &RawJsonRpcId) -> ResponderCancellation {
let generation = {
let mut inner = self
.inner
.lock()
.expect("request cancellation registry mutex poisoned");
let generation = inner.next_generation;
inner.next_generation += 1;
if inner
.slots
.insert(
id.clone(),
RequestCancellationSlot {
generation,
entry: RequestCancellationEntry::Armed,
},
)
.is_some()
{
tracing::debug!(
?id,
"peer reused the ID of a request that is still in flight"
);
}
generation
};
ResponderCancellation {
id: id.clone(),
generation,
registry: self.clone(),
}
}
fn marker(&self, id: &RawJsonRpcId, generation: u64) -> RequestCancellation {
let mut inner = self
.inner
.lock()
.expect("request cancellation registry mutex poisoned");
let Some(slot) = inner.slots.get_mut(id) else {
return RequestCancellation::new();
};
if slot.generation != generation {
return RequestCancellation::new();
}
let entry = &mut slot.entry;
match entry {
RequestCancellationEntry::Marker(marker) => marker.clone(),
RequestCancellationEntry::Armed => {
let marker = RequestCancellation::new();
*entry = RequestCancellationEntry::Marker(marker.clone());
marker
}
RequestCancellationEntry::Cancelled => {
let marker = RequestCancellation::new();
marker.cancel();
*entry = RequestCancellationEntry::Marker(marker.clone());
marker
}
}
}
fn cancel_if_requested(&self, dispatch: &Dispatch) -> Result<bool, crate::Error> {
let Some(request_id) = cancellation_request_id(dispatch)? else {
return Ok(false);
};
Ok(self.cancel(&request_id))
}
fn cancel(&self, request_id: &RawJsonRpcId) -> bool {
let marker = {
let mut inner = self
.inner
.lock()
.expect("request cancellation registry mutex poisoned");
let Some(slot) = inner.slots.get_mut(request_id) else {
return false;
};
let entry = &mut slot.entry;
match entry {
RequestCancellationEntry::Marker(marker) => marker.clone(),
RequestCancellationEntry::Cancelled => return true,
RequestCancellationEntry::Armed => {
*entry = RequestCancellationEntry::Cancelled;
return true;
}
}
};
marker.cancel();
true
}
fn remove(&self, request_id: &RawJsonRpcId, generation: u64) {
let mut inner = self
.inner
.lock()
.expect("request cancellation registry mutex poisoned");
if inner
.slots
.get(request_id)
.is_some_and(|slot| slot.generation == generation)
{
inner.slots.remove(request_id);
}
}
}
impl ResponderCancellation {
fn cancellation(&self) -> RequestCancellation {
self.registry.marker(&self.id, self.generation)
}
fn is_cancelled(&self) -> bool {
let inner = self
.registry
.inner
.lock()
.expect("request cancellation registry mutex poisoned");
let Some(slot) = inner.slots.get(&self.id) else {
return false;
};
if slot.generation != self.generation {
return false;
}
match &slot.entry {
RequestCancellationEntry::Armed => false,
RequestCancellationEntry::Cancelled => true,
RequestCancellationEntry::Marker(marker) => marker.is_cancelled(),
}
}
}
impl Drop for ResponderCancellation {
fn drop(&mut self) {
self.registry.remove(&self.id, self.generation);
}
}
fn cancellation_request_id(dispatch: &Dispatch) -> Result<Option<RawJsonRpcId>, crate::Error> {
let Dispatch::Notification(message) = dispatch else {
return Ok(None);
};
cancellation_request_id_from_message(message)
}
fn cancellation_request_id_from_message(
message: &UntypedMessage,
) -> Result<Option<RawJsonRpcId>, crate::Error> {
let (method, params) = peel_successor_envelopes(&message.method, &message.params);
if !crate::v1::schema::CancelRequestNotification::matches_method(method) {
return Ok(None);
}
let notification = crate::v1::schema::CancelRequestNotification::parse_message(method, params)?;
Ok(Some(notification.request_id.into()))
}
fn peel_successor_envelopes<'message>(
mut method: &'message str,
mut params: &'message serde_json::Value,
) -> (&'message str, &'message serde_json::Value) {
while crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
let Some(inner_method) = params.get("method").and_then(serde_json::Value::as_str) else {
break;
};
method = inner_method;
params = params.get("params").unwrap_or(&serde_json::Value::Null);
}
(method, params)
}
#[must_use]
pub fn is_cancel_request_notification<N: JsonRpcNotification>(notification: &N) -> bool {
let method = notification.method();
if crate::v1::schema::CancelRequestNotification::matches_method(method) {
return true;
}
if !crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
return false;
}
match notification.to_untyped_message() {
Ok(untyped) => {
let (method, _params) = peel_successor_envelopes(&untyped.method, &untyped.params);
crate::v1::schema::CancelRequestNotification::matches_method(method)
}
Err(error) => {
tracing::debug!(
error = ?crate::util::redacted_error(error.code),
"failed to inspect successor-wrapped notification for cancellation"
);
false
}
}
}
#[derive(Clone, Debug, Default)]
struct ResponseDestination {
state: Arc<ResponseDestinationState>,
}
#[derive(Debug, Default)]
struct ResponseDestinationState {
completed: AtomicBool,
attempts: Mutex<HandlerAttempts>,
}
#[derive(Debug, Default)]
struct HandlerAttempts {
active: usize,
deferred: Option<AbandonedResponse>,
}
#[derive(Debug)]
struct AbandonedResponse {
message_tx: OutgoingMessageTx,
connection_state: IncomingClosed,
message: OutgoingMessage,
}
impl AbandonedResponse {
fn send(self) {
if self.connection_state.is_shutting_down() {
tracing::debug!("not answering dropped responder: connection is shutting down");
return;
}
if let Err(error) = send_raw_message(&self.message_tx, self.message) {
tracing::debug!(
error = ?crate::util::redacted_error(error.code),
"could not answer request whose responder was dropped"
);
}
}
}
struct HandlerAttempt {
destination: ResponseDestination,
}
impl Drop for HandlerAttempt {
fn drop(&mut self) {
let deferred = {
let mut attempts = self.destination.attempts();
attempts.active -= 1;
if attempts.active == 0 {
attempts.deferred.take()
} else {
None
}
};
if let Some(deferred) = deferred {
deferred.send();
}
}
}
impl ResponseDestination {
fn individual() -> Self {
Self::default()
}
fn attempts(&self) -> std::sync::MutexGuard<'_, HandlerAttempts> {
self.state
.attempts
.lock()
.expect("response destination mutex poisoned")
}
fn is_completed(&self) -> bool {
self.state.completed.load(Ordering::Acquire)
}
fn complete(self, response: RawJsonRpcMessage) -> Option<TransportFrame> {
if self.state.completed.swap(true, Ordering::AcqRel) {
tracing::warn!("Ignoring duplicate completion of JSON-RPC request");
return None;
}
Some(TransportFrame::Single(response))
}
fn begin_handler_attempt(&self) -> HandlerAttempt {
self.attempts().active += 1;
HandlerAttempt {
destination: self.clone(),
}
}
fn abandon(&self, response: AbandonedResponse) {
{
let mut attempts = self.attempts();
if attempts.active > 0 {
attempts.deferred = Some(response);
return;
}
}
response.send();
}
fn discard_abandoned(&self) {
self.attempts().deferred = None;
}
}
#[derive(Clone, Debug)]
struct RequestReplyTarget {
id: RawJsonRpcId,
method: String,
destination: ResponseDestination,
}
#[derive(Clone)]
struct ResponseReplyTarget {
id: RawJsonRpcId,
method: String,
sender: Arc<Mutex<Option<oneshot::Sender<ResponsePayload>>>>,
ordering: ResponseOrdering,
dispatch: ResponseDispatch,
}
impl ResponseReplyTarget {
fn route(self, result: Result<serde_json::Value, crate::Error>) {
let sender = self
.sender
.lock()
.expect("response reply mutex poisoned")
.take();
let Some(sender) = sender else {
tracing::debug!(
method = %self.method,
id = ?self.id,
"response was already routed to its local awaiter"
);
return;
};
let ack_tx = self.dispatch.acknowledgment(&self.ordering);
if sender.send(ResponsePayload { result, ack_tx }).is_err() {
tracing::debug!(
method = %self.method,
id = ?self.id,
"dropped response because local receiver was gone"
);
}
}
}
#[derive(Clone, Default)]
struct ResponseDispatch {
state: Arc<Mutex<ResponseDispatchState>>,
}
#[derive(Default)]
struct ResponseDispatchState {
complete: bool,
ack_rx: Option<oneshot::Receiver<()>>,
}
impl ResponseDispatch {
fn acknowledgment(&self, ordering: &ResponseOrdering) -> Option<oneshot::Sender<()>> {
if !ordering.is_ordered() {
return None;
}
let mut state = self.state.lock().expect("response dispatch mutex poisoned");
if state.complete {
return None;
}
let (ack_tx, ack_rx) = oneshot::channel();
let previous_ack = state.ack_rx.replace(ack_rx);
debug_assert!(
previous_ack.is_none(),
"a response dispatch can only be routed once"
);
Some(ack_tx)
}
fn complete(&self) -> Option<oneshot::Receiver<()>> {
let mut state = self.state.lock().expect("response dispatch mutex poisoned");
state.complete = true;
state.ack_rx.take()
}
}
enum HandlerErrorTarget {
Request(RequestReplyTarget),
Response(ResponseReplyTarget),
}
impl HandlerErrorTarget {
fn begin_handler_attempt(&self) -> Option<HandlerAttempt> {
match self {
Self::Request(target) => Some(target.destination.begin_handler_attempt()),
Self::Response(_) => None,
}
}
}
#[derive(Debug)]
enum OutgoingMessage {
CloseAfterDraining { done: oneshot::Sender<()> },
Request {
id: RawJsonRpcId,
method: String,
untyped: UntypedMessage,
remote_style: crate::role::RemoteStyle,
readiness: Option<RequestReadiness>,
},
Notification {
untyped: UntypedMessage,
},
Response {
id: RawJsonRpcId,
method: String,
response: Result<serde_json::Value, crate::Error>,
destination: ResponseDestination,
},
UncorrelatedErrorResponse {
error: crate::Error,
destination: ResponseDestination,
},
}
impl OutgoingMessage {
fn log_summary(&self) -> (&'static str, Option<&str>, Option<&RawJsonRpcId>) {
match self {
Self::CloseAfterDraining { .. } => ("close_after_draining", None, None),
Self::Request { id, method, .. } => ("request", Some(method), Some(id)),
Self::Notification { untyped } => ("notification", Some(&untyped.method), None),
Self::Response { id, method, .. } => ("response", Some(method), Some(id)),
Self::UncorrelatedErrorResponse { .. } => ("uncorrelated_error_response", None, None),
}
}
}
#[must_use]
#[derive(Debug)]
pub enum Handled<T> {
Yes,
No {
message: T,
retry: bool,
},
}
pub trait IntoHandled<T> {
fn into_handled(self) -> Handled<T>;
}
impl<T> IntoHandled<T> for () {
fn into_handled(self) -> Handled<T> {
Handled::Yes
}
}
impl<T> IntoHandled<T> for Handled<T> {
fn into_handled(self) -> Handled<T> {
self
}
}
#[derive(Clone, Debug)]
pub struct ConnectionTo<Counterpart: Role> {
counterpart: Counterpart,
message_tx: OutgoingMessageTx,
task_tx: TaskTx,
dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
transport_completion: SharedTransportCompletion,
pending_replies: PendingRepliesRegistrar,
protocol_mode: ProtocolMode,
incoming_closed: IncomingClosed,
protected_operations: Arc<Mutex<ProtectedOperations>>,
runner_error_scope: Option<run::RunnerErrorScope>,
}
type SharedTransportCompletion = future::Shared<BoxFuture<'static, Result<(), crate::Error>>>;
type SharedCompletionSignal = future::Shared<BoxFuture<'static, ()>>;
#[derive(Default)]
struct ProtectedOperations {
pending: Vec<oneshot::Receiver<()>>,
joining: Option<SharedCompletionSignal>,
}
impl Debug for ProtectedOperations {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ProtectedOperations")
.field("pending", &self.pending.len())
.field("joining", &self.joining.is_some())
.finish_non_exhaustive()
}
}
fn completion_signal() -> (oneshot::Sender<()>, SharedCompletionSignal) {
let (tx, rx) = oneshot::channel();
let signal = async move {
if rx.await.is_err() {
future::pending::<()>().await;
}
}
.boxed()
.shared();
(tx, signal)
}
#[derive(Clone)]
struct IncomingClosed {
state: Arc<IncomingClosedState>,
}
struct IncomingClosedState {
closing: AtomicBool,
closed: AtomicBool,
signal_tx: Mutex<Option<oneshot::Sender<()>>>,
signal_rx: future::Shared<BoxFuture<'static, ()>>,
shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
#[cfg(any(feature = "unstable_mcp_over_acp", test))]
shutdown_rx: SharedCompletionSignal,
termination: Mutex<Option<ConnectionTermination>>,
terminated_tx: Mutex<Option<oneshot::Sender<()>>>,
terminated_rx: SharedCompletionSignal,
}
impl IncomingClosed {
fn new() -> Self {
let (signal_tx, signal_rx) = oneshot::channel();
let (shutdown_tx, shutdown_rx) = oneshot::channel();
#[cfg(not(any(feature = "unstable_mcp_over_acp", test)))]
drop(shutdown_rx);
let (terminated_tx, terminated_rx) = oneshot::channel();
Self {
state: Arc::new(IncomingClosedState {
closing: AtomicBool::new(false),
closed: AtomicBool::new(false),
signal_tx: Mutex::new(Some(signal_tx)),
signal_rx: signal_rx.map(|_| ()).boxed().shared(),
shutdown_tx: Mutex::new(Some(shutdown_tx)),
#[cfg(any(feature = "unstable_mcp_over_acp", test))]
shutdown_rx: shutdown_rx.map(|_| ()).boxed().shared(),
termination: Mutex::new(None),
terminated_tx: Mutex::new(Some(terminated_tx)),
terminated_rx: terminated_rx.map(|_| ()).boxed().shared(),
}),
}
}
fn begin_close(&self) {
self.state.closing.store(true, Ordering::Release);
self.request_shutdown();
}
fn request_shutdown(&self) {
if let Some(tx) = self
.state
.shutdown_tx
.lock()
.expect("shutdown signal mutex poisoned")
.take()
{
let _ = tx.send(());
}
}
fn is_shutting_down(&self) -> bool {
self.state
.shutdown_tx
.lock()
.expect("shutdown signal mutex poisoned")
.is_none()
|| self.termination().is_some()
}
fn finish_close(&self) {
self.state.closed.store(true, Ordering::Release);
let signal_tx = self
.state
.signal_tx
.lock()
.expect("incoming-close signal mutex poisoned")
.take();
if let Some(signal_tx) = signal_tx {
let _ = signal_tx.send(());
}
}
async fn closed(&self) {
self.state.signal_rx.clone().await;
}
fn is_closed(&self) -> bool {
self.state.closed.load(Ordering::Acquire)
}
fn is_closing(&self) -> bool {
self.state.closing.load(Ordering::Acquire)
}
fn set_termination(&self, termination: ConnectionTermination) -> bool {
let mut slot = self
.state
.termination
.lock()
.expect("termination mutex poisoned");
if slot.is_some() {
return false;
}
*slot = Some(termination);
true
}
fn signal_terminated(&self) {
if let Some(tx) = self
.state
.terminated_tx
.lock()
.expect("terminated signal mutex poisoned")
.take()
{
let _ = tx.send(());
}
}
fn termination(&self) -> Option<ConnectionTermination> {
self.state
.termination
.lock()
.expect("termination mutex poisoned")
.clone()
}
async fn terminated(&self) -> ConnectionTermination {
self.state.terminated_rx.clone().await;
self.termination()
.expect("termination is recorded before it is signalled")
}
}
struct TerminationGuard {
incoming_closed: IncomingClosed,
pending_replies: PendingReplies,
}
impl TerminationGuard {
fn finish(self, termination: ConnectionTermination) {
self.publish(termination);
}
fn publish(&self, termination: ConnectionTermination) {
if !self.incoming_closed.set_termination(termination.clone()) {
return;
}
let pending_reply_count = self.pending_replies.terminate(&termination);
tracing::debug!(
termination = termination.kind(),
pending_reply_count,
"Connection terminated"
);
self.incoming_closed.signal_terminated();
}
}
impl Drop for TerminationGuard {
fn drop(&mut self) {
self.publish(ConnectionTermination::Dropped);
}
}
impl Debug for IncomingClosed {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("IncomingClosed")
.field("is_closing", &self.is_closing())
.field("is_closed", &self.is_closed())
.finish_non_exhaustive()
}
}
pub const INCOMING_TRANSPORT_CLOSED_REASON: &str = "incoming_transport_closed";
#[must_use]
pub fn is_incoming_transport_closed(error: &crate::Error) -> bool {
error.origin == crate::error::ErrorOrigin::IncomingTransportClosed
}
fn incoming_transport_closed_error(method: &str) -> crate::Error {
let mut error = crate::Error::internal_error();
error.message = "Incoming transport closed".to_string();
error.origin = crate::error::ErrorOrigin::IncomingTransportClosed;
error.data(serde_json::json!({
"reason": INCOMING_TRANSPORT_CLOSED_REASON,
"method": method,
}))
}
fn run_incoming_until_foreground_succeeds(
incoming: impl Future<Output = Result<(), crate::Error>>,
foreground_succeeded: SharedCompletionSignal,
incoming_closed: IncomingClosed,
) -> impl Future<Output = Result<(), crate::Error>> {
let mut incoming = Box::pin(incoming);
future::poll_fn(move |cx| {
if foreground_succeeded.clone().poll_unpin(cx).is_ready() && !incoming_closed.is_closing() {
return std::task::Poll::Ready(Ok(()));
}
incoming.as_mut().poll(cx)
})
}
fn run_until_connection_close<R>(
background: impl Future<Output = Result<(), crate::Error>>,
foreground: impl Future<Output = Result<R, crate::Error>>,
incoming_closed: IncomingClosed,
) -> impl Future<Output = Result<R, crate::Error>> {
let background = Box::pin(background);
let foreground = Box::pin(foreground);
async move {
match future::select(background, foreground).await {
Either::Left((background_result, foreground)) => {
background_result?;
foreground.await
}
Either::Right((foreground_result, background)) => {
if !incoming_closed.is_closing() {
return foreground_result;
}
match future::select(background, Box::pin(incoming_closed.closed())).await {
Either::Left((background_result, _)) => {
background_result?;
foreground_result
}
Either::Right(((), background)) => {
crate::util::run_until(background, future::ready(foreground_result)).await
}
}
}
}
}
}
impl<Counterpart: Role> ConnectionTo<Counterpart> {
fn new(
counterpart: Counterpart,
message_tx: mpsc::UnboundedSender<OutgoingMessage>,
task_tx: mpsc::UnboundedSender<Task>,
dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
transport_completion: SharedTransportCompletion,
pending_replies: PendingRepliesRegistrar,
protocol_mode: ProtocolMode,
) -> Self {
Self {
counterpart,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies,
protocol_mode,
incoming_closed: IncomingClosed::new(),
protected_operations: Arc::default(),
runner_error_scope: None,
}
}
pub(crate) fn with_runner_error_scope(mut self, scope: run::RunnerErrorScope) -> Self {
self.runner_error_scope = Some(scope);
self
}
pub(crate) fn finish_runner_error(
&self,
error: crate::Error,
) -> impl Future<Output = ()> + Send + '_ {
if let Some(scope) = &self.runner_error_scope {
Either::Left(scope.finish(error))
} else {
self.request_shutdown();
Either::Right(self.wait_protected_operations())
}
}
#[cfg(any(feature = "unstable_mcp_over_acp", test))]
#[track_caller]
pub(crate) fn spawn_protected(
&self,
task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
) -> Result<(), crate::Error> {
let mut state = self
.protected_operations
.lock()
.expect("protected operations mutex poisoned");
if state.joining.is_some() {
return Err(crate::Error::request_cancelled());
}
state
.pending
.retain_mut(|done| matches!(done.try_recv(), Ok(None)));
let (done_tx, done_rx) = oneshot::channel();
let task = task.into_future();
self.spawn(async move {
let result = task.await;
let _ = done_tx.send(());
result
})?;
state.pending.push(done_rx);
Ok(())
}
pub(crate) async fn wait_protected_operations(&self) {
let joining = {
let mut state = self
.protected_operations
.lock()
.expect("protected operations mutex poisoned");
if state.joining.is_none() {
let operations = std::mem::take(&mut state.pending);
state.joining = Some(
async move {
for operation in operations {
let _ = operation.await;
}
}
.boxed()
.shared(),
);
}
state.joining.as_ref().expect("join initialized").clone()
};
joining.await;
}
pub(crate) fn request_shutdown(&self) {
self.incoming_closed.request_shutdown();
}
#[cfg(any(feature = "unstable_mcp_over_acp", test))]
pub(crate) async fn shutdown_requested(&self) {
self.incoming_closed.state.shutdown_rx.clone().await;
}
pub fn counterpart(&self) -> Counterpart {
self.counterpart.clone()
}
pub async fn incoming_closed(&self) {
self.incoming_closed.closed().await;
}
#[must_use]
pub fn is_incoming_closed(&self) -> bool {
self.incoming_closed.is_closed()
}
pub async fn terminated(&self) -> ConnectionTermination {
self.incoming_closed.terminated().await
}
#[must_use]
pub fn termination(&self) -> Option<ConnectionTermination> {
self.incoming_closed.termination()
}
async fn drain_outgoing(
&self,
finish: Option<crate::component::FinishControl>,
wait_owned_transport: bool,
) -> Result<(), crate::Error> {
let (done_tx, done_rx) = oneshot::channel();
let marker_result = send_raw_message(
&self.message_tx,
OutgoingMessage::CloseAfterDraining { done: done_tx },
);
let marker_result = match marker_result {
Ok(()) => done_rx.await.map_err(|error| {
crate::util::internal_error(format!(
"outgoing drain marker was dropped before completion: {error}"
))
}),
Err(error) => Err(error),
};
let physical_finish = finish.is_some();
if let Some(mut finish) = finish {
finish.request();
}
if physical_finish || wait_owned_transport {
self.transport_completion.clone().await?;
}
marker_result
}
fn is_incoming_closing(&self) -> bool {
self.incoming_closed.is_closing()
}
pub(super) fn begin_incoming_close(&self) {
self.incoming_closed.begin_close();
}
pub(super) fn finish_incoming_close(&self) {
self.incoming_closed.finish_close();
}
#[track_caller]
pub fn spawn(
&self,
task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
) -> Result<(), crate::Error> {
let location = std::panic::Location::caller();
let task = task.into_future();
Task::new(location, task).spawn(&self.task_tx)
}
#[track_caller]
pub fn spawn_connection<R: Role, P: Protocol>(
&self,
builder: Builder<
R,
impl HandleDispatchFrom<R::Counterpart> + 'static,
impl RunWithConnectionTo<R::Counterpart> + 'static,
impl HandleConnectionClose<R::Counterpart> + 'static,
P,
>,
transport: impl ConnectTo<R> + 'static,
) -> Result<P::Connection<R::Counterpart>, crate::Error> {
let (connection, future) =
builder.into_connection_and_future(transport, false, |_| std::future::pending());
Task::new(std::panic::Location::caller(), future).spawn(&self.task_tx)?;
Ok(protocol::connection_from_raw::<P, _>(connection))
}
#[must_use]
pub fn protocol_version(&self) -> Option<crate::schema::ProtocolVersion> {
self.protocol_mode.api_protocol_version()
}
pub fn into_protocol<P: Protocol>(self) -> Result<P::Connection<Counterpart>, crate::Error> {
if let (Some(guard), Some(requested)) =
(self.protocol_version(), protocol::protocol_version::<P>())
&& guard != requested
{
return Err(crate::Error::internal_error().data(format!(
"cannot use a connection guarded for ACP protocol version {guard} \
as a protocol version {requested} connection",
)));
}
Ok(protocol::connection_from_raw::<P, _>(self))
}
pub fn send_proxied_message<
Req: JsonRpcRequest<Response: Send>,
Notif: JsonRpcNotification,
Q: Protocol,
>(
&self,
message: Dispatch<Req, Notif, Q>,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Counterpart>,
{
self.send_proxied_message_to(self.counterpart(), message)
}
pub fn send_proxied_message_to<
Peer: Role,
Req: JsonRpcRequest<Response: Send>,
Notif: JsonRpcNotification,
Q: Protocol,
>(
&self,
peer: Peer,
message: Dispatch<Req, Notif, Q>,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Peer>,
{
match message {
Dispatch::Request(request, responder) => self
.send_ordered_request_to::<Raw, _, _>(peer, request)
.forward_response_to(responder),
Dispatch::Notification(notification) => {
if is_cancel_request_notification(¬ification) {
tracing::debug!(
"not forwarding hop-scoped `$/cancel_request` notification across proxy hop"
);
return Ok(());
}
self.send_notification_to(peer, notification)
}
Dispatch::Response(result, router) => {
router.route_with_result(result)
}
}
}
pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
where
Counterpart: HasPeer<Counterpart>,
{
self.send_request_to(self.counterpart.clone(), request)
}
pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
&self,
peer: Peer,
request: Req,
) -> SentRequest<Req::Response>
where
Counterpart: HasPeer<Peer>,
{
self.send_request_to_with_options(peer, request, false, None, None)
}
pub fn prepare_request<Req: JsonRpcRequest>(
&self,
request: Req,
) -> PreparedRequest<Req::Response>
where
Counterpart: HasPeer<Counterpart>,
{
self.prepare_request_to(self.counterpart.clone(), request)
}
pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
&self,
peer: Peer,
request: Req,
) -> PreparedRequest<Req::Response>
where
Counterpart: HasPeer<Peer>,
{
self.prepare_request_to_with_options(peer, request, None, None)
}
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) fn send_request_to_with_response_hook_after<
P: Protocol,
Peer: Role,
Req: JsonRpcRequest,
BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
>(
&self,
peer: Peer,
request: Req,
before_send: BeforeSend,
response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
) -> SentRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
let hook: ResponseRouteHook = Box::new(move |method, value| {
let response = Req::Response::from_json(method, value.clone())?;
response_hook(&response)
});
self.send_request_to_with_options(
peer,
request,
false,
Some(RequestReadiness::new(before_send)),
Some(hook),
)
}
#[cfg(feature = "unstable_protocol_v2")]
pub(crate) fn send_ordered_request_to_with_response_hook_after<
P: Protocol,
Peer: Role,
Req: JsonRpcRequest,
BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
>(
&self,
peer: Peer,
request: Req,
before_send: BeforeSend,
response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
) -> SentRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
let hook: ResponseRouteHook = Box::new(move |method, value| {
let response = Req::Response::from_json(method, value.clone())?;
response_hook(&response)
});
self.send_request_to_with_options(
peer,
request,
true,
Some(RequestReadiness::new(before_send)),
Some(hook),
)
}
pub(crate) fn send_ordered_request_to<P: Protocol, Peer: Role, Req: JsonRpcRequest>(
&self,
peer: Peer,
request: Req,
) -> SentRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
self.send_request_to_with_options(peer, request, true, None, None)
}
pub(crate) fn send_ordered_request_to_after<
P: Protocol,
Peer: Role,
Req: JsonRpcRequest,
BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
>(
&self,
peer: Peer,
request: Req,
before_send: BeforeSend,
) -> SentRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
self.send_request_to_with_options(
peer,
request,
true,
Some(RequestReadiness::new(before_send)),
None,
)
}
fn send_request_to_with_options<P: Protocol, Peer: Role, Req: JsonRpcRequest>(
&self,
peer: Peer,
request: Req,
ordered: bool,
readiness: Option<RequestReadiness>,
response_route_hook: Option<ResponseRouteHook>,
) -> SentRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
self.prepare_request_to_with_options(peer, request, readiness, response_route_hook)
.into_sent_request(ordered)
}
fn prepare_request_to_with_options<P: Protocol, Peer: Role, Req: JsonRpcRequest>(
&self,
peer: Peer,
request: Req,
readiness: Option<RequestReadiness>,
response_route_hook: Option<ResponseRouteHook>,
) -> PreparedRequest<Req::Response, P>
where
Counterpart: HasPeer<Peer>,
{
let method = request.method().to_string();
let id = HandleId::<P>::generated(Uuid::new_v4().to_string());
let (response_tx, response_rx) = oneshot::channel();
let response_ordering = ResponseOrdering::default();
let role_id = peer.role_id();
let remote_style = self.counterpart.remote_style(peer);
let cancellation = SentRequestCancellation::new(self.message_tx.clone(), id.raw().clone());
let pending_reply = PendingReply {
id: id.raw().clone(),
method: method.clone(),
role_id,
sender: response_tx,
cancellation_disarm: cancellation.disarm_handle(),
ordering: response_ordering.clone(),
response_route_hook,
accepts_lower_initialize_version: false,
};
let message = if self.is_incoming_closing() {
Err(incoming_transport_closed_error(&method))
} else {
request
.to_untyped_message()
.map(|untyped| OutgoingMessage::Request {
id: id.raw().clone(),
method: method.clone(),
untyped,
remote_style,
readiness,
})
.map_err(|error| {
crate::util::internal_error(format!(
"failed to create untyped request for `{method}`: {error}"
))
})
};
let sent = SentRequest::new(
id,
method.clone(),
self.task_tx.clone(),
response_rx,
cancellation,
response_ordering,
)
.map(move |json| <Req::Response>::from_json(&method, json));
PreparedRequest {
sent,
publication: RequestPublication {
message,
pending_reply,
message_tx: self.message_tx.clone(),
pending_replies: self.pending_replies.clone(),
incoming_closed: self.incoming_closed.clone(),
},
}
}
pub fn send_notification<N: JsonRpcNotification>(
&self,
notification: N,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Counterpart>,
{
self.send_notification_to(self.counterpart.clone(), notification)
}
pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
&self,
peer: Peer,
notification: N,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Peer>,
{
let remote_style = self.counterpart.remote_style(peer);
tracing::debug!(
role = std::any::type_name::<Counterpart>(),
peer = std::any::type_name::<Peer>(),
notification_type = std::any::type_name::<N>(),
?remote_style,
original_method = notification.method(),
"send_notification_to"
);
let transformed = remote_style.transform_outgoing_message(notification)?;
tracing::debug!(
transformed_method = %transformed.method,
"send_notification_to transformed"
);
send_raw_message(
&self.message_tx,
OutgoingMessage::Notification {
untyped: transformed,
},
)
}
pub fn send_cancel_request(
&self,
request_id: impl Into<RawJsonRpcId>,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Counterpart>,
{
self.send_cancel_request_to(self.counterpart.clone(), request_id)
}
pub fn send_cancel_request_to<Peer: Role>(
&self,
_peer: Peer,
request_id: impl Into<RawJsonRpcId>,
) -> Result<(), crate::Error>
where
Counterpart: HasPeer<Peer>,
{
let untyped = crate::v1::schema::CancelRequestNotification::new(
crate::v1::schema::RequestId::from(request_id.into()),
)
.to_untyped_message()?;
send_raw_message(&self.message_tx, OutgoingMessage::Notification { untyped })
}
pub fn add_dynamic_handler(
&self,
handler: impl HandleDispatchFrom<Counterpart> + 'static,
) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
let uuid = Uuid::new_v4();
let active = Arc::new(AtomicBool::new(true));
self.dynamic_handler_tx
.unbounded_send(DynamicHandlerMessage::AddDynamicHandler(
uuid,
Box::new(GuardedDynamicHandler {
active: active.clone(),
handler,
}),
active.clone(),
))
.map_err(crate::util::internal_error)?;
Ok(DynamicHandlerGuard::new(uuid, active, self.clone()))
}
pub(crate) fn dynamic_handler_barrier(&self) -> BoxFuture<'static, Result<(), crate::Error>> {
let (acknowledgment_tx, acknowledgment_rx) = oneshot::channel();
if let Err(error) =
self.dynamic_handler_tx
.unbounded_send(DynamicHandlerMessage::AcknowledgedBarrier(
acknowledgment_tx,
))
{
return future::ready(Err(crate::Error::into_internal_error(error))).boxed();
}
async move {
acknowledgment_rx.await.map_err(|error| {
crate::util::internal_error(format!(
"dynamic-handler barrier was dropped before acknowledgment: {error}"
))
})
}
.boxed()
}
fn remove_dynamic_handler(&self, uuid: Uuid) {
drop(
self.dynamic_handler_tx
.unbounded_send(DynamicHandlerMessage::RemoveDynamicHandler(uuid)),
);
}
}
struct GuardedDynamicHandler<Handler> {
active: Arc<AtomicBool>,
handler: Handler,
}
impl<Counterpart, Handler> HandleDispatchFrom<Counterpart> for GuardedDynamicHandler<Handler>
where
Counterpart: Role,
Handler: HandleDispatchFrom<Counterpart>,
{
async fn handle_dispatch_from(
&mut self,
message: Dispatch,
connection: ConnectionTo<Counterpart>,
) -> Result<Handled<Dispatch>, crate::Error> {
if !self.active.load(Ordering::Acquire) {
return Ok(Handled::No {
message,
retry: false,
});
}
self.handler.handle_dispatch_from(message, connection).await
}
fn describe_chain(&self) -> impl Debug {
self.handler.describe_chain()
}
}
#[must_use = "dropping this guard unregisters the dynamic handler"]
#[derive(Debug)]
pub struct DynamicHandlerGuard<R: Role> {
uuid: Option<Uuid>,
active: Arc<AtomicBool>,
cx: ConnectionTo<R>,
cleanup: Option<Arc<dyn DynamicHandlerCleanup>>,
}
pub(crate) trait DynamicHandlerCleanup: std::fmt::Debug + Send + Sync {
fn close(&self);
fn wait(&self) -> futures::future::BoxFuture<'static, ()>;
}
impl<R: Role> DynamicHandlerGuard<R> {
fn new(uuid: Uuid, active: Arc<AtomicBool>, cx: ConnectionTo<R>) -> Self {
Self {
uuid: Some(uuid),
active,
cx,
cleanup: None,
}
}
#[cfg(feature = "unstable_mcp_over_acp")]
pub(crate) fn with_cleanup(mut self, cleanup: Arc<dyn DynamicHandlerCleanup>) -> Self {
self.cleanup = Some(cleanup);
self
}
pub(crate) fn cleanup(&self) -> Option<Arc<dyn DynamicHandlerCleanup>> {
self.cleanup.clone()
}
pub(crate) fn deactivate(&self) {
self.active.store(false, Ordering::Release);
}
pub fn detach(mut self) {
self.uuid = None;
}
}
impl<R: Role> Drop for DynamicHandlerGuard<R> {
fn drop(&mut self) {
if let Some(uuid) = self.uuid {
self.active.store(false, Ordering::Release);
if let Some(cleanup) = &self.cleanup {
cleanup.close();
}
self.cx.remove_dynamic_handler(uuid);
}
}
}
#[must_use]
pub struct Responder<T: JsonRpcResponse = serde_json::Value, P: Protocol = Raw> {
method: String,
id: HandleId<P>,
destination: ResponseDestination,
send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
drop_guard: ResponderDropGuard,
}
struct ResponderDropGuard {
message_tx: OutgoingMessageTx,
connection_state: IncomingClosed,
wire_method: String,
id: RawJsonRpcId,
destination: ResponseDestination,
cancellation: ResponderCancellation,
armed: bool,
}
impl ResponderDropGuard {
fn disarm(&mut self) {
self.armed = false;
}
}
impl Drop for ResponderDropGuard {
fn drop(&mut self) {
if !self.armed || self.destination.is_completed() {
return;
}
if self.connection_state.is_shutting_down() {
tracing::debug!(
id = ?self.id,
method = %self.wire_method,
"responder dropped while the connection is shutting down; not answering"
);
return;
}
let error = if self.cancellation.is_cancelled() {
crate::Error::request_cancelled()
} else {
crate::Error::internal_error().data(format!(
"request handler dropped its responder for `{}` without responding",
self.wire_method
))
};
tracing::warn!(
id = ?self.id,
method = %self.wire_method,
code = ?error.code,
"Responder dropped without responding; answering with an error"
);
self.destination.abandon(AbandonedResponse {
message_tx: self.message_tx.clone(),
connection_state: self.connection_state.clone(),
message: OutgoingMessage::Response {
id: self.id.clone(),
method: self.wire_method.clone(),
response: Err(error),
destination: self.destination.clone(),
},
});
}
}
impl<T: JsonRpcResponse, P: Protocol> std::fmt::Debug for Responder<T, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Responder")
.field("method", &self.method)
.field("id", &self.id)
.field("response_type", &std::any::type_name::<T>())
.finish_non_exhaustive()
}
}
impl Responder<serde_json::Value, Raw> {
fn new(
message_tx: OutgoingMessageTx,
connection_state: IncomingClosed,
method: String,
id: RawJsonRpcId,
cancellation_registry: &RequestCancellationRegistry,
destination: ResponseDestination,
) -> Self {
let id_clone = id.clone();
let method_clone = method.clone();
let cancellation = cancellation_registry.register(&id);
let send_destination = destination.clone();
let drop_guard = ResponderDropGuard {
message_tx: message_tx.clone(),
connection_state,
wire_method: method.clone(),
id: id.clone(),
destination: destination.clone(),
cancellation,
armed: true,
};
Self {
method,
id: HandleId::raw_layer(id),
destination,
send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
send_raw_message(
&message_tx,
OutgoingMessage::Response {
id: id_clone,
method: method_clone,
response,
destination: send_destination,
},
)
}),
drop_guard,
}
}
}
impl<P: Protocol> Responder<serde_json::Value, P> {
pub fn cast<T: JsonRpcResponse>(self) -> Responder<T, P> {
self.wrap_params(move |method, value| match value {
Ok(value) => T::into_json(value, method),
Err(e) => Err(e),
})
}
}
impl<T: JsonRpcResponse, P: Protocol> Responder<T, P> {
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn id(&self) -> &P::RequestId {
self.id.typed()
}
#[must_use]
pub fn raw_id(&self) -> &RawJsonRpcId {
self.id.raw()
}
#[must_use]
pub fn cancellation(&self) -> RequestCancellation {
self.drop_guard.cancellation.cancellation()
}
pub fn erase_to_json(self) -> Responder<serde_json::Value, P> {
self.wrap_params(|method, value| T::from_json(method, value?))
}
pub fn wrap_method(self, method: String) -> Responder<T, P> {
Responder {
method,
id: self.id,
destination: self.destination,
send_fn: self.send_fn,
drop_guard: self.drop_guard,
}
}
pub fn wrap_params<U: JsonRpcResponse>(
self,
wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
) -> Responder<U, P> {
let method = self.method.clone();
let send_fn = self.send_fn;
Responder {
method: self.method,
id: self.id,
destination: self.destination,
send_fn: Box::new(move |input: Result<U, crate::Error>| {
let t_value = wrap_fn(&method, input);
send_fn(t_value)
}),
drop_guard: self.drop_guard,
}
}
pub fn respond_with_result(
mut self,
response: Result<T, crate::Error>,
) -> Result<(), crate::Error> {
tracing::debug!(id = ?self.id, "respond called");
self.drop_guard.disarm();
(self.send_fn)(response)
}
pub fn respond(self, response: T) -> Result<(), crate::Error> {
self.respond_with_result(Ok(response))
}
pub fn respond_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
self.respond_with_error(crate::util::internal_error(message))
}
pub fn respond_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
tracing::debug!(id = ?self.id, error = ?crate::util::redacted_error(error.code), "respond_with_error called");
self.respond_with_result(Err(error))
}
pub(crate) fn retag<Q: Protocol>(self) -> Result<Responder<T, Q>, crate::Error> {
Ok(Responder {
method: self.method,
id: self.id.into_protocol()?,
destination: self.destination,
send_fn: self.send_fn,
drop_guard: self.drop_guard,
})
}
pub fn into_raw(self) -> Responder<T, Raw> {
Responder {
method: self.method,
id: self.id.into_raw(),
destination: self.destination,
send_fn: self.send_fn,
drop_guard: self.drop_guard,
}
}
fn reply_target(&self) -> RequestReplyTarget {
RequestReplyTarget {
id: self.id.raw().clone(),
method: self.method.clone(),
destination: self.destination.clone(),
}
}
}
impl<T: JsonRpcResponse> Responder<T, Raw> {
pub fn into_protocol<Q: Protocol>(self) -> Result<Responder<T, Q>, crate::Error> {
self.retag()
}
}
#[must_use]
pub struct ResponseRouter<T: JsonRpcResponse = serde_json::Value, P: Protocol = Raw> {
method: String,
id: HandleId<P>,
role_id: RoleId,
send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
reply_target: ResponseReplyTarget,
}
impl<T: JsonRpcResponse, P: Protocol> std::fmt::Debug for ResponseRouter<T, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResponseRouter")
.field("method", &self.method)
.field("id", &self.id)
.field("response_type", &std::any::type_name::<T>())
.finish_non_exhaustive()
}
}
impl ResponseRouter<serde_json::Value> {
fn new(pending_reply: PendingReply, dispatch: ResponseDispatch) -> Self {
let PendingReply {
id,
method,
role_id,
sender,
cancellation_disarm,
ordering,
response_route_hook,
accepts_lower_initialize_version: _,
} = pending_reply;
let reply_target = ResponseReplyTarget {
id: id.clone(),
method: method.clone(),
sender: Arc::new(Mutex::new(Some(sender))),
ordering,
dispatch,
};
let send_target = reply_target.clone();
cancellation_disarm.disarm();
let hook_method = method.clone();
Self {
method,
id: HandleId::raw_layer(id),
role_id,
send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
let response = match response {
Ok(value) => match response_route_hook {
Some(hook) => hook(&hook_method, &value).map(|()| value),
None => Ok(value),
},
Err(error) => Err(error),
};
send_target.route(response);
Ok(())
}),
reply_target,
}
}
}
impl<P: Protocol> ResponseRouter<serde_json::Value, P> {
pub fn cast<T: JsonRpcResponse>(self) -> ResponseRouter<T, P> {
self.wrap_params(move |method, value| match value {
Ok(value) => T::into_json(value, method),
Err(e) => Err(e),
})
}
}
impl<T: JsonRpcResponse, P: Protocol> ResponseRouter<T, P> {
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn id(&self) -> &P::RequestId {
self.id.typed()
}
#[must_use]
pub fn raw_id(&self) -> &RawJsonRpcId {
self.id.raw()
}
#[must_use]
pub fn role_id(&self) -> RoleId {
self.role_id.clone()
}
pub fn erase_to_json(self) -> ResponseRouter<serde_json::Value, P> {
self.wrap_params(|method, value| T::from_json(method, value?))
}
pub(crate) fn retag<Q: Protocol>(self) -> Result<ResponseRouter<T, Q>, crate::Error> {
Ok(ResponseRouter {
method: self.method,
id: self.id.into_protocol()?,
role_id: self.role_id,
send_fn: self.send_fn,
reply_target: self.reply_target,
})
}
pub fn into_raw(self) -> ResponseRouter<T, Raw> {
ResponseRouter {
method: self.method,
id: self.id.into_raw(),
role_id: self.role_id,
send_fn: self.send_fn,
reply_target: self.reply_target,
}
}
fn wrap_params<U: JsonRpcResponse>(
self,
wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
) -> ResponseRouter<U, P> {
let method = self.method.clone();
ResponseRouter {
method: self.method,
id: self.id,
role_id: self.role_id,
send_fn: Box::new(move |input: Result<U, crate::Error>| {
let t_value = wrap_fn(&method, input);
(self.send_fn)(t_value)
}),
reply_target: self.reply_target,
}
}
pub fn route_with_result(self, response: Result<T, crate::Error>) -> Result<(), crate::Error> {
tracing::debug!(id = ?self.id, "response routed to awaiter");
(self.send_fn)(response)
}
pub fn route(self, response: T) -> Result<(), crate::Error> {
self.route_with_result(Ok(response))
}
pub fn route_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
self.route_with_error(crate::util::internal_error(message))
}
pub fn route_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
tracing::debug!(id = ?self.id, error = ?crate::util::redacted_error(error.code), "error routed to awaiter");
self.route_with_result(Err(error))
}
}
impl<T: JsonRpcResponse> ResponseRouter<T, Raw> {
pub fn into_protocol<Q: Protocol>(self) -> Result<ResponseRouter<T, Q>, crate::Error> {
self.retag()
}
}
pub trait JsonRpcMessage: 'static + Debug + Sized + Send + Clone {
fn matches_method(method: &str) -> bool;
fn method(&self) -> &str;
fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error>;
fn parse_message(method: &str, params: &serde_json::Value) -> Result<Self, crate::Error>;
}
pub trait JsonRpcResponse: 'static + Debug + Sized + Send + Clone {
fn into_json(self, method: &str) -> Result<serde_json::Value, crate::Error>;
fn from_json(method: &str, value: serde_json::Value) -> Result<Self, crate::Error>;
}
impl JsonRpcResponse for serde_json::Value {
fn from_json(_method: &str, value: serde_json::Value) -> Result<Self, crate::Error> {
Ok(value)
}
fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
Ok(self)
}
}
pub trait JsonRpcNotification: JsonRpcMessage {}
pub trait JsonRpcRequest: JsonRpcMessage {
type Response: JsonRpcResponse;
}
#[derive(Debug)]
pub enum Dispatch<
Req: JsonRpcRequest = UntypedMessage,
Notif: JsonRpcNotification = UntypedMessage,
P: Protocol = Raw,
> {
Request(Req, Responder<Req::Response, P>),
Notification(Notif),
Response(
Result<Req::Response, crate::Error>,
ResponseRouter<Req::Response, P>,
),
}
impl<Req: JsonRpcRequest, Notif: JsonRpcNotification, P: Protocol> Dispatch<Req, Notif, P> {
pub fn map<Req1, Notif1>(
self,
map_request: impl FnOnce(
Req,
Responder<Req::Response, P>,
) -> (Req1, Responder<Req1::Response, P>),
map_notification: impl FnOnce(Notif) -> Notif1,
) -> Dispatch<Req1, Notif1, P>
where
Req1: JsonRpcRequest<Response = Req::Response>,
Notif1: JsonRpcNotification,
{
match self {
Dispatch::Request(request, responder) => {
let (new_request, new_responder) = map_request(request, responder);
Dispatch::Request(new_request, new_responder)
}
Dispatch::Notification(notification) => {
let new_notification = map_notification(notification);
Dispatch::Notification(new_notification)
}
Dispatch::Response(result, router) => Dispatch::Response(result, router),
}
}
pub fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
match self {
Dispatch::Request(request, _) => request.to_untyped_message(),
Dispatch::Notification(notification) => notification.to_untyped_message(),
Dispatch::Response(_, _) => Err(crate::util::internal_error(
"Response variant has no untyped message representation",
)),
}
}
pub fn into_untyped_dispatch(
self,
) -> Result<Dispatch<UntypedMessage, UntypedMessage, P>, crate::Error> {
match self {
Dispatch::Request(request, responder) => Ok(Dispatch::Request(
request.to_untyped_message()?,
responder.erase_to_json(),
)),
Dispatch::Notification(notification) => {
Ok(Dispatch::Notification(notification.to_untyped_message()?))
}
Dispatch::Response(_, _) => Err(crate::util::internal_error(
"cannot convert Response variant to untyped message context",
)),
}
}
pub fn id(&self) -> Option<&P::RequestId> {
match self {
Dispatch::Request(_, cx) => Some(cx.id()),
Dispatch::Notification(_) => None,
Dispatch::Response(_, cx) => Some(cx.id()),
}
}
#[must_use]
pub fn raw_id(&self) -> Option<&RawJsonRpcId> {
match self {
Dispatch::Request(_, cx) => Some(cx.raw_id()),
Dispatch::Notification(_) => None,
Dispatch::Response(_, cx) => Some(cx.raw_id()),
}
}
pub(crate) fn retag<Q: Protocol>(self) -> Result<Dispatch<Req, Notif, Q>, crate::Error> {
Ok(match self {
Dispatch::Request(request, responder) => Dispatch::Request(request, responder.retag()?),
Dispatch::Notification(notification) => Dispatch::Notification(notification),
Dispatch::Response(result, router) => Dispatch::Response(result, router.retag()?),
})
}
pub fn into_raw(self) -> Dispatch<Req, Notif, Raw> {
match self {
Dispatch::Request(request, responder) => {
Dispatch::Request(request, responder.into_raw())
}
Dispatch::Notification(notification) => Dispatch::Notification(notification),
Dispatch::Response(result, router) => Dispatch::Response(result, router.into_raw()),
}
}
fn handler_error_target(&self) -> Option<HandlerErrorTarget> {
match self {
Dispatch::Request(_, responder) => {
Some(HandlerErrorTarget::Request(responder.reply_target()))
}
Dispatch::Notification(_) => None,
Dispatch::Response(_, router) => {
Some(HandlerErrorTarget::Response(router.reply_target.clone()))
}
}
}
pub fn method(&self) -> &str {
match self {
Dispatch::Request(msg, _) => msg.method(),
Dispatch::Notification(msg) => msg.method(),
Dispatch::Response(_, cx) => cx.method(),
}
}
}
impl<Req: JsonRpcRequest, Notif: JsonRpcNotification> Dispatch<Req, Notif, Raw> {
pub fn into_protocol<Q: Protocol>(self) -> Result<Dispatch<Req, Notif, Q>, crate::Error> {
self.retag()
}
}
impl Dispatch {
#[tracing::instrument(skip(self), fields(Request = ?std::any::type_name::<Req>(), Notif = ?std::any::type_name::<Notif>()), level = "trace", ret)]
pub(crate) fn into_typed_dispatch<Req: JsonRpcRequest, Notif: JsonRpcNotification>(
self,
) -> Result<Result<Dispatch<Req, Notif>, Dispatch>, crate::Error> {
tracing::debug!(
method = self.method(),
id = ?self.raw_id(),
"into_typed_dispatch"
);
match self {
Dispatch::Request(message, responder) => {
if Req::matches_method(&message.method) {
match Req::parse_message(&message.method, &message.params) {
Ok(req) => {
tracing::trace!("parsed ok");
Ok(Ok(Dispatch::Request(req, responder.cast())))
}
Err(err) => {
tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
Err(err)
}
}
} else {
tracing::trace!("method doesn't match");
Ok(Err(Dispatch::Request(message, responder)))
}
}
Dispatch::Notification(message) => {
if Notif::matches_method(&message.method) {
match Notif::parse_message(&message.method, &message.params) {
Ok(notif) => {
tracing::trace!("parse ok");
Ok(Ok(Dispatch::Notification(notif)))
}
Err(err) => {
tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
Err(err)
}
}
} else {
tracing::trace!("method doesn't match");
Ok(Err(Dispatch::Notification(message)))
}
}
Dispatch::Response(result, cx) => {
let method = cx.method();
if Req::matches_method(method) {
let typed_result = match result {
Ok(value) => {
match <Req::Response as JsonRpcResponse>::from_json(method, value) {
Ok(parsed) => {
tracing::trace!("parse ok");
Ok(parsed)
}
Err(err) => {
tracing::trace!(err = ?crate::util::redacted_error(err.code), "parse error");
return Err(err);
}
}
}
Err(err) => {
tracing::trace!("error, passthrough");
Err(err)
}
};
Ok(Ok(Dispatch::Response(typed_result, cx.cast())))
} else {
tracing::trace!("method doesn't match");
Ok(Err(Dispatch::Response(result, cx)))
}
}
}
}
#[must_use]
pub fn has_field(&self, field_name: &str) -> bool {
self.message()
.and_then(|m| m.params().get(field_name))
.is_some()
}
pub(crate) fn has_session_id(&self) -> bool {
self.has_field("sessionId")
}
pub(crate) fn get_session_id(&self) -> Result<Option<SessionId>, crate::Error> {
let Some(message) = self.message() else {
return Ok(None);
};
let Some(value) = message.params().get("sessionId") else {
return Ok(None);
};
let session_id = serde_json::from_value(value.clone())?;
Ok(Some(session_id))
}
pub fn into_notification<N: JsonRpcNotification>(
self,
) -> Result<Result<N, Dispatch>, crate::Error> {
match self {
Dispatch::Notification(msg) => {
if !N::matches_method(&msg.method) {
return Ok(Err(Dispatch::Notification(msg)));
}
match N::parse_message(&msg.method, &msg.params) {
Ok(n) => Ok(Ok(n)),
Err(err) => Err(err),
}
}
Dispatch::Request(..) | Dispatch::Response(..) => Ok(Err(self)),
}
}
pub fn into_request<Req: JsonRpcRequest>(
self,
) -> Result<Result<(Req, Responder<Req::Response>), Dispatch>, crate::Error> {
match self {
Dispatch::Request(msg, responder) => {
if !Req::matches_method(&msg.method) {
return Ok(Err(Dispatch::Request(msg, responder)));
}
match Req::parse_message(&msg.method, &msg.params) {
Ok(req) => Ok(Ok((req, responder.cast()))),
Err(err) => Err(err),
}
}
Dispatch::Notification(..) | Dispatch::Response(..) => Ok(Err(self)),
}
}
}
impl<M: JsonRpcRequest + JsonRpcNotification> Dispatch<M, M> {
pub fn message(&self) -> Option<&M> {
match self {
Dispatch::Request(msg, _) | Dispatch::Notification(msg) => Some(msg),
Dispatch::Response(_, _) => None,
}
}
pub(crate) fn try_map_message(
self,
map_message: impl FnOnce(M) -> Result<M, crate::Error>,
) -> Result<Dispatch<M, M>, crate::Error> {
match self {
Dispatch::Request(request, cx) => Ok(Dispatch::Request(map_message(request)?, cx)),
Dispatch::Notification(notification) => {
Ok(Dispatch::<M, M>::Notification(map_message(notification)?))
}
Dispatch::Response(result, cx) => Ok(Dispatch::Response(result, cx)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UntypedMessage {
pub method: String,
pub params: serde_json::Value,
}
impl UntypedMessage {
pub fn new(method: &str, params: impl Serialize) -> Result<Self, crate::Error> {
let params = serde_json::to_value(params)?;
Ok(Self {
method: method.to_string(),
params,
})
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn params(&self) -> &serde_json::Value {
&self.params
}
#[must_use]
pub fn into_parts(self) -> (String, serde_json::Value) {
(self.method, self.params)
}
pub(crate) fn into_raw_jsonrpc_message(
self,
id: Option<RawJsonRpcId>,
) -> Result<RawJsonRpcMessage, crate::Error> {
let Self { method, params } = self;
match id {
Some(id) => RawJsonRpcMessage::request(method, params, id),
None => RawJsonRpcMessage::notification(method, params),
}
}
}
impl JsonRpcMessage for UntypedMessage {
fn matches_method(_method: &str) -> bool {
true
}
fn method(&self) -> &str {
&self.method
}
fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
Ok(self.clone())
}
fn parse_message(method: &str, params: &serde_json::Value) -> Result<Self, crate::Error> {
UntypedMessage::new(method, params)
}
}
impl JsonRpcRequest for UntypedMessage {
type Response = serde_json::Value;
}
impl JsonRpcNotification for UntypedMessage {}
#[must_use = "dropping a SentRequest asks the peer to cancel the request and \
discards the response; consume it with `block_task`, \
`on_receiving_result`, `forward_response_to`, or `detach`"]
pub struct SentRequest<T, P: Protocol = Raw> {
id: HandleId<P>,
method: String,
task_tx: TaskTx,
response_rx: oneshot::Receiver<ResponsePayload>,
to_result:
Box<dyn FnOnce(Result<serde_json::Value, crate::Error>) -> Result<T, crate::Error> + Send>,
cancellation: SentRequestCancellation,
response_ordering: ResponseOrdering,
cancellation_sources: Vec<RequestCancellation>,
}
#[must_use = "a prepared request is not sent until consumed with `block_task`, \
`on_receiving_result`, `forward_response_to`, or `detach`"]
pub struct PreparedRequest<T, P: Protocol = Raw> {
sent: SentRequest<T, P>,
publication: RequestPublication,
}
struct RequestPublication {
message: Result<OutgoingMessage, crate::Error>,
pending_reply: PendingReply,
message_tx: OutgoingMessageTx,
pending_replies: PendingRepliesRegistrar,
incoming_closed: IncomingClosed,
}
impl RequestPublication {
fn publish(self) -> Result<(), crate::Error> {
let message = if self.incoming_closed.is_closing() {
Err(incoming_transport_closed_error(&self.pending_reply.method))
} else {
self.message
};
let message = match message {
Ok(message) => message,
Err(error) => {
self.pending_reply.fail(error.clone());
return Err(error);
}
};
let OutgoingMessage::Request { id, method, .. } = &message else {
unreachable!();
};
let id = id.clone();
let method = method.clone();
let cancellation_disarm = self.pending_reply.cancellation_disarm.clone();
self.pending_replies
.subscribe(id.clone(), self.pending_reply, &self.incoming_closed)?;
if self.message_tx.unbounded_send(message).is_err() {
let error = if self.incoming_closed.is_closing() {
incoming_transport_closed_error(&method)
} else {
crate::util::internal_error(format!("failed to send outgoing request `{method}`"))
};
if let Some(pending_reply) = self.pending_replies.remove(&id) {
pending_reply.fail(error.clone());
}
return Err(error);
}
cancellation_disarm.arm();
Ok(())
}
}
impl<T: Debug, P: Protocol> Debug for PreparedRequest<T, P> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PreparedRequest")
.field("request", &self.sent)
.finish_non_exhaustive()
}
}
impl<T, P: Protocol> PreparedRequest<T, P> {
pub fn into_raw(self) -> PreparedRequest<T, Raw> {
PreparedRequest {
sent: self.sent.into_raw(),
publication: self.publication,
}
}
}
impl<T> PreparedRequest<T, Raw> {
pub fn into_protocol<Q: Protocol>(self) -> Result<PreparedRequest<T, Q>, crate::Error> {
Ok(PreparedRequest {
sent: self.sent.into_protocol()?,
publication: self.publication,
})
}
}
impl<T, P: Protocol> PreparedRequest<T, P> {
#[must_use]
pub fn id(&self) -> &P::RequestId {
self.sent.id()
}
#[must_use]
pub fn raw_id(&self) -> &RawJsonRpcId {
self.sent.raw_id()
}
#[must_use]
pub fn method(&self) -> &str {
self.sent.method()
}
#[must_use]
pub fn cancellation_handle(&self) -> RequestCancellationHandle {
self.sent.cancellation_handle()
}
pub fn map<U>(
self,
map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
) -> PreparedRequest<U, P>
where
T: 'static,
{
PreparedRequest {
sent: self.sent.map(map_fn),
publication: self.publication,
}
}
pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
self.sent = self.sent.forward_cancellation_from(source);
self
}
pub fn block_task(self) -> impl Future<Output = Result<T, crate::Error>> {
self.into_sent_request(false).block_task()
}
pub fn detach(self) -> Result<(), crate::Error> {
let result = self.publication.publish();
self.sent.detach();
result
}
#[track_caller]
pub fn on_receiving_result<F>(
self,
task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
self.consume_with(move |response| match response {
Ok(result) => Either::Left(task(result)),
Err(error) => Either::Right(future::ready(Err(error))),
})
}
#[track_caller]
pub fn on_receiving_ok_result<F, Q: Protocol>(
self,
responder: Responder<T, Q>,
task: impl FnOnce(T, Responder<T, Q>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
T: JsonRpcResponse,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
self.on_receiving_result(async move |result| match result {
Ok(value) => task(value, responder).await,
Err(error) => responder.respond_with_error(error),
})
}
#[track_caller]
pub fn forward_response_to<Q: Protocol>(
self,
responder: Responder<T, Q>,
) -> Result<(), crate::Error>
where
T: JsonRpcResponse,
{
self.forward_cancellation_from(responder.cancellation())
.consume_with(async move |response| {
responder.respond_with_result(response.unwrap_or_else(Err))
})
}
fn into_sent_request(self, ordered: bool) -> SentRequest<T, P> {
if ordered {
self.sent.response_ordering.mark_ordered();
}
drop(self.publication.publish());
self.sent
}
#[track_caller]
fn consume_with<F>(
self,
handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
let published_tx = self.register_consumer(handle)?;
drop(self.publication.publish());
drop(published_tx.send(self.sent));
Ok(())
}
#[track_caller]
fn register_consumer<F>(
&self,
handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
) -> Result<oneshot::Sender<SentRequest<T, P>>, crate::Error>
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
self.sent.response_ordering.mark_ordered();
let (published_tx, published_rx) = oneshot::channel::<SentRequest<T, P>>();
Task::new(Location::caller(), async move {
match published_rx.await {
Ok(sent) => sent.handle_response(handle).await,
Err(_) => Ok(()),
}
})
.spawn(&self.sent.task_tx)?;
Ok(published_tx)
}
}
#[derive(Clone, Debug)]
pub(crate) struct SentRequestCancellationDisarm {
state: Arc<AtomicU8>,
}
#[repr(u8)]
enum OutgoingCancellationState {
Unpublished,
Armed,
Disarmed,
}
impl SentRequestCancellationDisarm {
fn new() -> Self {
Self {
state: Arc::new(AtomicU8::new(OutgoingCancellationState::Unpublished as u8)),
}
}
fn disarm(&self) {
self.state
.store(OutgoingCancellationState::Disarmed as u8, Ordering::Release);
}
fn arm(&self) -> bool {
self.state
.compare_exchange(
OutgoingCancellationState::Unpublished as u8,
OutgoingCancellationState::Armed as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
fn take_armed(&self) -> bool {
self.state
.compare_exchange(
OutgoingCancellationState::Armed as u8,
OutgoingCancellationState::Disarmed as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
fn is_armed(&self) -> bool {
self.state.load(Ordering::Acquire) == OutgoingCancellationState::Armed as u8
}
}
#[derive(Clone)]
pub struct RequestCancellationHandle {
message_tx: OutgoingMessageTx,
request_id: RawJsonRpcId,
disarm: SentRequestCancellationDisarm,
}
impl RequestCancellationHandle {
pub fn cancel(&self) -> Result<(), crate::Error> {
if !self.disarm.take_armed() {
return Ok(());
}
let untyped = crate::v1::schema::CancelRequestNotification::new(self.request_id.clone())
.to_untyped_message()?;
send_raw_message(&self.message_tx, OutgoingMessage::Notification { untyped })
}
}
impl Debug for RequestCancellationHandle {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("RequestCancellationHandle")
.field("request_id", &self.request_id)
.field("armed", &self.disarm.is_armed())
.finish_non_exhaustive()
}
}
#[derive(Debug)]
struct SentRequestCancellation {
handle: RequestCancellationHandle,
cancel_on_drop: bool,
}
impl SentRequestCancellation {
fn new(message_tx: OutgoingMessageTx, request_id: RawJsonRpcId) -> Self {
Self {
handle: RequestCancellationHandle {
message_tx,
request_id,
disarm: SentRequestCancellationDisarm::new(),
},
cancel_on_drop: true,
}
}
fn disarm(&self) {
self.handle.disarm.disarm();
}
fn disarm_handle(&self) -> SentRequestCancellationDisarm {
self.handle.disarm.clone()
}
fn send(&self) -> Result<(), crate::Error> {
self.handle.cancel()
}
}
impl Drop for SentRequestCancellation {
fn drop(&mut self) {
if !self.cancel_on_drop {
return;
}
if let Err(error) = self.send() {
tracing::debug!(
error = ?crate::util::redacted_error(error.code),
"failed to auto-cancel dropped request"
);
}
}
}
async fn await_response_forwarding_cancellation(
response_rx: oneshot::Receiver<ResponsePayload>,
cancellation: &SentRequestCancellation,
sources: &[RequestCancellation],
) -> Result<ResponsePayload, oneshot::Canceled> {
let forward_cancellation = || {
if let Err(error) = cancellation.send() {
tracing::debug!(
error = ?crate::util::redacted_error(error.code),
"failed to forward cancellation to downstream request"
);
}
};
let response = if sources.is_empty() {
response_rx.await
} else if sources.iter().any(RequestCancellation::is_cancelled) {
forward_cancellation();
response_rx.await
} else {
let cancelled = sources.iter().map(|source| source.state.signal_rx.clone());
match future::select(future::select_all(cancelled), response_rx).await {
Either::Left((_, response_rx)) => {
forward_cancellation();
response_rx.await
}
Either::Right((response, _)) => response,
}
};
cancellation.disarm();
response
}
impl<T: Debug, P: Protocol> Debug for SentRequest<T, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("SentRequest");
debug
.field("id", &self.id)
.field("method", &self.method)
.field("task_tx", &self.task_tx)
.field("response_rx", &self.response_rx);
debug
.field("cancellation", &self.cancellation)
.field("cancellation_sources", &self.cancellation_sources);
debug.finish_non_exhaustive()
}
}
impl<P: Protocol> SentRequest<serde_json::Value, P> {
fn new(
id: HandleId<P>,
method: String,
task_tx: mpsc::UnboundedSender<Task>,
response_rx: oneshot::Receiver<ResponsePayload>,
cancellation: SentRequestCancellation,
response_ordering: ResponseOrdering,
) -> Self {
Self {
id,
method,
response_rx,
task_tx,
to_result: Box::new(|result| result),
cancellation,
response_ordering,
cancellation_sources: Vec::new(),
}
}
}
impl<T, P: Protocol> SentRequest<T, P> {
pub fn detach(mut self) {
self.cancellation.cancel_on_drop = false;
}
pub fn cancel(&self) -> Result<(), crate::Error> {
self.cancellation.send()
}
#[must_use]
pub fn cancellation_handle(&self) -> RequestCancellationHandle {
self.cancellation.handle.clone()
}
pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
self.cancellation_sources.push(source);
self
}
}
impl<T, P: Protocol> SentRequest<T, P> {
pub fn into_raw(self) -> SentRequest<T, Raw> {
self.retag_id(HandleId::into_raw)
}
fn retag_id<Q: Protocol>(
self,
retag: impl FnOnce(HandleId<P>) -> HandleId<Q>,
) -> SentRequest<T, Q> {
SentRequest {
id: retag(self.id),
method: self.method,
task_tx: self.task_tx,
response_rx: self.response_rx,
to_result: self.to_result,
cancellation: self.cancellation,
response_ordering: self.response_ordering,
cancellation_sources: self.cancellation_sources,
}
}
}
impl<T> SentRequest<T, Raw> {
pub fn into_protocol<Q: Protocol>(self) -> Result<SentRequest<T, Q>, crate::Error> {
let id = self.id.clone().into_protocol()?;
Ok(self.retag_id(|_| id))
}
}
impl<T, P: Protocol> SentRequest<T, P> {
#[must_use]
pub fn id(&self) -> &P::RequestId {
self.id.typed()
}
#[must_use]
pub fn raw_id(&self) -> &RawJsonRpcId {
self.id.raw()
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[cfg_attr(not(feature = "unstable_protocol_v2"), allow(dead_code))]
pub(crate) fn recover_error(
self,
recover: impl FnOnce(crate::Error) -> Result<T, crate::Error> + 'static + Send,
) -> SentRequest<T, P>
where
T: 'static,
{
let to_result = self.to_result;
SentRequest {
to_result: Box::new(move |result| match result {
Ok(value) => to_result(Ok(value)),
Err(error) => recover(error),
}),
..self
}
}
pub fn map<U>(
self,
map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
) -> SentRequest<U, P>
where
T: 'static,
{
SentRequest {
id: self.id,
method: self.method,
response_rx: self.response_rx,
task_tx: self.task_tx,
to_result: Box::new(move |result| map_fn((self.to_result)(result)?)),
cancellation: self.cancellation,
response_ordering: self.response_ordering,
cancellation_sources: self.cancellation_sources,
}
}
#[track_caller]
pub fn forward_response_to<Q: Protocol>(
self,
responder: Responder<T, Q>,
) -> Result<(), crate::Error>
where
T: JsonRpcResponse,
{
let this = self.forward_cancellation_from(responder.cancellation());
this.consume_with(async move |response| {
responder.respond_with_result(response.unwrap_or_else(Err))
})
}
#[track_caller]
fn consume_with<F>(
self,
handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
self.response_ordering.mark_ordered();
let task_tx = self.task_tx.clone();
Task::new(Location::caller(), self.handle_response(handle)).spawn(&task_tx)
}
fn handle_response<F>(
self,
handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
) -> impl Future<Output = Result<(), crate::Error>> + Send
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
let method = self.method;
let response_rx = self.response_rx;
let to_result = self.to_result;
let cancellation = self.cancellation;
let cancellation_sources = self.cancellation_sources;
async move {
let response = await_response_forwarding_cancellation(
response_rx,
&cancellation,
&cancellation_sources,
)
.await;
match response {
Ok(ResponsePayload { result, ack_tx }) => {
let typed_result = to_result(result);
let outcome = handle(Ok(typed_result)).await;
if let Some(tx) = ack_tx {
let _ = tx.send(());
}
outcome
}
Err(err) => {
handle(Err(crate::util::internal_error(format!(
"response to `{method}` never received: {err}"
))))
.await
}
}
}
}
pub async fn block_task(self) -> Result<T, crate::Error> {
let response = await_response_forwarding_cancellation(
self.response_rx,
&self.cancellation,
&self.cancellation_sources,
)
.await;
match response {
Ok(ResponsePayload { result, ack_tx }) => {
if let Some(tx) = ack_tx {
let _ = tx.send(());
}
(self.to_result)(result)
}
Err(err) => Err(crate::util::internal_error(format!(
"response to `{}` never received: {}",
self.method, err
))),
}
}
pub(crate) async fn block_task_with_ordered_result<U>(
self,
transform: impl FnOnce(Result<T, crate::Error>) -> Result<U, crate::Error>,
) -> Result<U, crate::Error> {
let response = await_response_forwarding_cancellation(
self.response_rx,
&self.cancellation,
&self.cancellation_sources,
)
.await;
let (result, ack_tx) = match response {
Ok(ResponsePayload { result, ack_tx }) => ((self.to_result)(result), ack_tx),
Err(error) => (
Err(crate::util::internal_error(format!(
"response to `{}` never received: {error}",
self.method
))),
None,
),
};
let outcome = transform(result);
if let Some(acknowledgment) = ack_tx {
let _ = acknowledgment.send(());
}
outcome
}
#[track_caller]
pub fn on_receiving_ok_result<F, Q: Protocol>(
self,
responder: Responder<T, Q>,
task: impl FnOnce(T, Responder<T, Q>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
T: JsonRpcResponse,
{
self.on_receiving_result(async move |result| match result {
Ok(value) => task(value, responder).await,
Err(err) => responder.respond_with_error(err),
})
}
#[track_caller]
pub fn on_receiving_result<F>(
self,
task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
) -> Result<(), crate::Error>
where
T: 'static,
F: Future<Output = Result<(), crate::Error>> + 'static + Send,
{
self.consume_with(move |response| match response {
Ok(result) => Either::Left(task(result)),
Err(err) => Either::Right(future::ready(Err(err))),
})
}
}
#[derive(Debug)]
pub struct Lines<OutgoingSink, IncomingStream> {
outgoing: OutgoingSink,
incoming: IncomingStream,
}
impl<OutgoingSink, IncomingStream> Lines<OutgoingSink, IncomingStream>
where
OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
{
pub fn new(outgoing: OutgoingSink, incoming: IncomingStream) -> Self {
Self { outgoing, incoming }
}
fn into_channel_transport(self) -> (Channel, crate::ConnectionDriver) {
let Self { outgoing, incoming } = self;
let (channel_for_caller, channel_for_lines) = Channel::duplex();
let Channel { mut rx, tx } = channel_for_lines;
let (finish_tx, finish_rx) = oneshot::channel();
let finish = async move {
if finish_rx.await.is_err() {
future::pending::<()>().await;
}
}
.boxed()
.shared();
let outgoing_frames = futures::stream::poll_fn({
let mut finish = finish.clone();
let mut finishing = false;
move |cx| {
if !finishing && std::pin::Pin::new(&mut finish).poll(cx).is_ready() {
rx.close();
finishing = true;
}
rx.poll_next_unpin(cx)
}
});
let discard_incoming = Arc::new(AtomicBool::new(false));
let incoming = incoming.filter_map({
let discard_incoming = discard_incoming.clone();
move |item| {
let discard = discard_incoming.load(Ordering::Acquire);
future::ready((!discard || item.is_err()).then_some(item))
}
});
let outgoing = transport_actor::transport_outgoing_lines_actor(outgoing_frames, outgoing)
.boxed()
.shared();
let serve_self = Box::pin({
let outgoing = outgoing.clone();
async move {
futures::try_join!(
outgoing,
transport_actor::transport_incoming_lines_actor(incoming, tx),
)?;
Ok(())
}
});
let server_future = crate::ConnectionDriver::with_finish(
async move {
match future::select(finish, serve_self).await {
Either::Left(((), serve_self)) => {
discard_incoming.store(true, Ordering::Release);
match future::select(serve_self, outgoing).await {
Either::Left((result, _)) | Either::Right((result, _)) => result,
}
}
Either::Right((result, _)) => result,
}
},
move || {
let _ = finish_tx.send(());
},
);
(channel_for_caller, server_future)
}
}
impl<OutgoingSink, IncomingStream, R: Role> ConnectTo<R> for Lines<OutgoingSink, IncomingStream>
where
OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
{
async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
let (channel, mut serve_self) = self.into_channel_transport();
let mut finish = serve_self
.take_finish()
.expect("built-in Lines transport supports explicit finishing");
let client_future = Box::pin(ConnectTo::<R>::connect_to(channel, client));
match futures::future::select(client_future, serve_self).await {
Either::Left((result, serve_self)) => {
result?;
finish.request();
serve_self.await
}
Either::Right((result, _)) => result,
}
}
fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
let (channel, driver) = self.into_channel_transport();
(channel, Some(driver))
}
}
#[derive(Debug)]
pub struct ByteStreams<OB, IB> {
outgoing: OB,
incoming: IB,
}
impl<OB, IB> ByteStreams<OB, IB>
where
OB: AsyncWrite + Send + 'static,
IB: AsyncRead + Send + 'static,
{
pub fn new(outgoing: OB, incoming: IB) -> Self {
Self { outgoing, incoming }
}
fn into_lines(
self,
) -> Lines<
impl futures::Sink<String, Error = std::io::Error> + Send + 'static,
impl futures::Stream<Item = std::io::Result<String>> + Send + 'static,
> {
use futures::AsyncBufReadExt;
use futures::io::BufReader;
let Self { outgoing, incoming } = self;
let incoming_lines = Box::pin(BufReader::new(incoming).lines());
let outgoing_lines = transport_actor::LineWriter::new(outgoing);
Lines::new(outgoing_lines, incoming_lines)
}
}
#[cfg(any(
all(
any(feature = "process", feature = "stdio"),
not(target_family = "wasm")
),
test
))]
pub(crate) async fn write_line<W>(writer: &mut W, line: String) -> std::io::Result<()>
where
W: AsyncWrite + Unpin + ?Sized,
{
use futures::AsyncWriteExt as _;
let mut bytes = line.into_bytes();
bytes.push(b'\n');
writer.write_all(&bytes).await?;
writer.flush().await
}
impl<OB, IB, R: Role> ConnectTo<R> for ByteStreams<OB, IB>
where
OB: AsyncWrite + Send + 'static,
IB: AsyncRead + Send + 'static,
{
async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
ConnectTo::<R>::connect_to(self.into_lines(), client).await
}
fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
ConnectTo::<R>::into_channel_and_future(self.into_lines())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Channel {
pub rx: mpsc::UnboundedReceiver<TransportFrame>,
pub tx: mpsc::UnboundedSender<TransportFrame>,
}
impl Channel {
#[must_use]
pub fn new(
rx: mpsc::UnboundedReceiver<TransportFrame>,
tx: mpsc::UnboundedSender<TransportFrame>,
) -> Self {
Self { rx, tx }
}
#[must_use]
pub fn into_parts(
self,
) -> (
mpsc::UnboundedReceiver<TransportFrame>,
mpsc::UnboundedSender<TransportFrame>,
) {
(self.rx, self.tx)
}
#[must_use]
pub fn duplex() -> (Self, Self) {
let (a_tx, b_rx) = mpsc::unbounded();
let (b_tx, a_rx) = mpsc::unbounded();
(Self { rx: a_rx, tx: a_tx }, Self { rx: b_rx, tx: b_tx })
}
pub(crate) async fn copy(mut self) -> Result<(), crate::Error> {
while let Some(frame) = self.rx.next().await {
self.tx
.unbounded_send(frame)
.map_err(crate::util::internal_error)?;
}
Ok(())
}
pub(crate) async fn copy_with_driver(
self,
driver: Option<crate::ConnectionDriver>,
) -> Result<(), crate::Error> {
self.copy_with_driver_until(driver, future::pending()).await
}
pub(crate) async fn copy_with_driver_until(
mut self,
mut driver: Option<crate::ConnectionDriver>,
stop_delivery: impl Future<Output = ()>,
) -> Result<(), crate::Error> {
let mut stop_delivery = pin!(stop_delivery);
let mut delivering = true;
let mut done = false;
loop {
let event = future::poll_fn(|cx| {
if delivering && stop_delivery.as_mut().poll(cx).is_ready() {
delivering = false;
}
if !done
&& let Some(driver) = driver.as_mut()
&& let std::task::Poll::Ready(result) = std::pin::Pin::new(driver).poll(cx)
{
return std::task::Poll::Ready(Either::Left(result));
}
if !delivering && driver.is_none() {
return std::task::Poll::Ready(Either::Right(None));
}
self.rx.poll_next_unpin(cx).map(Either::Right)
})
.await;
let frame = match event {
Either::Left(result) => {
result?;
done = true;
self.rx.close();
continue;
}
Either::Right(frame) => frame,
};
let Some(frame) = frame else {
break;
};
if delivering {
self.tx
.unbounded_send(frame)
.map_err(crate::util::internal_error)?;
}
}
drop(self);
if !done && let Some(driver) = driver {
driver.await?;
}
Ok(())
}
pub async fn bridge_with_inspection(
left: Self,
right: Self,
mut left_to_right: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
mut right_to_left: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
) -> Result<(), crate::Error> {
let Self {
rx: mut left_rx,
tx: left_tx,
} = left;
let Self {
rx: mut right_rx,
tx: right_tx,
} = right;
let left_to_right = async move {
while let Some(frame) = left_rx.next().await {
frame.inspect_messages(&mut left_to_right)?;
right_tx
.unbounded_send(frame)
.map_err(crate::util::internal_error)?;
}
Ok::<(), crate::Error>(())
};
let right_to_left = async move {
while let Some(frame) = right_rx.next().await {
frame.inspect_messages(&mut right_to_left)?;
left_tx
.unbounded_send(frame)
.map_err(crate::util::internal_error)?;
}
Ok::<(), crate::Error>(())
};
futures::try_join!(left_to_right, right_to_left)?;
Ok(())
}
}
impl<R: Role> ConnectTo<R> for Channel {
async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
let (client_channel, client_future) = client.into_channel_and_future();
let passive = client_future.is_none();
let outgoing = Box::pin(
Channel {
rx: client_channel.rx,
tx: self.tx,
}
.copy_with_driver(client_future),
);
let incoming = Box::pin(
Channel {
rx: self.rx,
tx: client_channel.tx,
}
.copy(),
);
if passive {
futures::try_join!(outgoing, incoming)?;
return Ok(());
}
match future::select(outgoing, incoming).await {
Either::Left((result, _)) => result,
Either::Right((result, outgoing)) => {
result?;
outgoing.await
}
}
}
fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
(self, None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Client;
#[cfg(feature = "unstable_protocol_v2")]
use crate::Proxy;
#[test]
fn raw_notification_round_trips_and_drops_unknown_envelope_members() {
let message: RawJsonRpcMessage = serde_json::from_value(serde_json::json!({
"jsonrpc": "2.0",
"method": "_vendor/ping",
"params": { "n": 1 },
"extra": true,
}))
.unwrap();
let mut params = serde_json::Map::new();
params.insert("n".into(), 1.into());
assert_eq!(
message,
RawJsonRpcMessage::Notification(RawJsonRpcNotification {
method: "_vendor/ping".into(),
params: Some(RawJsonRpcParams::Object(params)),
})
);
assert_eq!(
serde_json::to_value(&message).unwrap(),
serde_json::json!({
"jsonrpc": "2.0",
"method": "_vendor/ping",
"params": { "n": 1 },
})
);
let without_params =
RawJsonRpcMessage::notification("ping".into(), serde_json::Value::Null).unwrap();
assert_eq!(
serde_json::to_value(&without_params).unwrap(),
serde_json::json!({ "jsonrpc": "2.0", "method": "ping" })
);
}
#[test]
fn protected_cleanup_keeps_scoped_runners_polled_on_every_shutdown_path() {
#[derive(Clone, Copy, Debug)]
enum Stop {
ForegroundSuccess,
ForegroundError,
InputEof,
TransportError,
TaskError,
RunnerError,
SupervisorError,
}
struct Dropped(Arc<AtomicBool>);
impl Drop for Dropped {
fn drop(&mut self) {
self.0.store(true, Ordering::Release);
}
}
for stop in [
Stop::ForegroundSuccess,
Stop::ForegroundError,
Stop::InputEof,
Stop::TransportError,
Stop::TaskError,
Stop::RunnerError,
Stop::SupervisorError,
] {
let cleaned = Arc::new(AtomicBool::new(false));
let disposable_dropped = Arc::new(AtomicBool::new(false));
let close_finished = Arc::new(AtomicBool::new(false));
let (cleanup_tx, cleanup_rx) = oneshot::channel::<()>();
let (scoped_done_tx, scoped_done_rx) = completion_signal();
let (stop_tx, stop_rx) = oneshot::channel::<()>();
let stop_signal = stop_rx.map(|_| ()).boxed().shared();
let (incoming_tx, incoming_rx) = mpsc::unbounded();
let outgoing = futures::sink::unfold((), |(), _line: String| {
future::ready(Ok::<_, std::io::Error>(()))
});
let builder = Client
.v1()
.with_spawned({
let cleaned = cleaned.clone();
async move |cx: crate::v1::ConnectionTo<Agent>| {
cx.raw_connection().shutdown_requested().await;
cleanup_rx.await.unwrap();
cleaned.store(true, Ordering::Release);
let _ = scoped_done_tx.send(());
Ok(())
}
})
.with_spawned({
let stop_signal = stop_signal.clone();
async move |_cx| {
stop_signal.await;
if matches!(stop, Stop::RunnerError) {
Err(crate::Error::internal_error().data("runner failure"))
} else {
future::pending().await
}
}
})
.on_close({
let close_finished = close_finished.clone();
let scoped_done = scoped_done_rx.clone();
async move |cx: crate::v1::ConnectionTo<Agent>| {
cx.raw_connection().shutdown_requested().await;
assert!(!cx.is_incoming_closed());
scoped_done.await;
close_finished.store(true, Ordering::Release);
Ok(())
}
});
let (connection, driver) =
builder.into_connection_and_future(Lines::new(outgoing, incoming_rx), false, {
let stop_signal = stop_signal.clone();
async move |cx| {
if matches!(stop, Stop::InputEof) {
cx.incoming_closed().await;
return Ok(());
}
stop_signal.await;
match stop {
Stop::ForegroundSuccess | Stop::SupervisorError => Ok(()),
Stop::ForegroundError => {
Err(crate::Error::internal_error().data("foreground failure"))
}
_ => future::pending().await,
}
}
});
let disposable = Dropped(disposable_dropped.clone());
connection
.spawn(async move {
let _disposable = disposable;
future::pending().await
})
.unwrap();
connection
.spawn({
let stop_signal = stop_signal.clone();
async move {
stop_signal.await;
if matches!(stop, Stop::TaskError) {
Err(crate::Error::internal_error().data("task failure"))
} else {
future::pending().await
}
}
})
.unwrap();
connection
.spawn_protected({
let connection = connection.clone();
async move {
connection.shutdown_requested().await;
scoped_done_rx.await;
if matches!(stop, Stop::SupervisorError) {
Err(crate::Error::internal_error().data("supervisor failure"))
} else {
Ok(())
}
}
})
.unwrap();
let mut driver = Box::pin(driver);
assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
assert!(connection.shutdown_requested().now_or_never().is_none());
let _ = stop_tx.send(());
let incoming_tx = match stop {
Stop::InputEof => {
drop(incoming_tx);
None
}
Stop::TransportError => {
incoming_tx
.unbounded_send(Err(std::io::Error::other("transport failure")))
.unwrap();
Some(incoming_tx)
}
_ => Some(incoming_tx),
};
for _ in 0..10 {
assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
if connection.shutdown_requested().now_or_never().is_some() {
break;
}
}
assert!(
connection.shutdown_requested().now_or_never().is_some(),
"{stop:?}"
);
assert!(!cleaned.load(Ordering::Acquire), "{stop:?}");
assert!(!disposable_dropped.load(Ordering::Acquire), "{stop:?}");
cleanup_tx.send(()).unwrap();
let mut result = None;
for _ in 0..10 {
result = driver.as_mut().now_or_never();
if result.is_some() {
break;
}
}
let result =
result.unwrap_or_else(|| panic!("driver did not finish owned cleanup: {stop:?}"));
match stop {
Stop::ForegroundSuccess | Stop::InputEof => result.unwrap(),
_ => {
let error = result.expect_err("shutdown must preserve the first error");
let expected = match stop {
Stop::ForegroundError => "foreground failure",
Stop::TransportError => "transport failure",
Stop::TaskError => "task failure",
Stop::RunnerError => "runner failure",
Stop::SupervisorError => "supervisor failure",
_ => unreachable!(),
};
assert!(
error.data.unwrap().to_string().contains(expected),
"{stop:?}"
);
}
}
assert!(cleaned.load(Ordering::Acquire), "{stop:?}");
assert!(disposable_dropped.load(Ordering::Acquire), "{stop:?}");
assert_eq!(
close_finished.load(Ordering::Acquire),
matches!(stop, Stop::InputEof),
"{stop:?}",
);
assert!(connection.spawn_protected(async { Ok(()) }).is_err());
drop(incoming_tx);
}
}
#[test]
fn protected_operation_acknowledgments_are_reaped_and_join_seals_registration() {
let (connection, _message_rx, _pending_replies) = connection_for_response_hook_tests();
let (task_tx, mut task_rx) = mpsc::unbounded();
let connection = ConnectionTo {
task_tx,
..connection
};
for _ in 0..100 {
connection.spawn_protected(async { Ok(()) }).unwrap();
assert_eq!(
connection
.protected_operations
.lock()
.unwrap()
.pending
.len(),
1
);
let task = task_rx.next().now_or_never().unwrap().unwrap();
futures::executor::block_on(task.run_for_test()).unwrap();
}
assert!(
connection
.wait_protected_operations()
.now_or_never()
.is_some()
);
assert!(
connection
.wait_protected_operations()
.now_or_never()
.is_some()
);
assert!(
connection
.protected_operations
.lock()
.unwrap()
.pending
.is_empty()
);
assert!(connection.spawn_protected(async { Ok(()) }).is_err());
assert!(task_rx.next().now_or_never().is_none());
}
#[test]
fn dropping_unused_finish_signal_preserves_physical_half_closes() {
let outgoing = futures::sink::unfold((), |(), _line: String| {
future::ready(Ok::<_, std::io::Error>(()))
});
let (incoming_tx, incoming_rx) = mpsc::unbounded();
let (Channel { mut rx, tx }, mut driver) =
Lines::new(outgoing, incoming_rx).into_channel_transport();
drop(
driver
.take_finish()
.expect("built-in Lines driver is finishable"),
);
drop(tx);
assert!((&mut driver).now_or_never().is_none());
incoming_tx
.unbounded_send(Ok(
r#"{"jsonrpc":"2.0","method":"test/after-output-eof"}"#.into()
))
.unwrap();
assert!((&mut driver).now_or_never().is_none());
assert!(rx.next().now_or_never().unwrap().is_some());
drop(incoming_tx);
futures::executor::block_on(driver).unwrap();
assert!(rx.next().now_or_never().unwrap().is_none());
}
#[test]
fn explicit_physical_finish_does_not_hide_a_ready_read_error() {
let outgoing = futures::sink::unfold((), |(), _line: String| {
future::ready(Ok::<_, std::io::Error>(()))
});
let incoming = futures::stream::iter([Err(std::io::Error::other("finish read failed"))]);
let (_channel, mut driver) = Lines::new(outgoing, incoming).into_channel_transport();
assert!(driver.request_finish());
let error = futures::executor::block_on(driver).unwrap_err();
assert_eq!(
error
.data
.and_then(|value| value.as_str().map(str::to_owned)),
Some("finish read failed".into())
);
}
#[cfg(feature = "unstable_protocol_v2")]
fn connection_with_task_receiver() -> (
ConnectionTo<crate::role::UntypedRole>,
mpsc::UnboundedReceiver<Task>,
) {
let (message_tx, _message_rx) = mpsc::unbounded();
let (task_tx, task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
(
ConnectionTo::new(
crate::role::UntypedRole,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::disabled(),
),
task_rx,
)
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_builder_exposes_typed_context_to_user_callbacks() {
fn assert_v2_context(_connection: &crate::v2::ConnectionTo<Agent>) {}
let _builder = Client
.v2()
.on_receive_request(
async |_request: UntypedMessage, _responder, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_request!(),
)
.on_receive_notification(
async |_notification: UntypedMessage, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_notification!(),
)
.on_receive_dispatch(
async |_dispatch: Dispatch<UntypedMessage, UntypedMessage, V2>, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_dispatch!(),
)
.on_receive_request_from(
Agent,
async |_request: UntypedMessage, _responder, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_request!(),
)
.on_receive_notification_from(
Agent,
async |_notification: UntypedMessage, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_notification!(),
)
.on_receive_dispatch_from(
Agent,
async |_dispatch: Dispatch<UntypedMessage, UntypedMessage, V2>, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_dispatch!(),
)
.with_spawned(async |connection| {
assert_v2_context(&connection);
Ok(())
})
.on_close(async |connection| {
assert_v2_context(&connection);
Ok(())
});
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn proxy_builders_negotiate_proxy_initialize_like_agents() -> Result<(), crate::Error> {
use crate::schema::ProtocolVersion;
let mode = Proxy.v1().protocol_mode;
assert_eq!(mode.api_protocol_version(), Some(ProtocolVersion::V1));
let request = ProtocolCompat::new(mode).incoming_message(UntypedMessage::new(
"proxy/initialize",
serde_json::json!({ "protocolVersion": ProtocolVersion::V2 }),
)?)?;
assert_eq!(request.params()["protocolVersion"], serde_json::json!(1));
let mode = Proxy.v2().protocol_mode;
assert_eq!(mode.api_protocol_version(), Some(ProtocolVersion::V2));
let error = ProtocolCompat::new(mode)
.incoming_message(UntypedMessage::new(
"proxy/initialize",
serde_json::json!({ "protocolVersion": ProtocolVersion::V1 }),
)?)
.expect_err("a v2 proxy builder must reject v1");
assert_eq!(i32::from(error.code), -32022);
Ok(())
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_proxy_rejects_explicitly_prewrapped_initialize_request() {
let (message_tx, message_rx) = mpsc::unbounded();
let (task_tx, _task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
let connection = ConnectionTo::new(
crate::Conductor,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::v2_proxy(),
);
let request = crate::schema::SuccessorMessage::new(
UntypedMessage::new(
"initialize",
serde_json::json!({ "protocolVersion": crate::schema::ProtocolVersion::V1 }),
)
.expect("test initialize request should serialize"),
);
let sent = connection.send_request_to(Agent, request);
let (transport_tx, mut transport_rx) = mpsc::unbounded();
let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
message_rx,
pending_replies,
transport_tx,
ProtocolCompat::new(ProtocolMode::v2_proxy()),
future::pending::<()>().boxed().shared(),
));
assert!(
actor.as_mut().now_or_never().is_none(),
"the outgoing actor should continue after rejecting the request"
);
assert!(
transport_rx.next().now_or_never().is_none(),
"an explicitly prewrapped initialize must not reach the transport"
);
let error = futures::executor::block_on(sent.block_task())
.expect_err("connection routing must own successor wrapping");
let data = error
.data
.as_ref()
.and_then(|data| data.as_str())
.unwrap_or_default();
assert!(data.contains("logical `initialize`"), "{error:?}");
assert!(data.contains("proxy/successor"), "{error:?}");
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_proxy_builder_exposes_typed_context_to_user_callbacks() {
fn assert_v2_context(_connection: &crate::v2::ConnectionTo<crate::Conductor>) {}
let _builder = Proxy
.v2()
.on_receive_request_from(
Client,
async |_request: UntypedMessage, _responder, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_request!(),
)
.on_receive_notification_from(
Agent,
async |_notification: UntypedMessage, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_notification!(),
)
.on_receive_dispatch_from(
Client,
async |_dispatch: Dispatch<UntypedMessage, UntypedMessage, V2>, connection| {
assert_v2_context(&connection);
Ok(())
},
crate::on_receive_dispatch!(),
)
.with_spawned(async |connection| {
assert_v2_context(&connection);
Ok(())
})
.on_close(async |connection| {
assert_v2_context(&connection);
Ok(())
});
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn raw_connection_spawns_v2_builder_with_typed_child_callback() {
let (parent, mut task_rx) = connection_with_task_receiver();
let (transport, _peer) = Channel::duplex();
let (callback_tx, callback_rx) = oneshot::channel();
let child: crate::v2::ConnectionTo<Agent> = parent
.spawn_connection(
Client.v2().with_spawned(
async move |_connection: crate::v2::ConnectionTo<Agent>| {
callback_tx.send(()).map_err(|()| {
crate::util::internal_error("typed child callback receiver was dropped")
})
},
),
transport,
)
.expect("v2 child connection should be spawned");
let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
.expect("child connection task should already be queued")
.expect("parent task queue should remain open");
futures::executor::block_on(async {
match future::select(Box::pin(task.run_for_test()), Box::pin(callback_rx)).await {
Either::Right((Ok(()), child_task)) => drop(child_task),
Either::Right((Err(error), _)) => {
panic!("typed child callback sender was dropped: {error}")
}
Either::Left((result, _)) => {
panic!("child connection stopped before its typed callback ran: {result:?}")
}
}
});
drop(child);
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn raw_connection_returns_raw_context_for_raw_builder() {
let (parent, mut task_rx) = connection_with_task_receiver();
let (transport, _peer) = Channel::duplex();
let child: ConnectionTo<Agent> = parent
.spawn_connection(Builder::new(Client), transport)
.expect("raw child connection should be spawned");
assert_eq!(child.protocol_version(), None);
let child_task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
.expect("child connection task should already be queued")
.expect("parent task queue should remain open");
drop((child, child_task));
}
fn connection_with_dynamic_handler_receiver() -> (
ConnectionTo<crate::role::UntypedRole>,
mpsc::UnboundedReceiver<DynamicHandlerMessage<crate::role::UntypedRole>>,
) {
let (message_tx, _message_rx) = mpsc::unbounded();
let (task_tx, _task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
(
ConnectionTo::new(
crate::role::UntypedRole,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::disabled(),
),
dynamic_handler_rx,
)
}
struct ClaimingDynamicHandler;
impl HandleDispatchFrom<crate::role::UntypedRole> for ClaimingDynamicHandler {
fn handle_dispatch_from(
&mut self,
_message: Dispatch,
_connection: ConnectionTo<crate::role::UntypedRole>,
) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
future::ready(Ok(Handled::Yes))
}
fn describe_chain(&self) -> impl Debug {
"ClaimingDynamicHandler"
}
}
fn connection_for_response_hook_tests() -> (
ConnectionTo<crate::role::UntypedRole>,
mpsc::UnboundedReceiver<OutgoingMessage>,
PendingReplies,
) {
let (message_tx, message_rx) = mpsc::unbounded();
let (task_tx, _task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
(
ConnectionTo::new(
crate::role::UntypedRole,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::disabled(),
),
message_rx,
pending_replies,
)
}
#[cfg(feature = "unstable_protocol_v2")]
fn route_test_response(
request_id: RawJsonRpcId,
pending_replies: &PendingReplies,
result: Result<serde_json::Value, crate::Error>,
) {
let pending_reply = pending_replies
.remove(&request_id)
.expect("the request should have a pending reply");
let (dispatch, _) = incoming_actor::dispatch_from_response(pending_reply, result);
let Dispatch::Response(result, router) = dispatch else {
panic!("expected a response dispatch");
};
router
.route_with_result(result)
.expect("response should route to the pending request");
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn handle_ids_are_typed_by_protocol_and_match_the_wire_id() {
let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
let raw_sent = connection.send_request_to(
crate::role::UntypedRole,
UntypedMessage::new("probe", serde_json::json!({}))
.expect("test request should serialize"),
);
let raw_sent_id: &RawJsonRpcId = raw_sent.id();
assert_eq!(raw_sent_id, raw_sent.raw_id());
raw_sent.detach();
let v2_connection = crate::v2::ConnectionTo { inner: connection };
let sent = v2_connection.send_request_to(
crate::role::UntypedRole,
UntypedMessage::new("probe", serde_json::json!({}))
.expect("test request should serialize"),
);
let v2_id: crate::v2::schema::RequestId = sent.id().clone();
let raw_id = sent.raw_id().clone();
assert_eq!(
serde_json::to_value(&v2_id).expect("v2 id should serialize"),
serde_json::to_value(&raw_id).expect("raw id should serialize"),
);
let pending_reply = pending_replies
.remove(&raw_id)
.expect("the request should have a pending reply");
let (dispatch, _) =
incoming_actor::dispatch_from_response(pending_reply, Ok(serde_json::json!({})));
assert_eq!(dispatch.raw_id(), Some(&raw_id));
let dispatch = dispatch
.into_protocol::<V2>()
.expect("SDK-assigned ids are valid v2 ids");
assert_eq!(dispatch.id(), Some(&v2_id));
let Dispatch::Response(_, router) = dispatch else {
panic!("expected a response dispatch");
};
assert_eq!(router.id(), &v2_id);
assert_eq!(router.raw_id(), &raw_id);
sent.detach();
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn response_hook_runs_when_success_is_routed_before_consumption() {
let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
let hook_ran = Arc::new(AtomicBool::new(false));
let sent = connection.send_request_to_with_response_hook_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("hooked", serde_json::json!({}))
.expect("test request should serialize"),
future::ready(Ok(())),
{
let hook_ran = hook_ran.clone();
move |response| {
assert_eq!(response, &serde_json::json!({"ok": true}));
hook_ran.store(true, Ordering::Release);
Ok(())
}
},
);
let request_id = sent.raw_id().clone();
route_test_response(
request_id,
&pending_replies,
Ok(serde_json::json!({"ok": true})),
);
assert!(hook_ran.load(Ordering::Acquire));
assert_eq!(
futures::executor::block_on(sent.block_task())
.expect("routed response should remain consumable"),
serde_json::json!({"ok": true})
);
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn response_hook_skips_errors_but_outlives_a_dropped_consumer() {
let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
let peer_error_hook_ran = Arc::new(AtomicBool::new(false));
let peer_error = connection.send_request_to_with_response_hook_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("peer-error", serde_json::json!({}))
.expect("test request should serialize"),
future::ready(Ok(())),
{
let hook_ran = peer_error_hook_ran.clone();
move |_| {
hook_ran.store(true, Ordering::Release);
Ok(())
}
},
);
let peer_error_id = peer_error.raw_id().clone();
route_test_response(
peer_error_id,
&pending_replies,
Err(crate::Error::invalid_request()),
);
assert!(
futures::executor::block_on(peer_error.block_task()).is_err(),
"the peer error should reach the consumer"
);
assert!(!peer_error_hook_ran.load(Ordering::Acquire));
let dropped_hook_ran = Arc::new(AtomicBool::new(false));
let dropped = connection.send_request_to_with_response_hook_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("dropped", serde_json::json!({}))
.expect("test request should serialize"),
future::ready(Ok(())),
{
let hook_ran = dropped_hook_ran.clone();
move |_| {
hook_ran.store(true, Ordering::Release);
Ok(())
}
},
);
let dropped_id = dropped.raw_id().clone();
drop(dropped);
route_test_response(
dropped_id,
&pending_replies,
Ok(serde_json::json!({"ok": true})),
);
assert!(dropped_hook_ran.load(Ordering::Acquire));
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn response_hook_failure_replaces_the_success_result() {
let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
let sent = connection.send_request_to_with_response_hook_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("hook-failure", serde_json::json!({}))
.expect("test request should serialize"),
future::ready(Ok(())),
|_| Err(crate::Error::internal_error().data("response hook failed")),
);
let request_id = sent.raw_id().clone();
route_test_response(
request_id,
&pending_replies,
Ok(serde_json::json!({"ok": true})),
);
let error = futures::executor::block_on(sent.block_task())
.expect_err("the hook failure should replace the successful response");
assert_eq!(error.code, crate::ErrorCode::INTERNAL_ERROR);
assert_eq!(error.data, Some(serde_json::json!("response hook failed")));
}
#[test]
fn ordered_request_waits_for_readiness_before_publication() {
let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
let (ready_tx, ready_rx) = oneshot::channel();
let sent = connection.send_ordered_request_to_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("after-ready", serde_json::json!({}))
.expect("test request should serialize"),
async move { ready_rx.await.map_err(crate::Error::into_internal_error) },
);
let (transport_tx, mut transport_rx) = mpsc::unbounded();
let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
message_rx,
pending_replies,
transport_tx,
ProtocolCompat::new(ProtocolMode::disabled()),
future::pending::<()>().boxed().shared(),
));
assert!(
actor.as_mut().now_or_never().is_none(),
"the outgoing actor should wait for readiness"
);
assert!(
transport_rx.next().now_or_never().is_none(),
"the request must not be published before readiness"
);
ready_tx
.send(())
.expect("the readiness receiver should remain active");
assert!(
actor.as_mut().now_or_never().is_none(),
"the outgoing actor should continue serving after publication"
);
let frame = transport_rx
.next()
.now_or_never()
.expect("the ready request should be published")
.expect("the transport queue should remain open");
assert!(matches!(
frame,
TransportFrame::Single(RawJsonRpcMessage::Request(_))
));
drop(sent);
}
#[test]
fn foreground_finish_settles_unready_requests_and_preserves_ready_output_fifo() {
let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
let unready = connection.send_ordered_request_to_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("unready", serde_json::json!({})).unwrap(),
future::pending(),
);
let unready_id = unready.raw_id().clone();
send_raw_message(
&connection.message_tx,
OutgoingMessage::Notification {
untyped: UntypedMessage::new("first", serde_json::json!({})).unwrap(),
},
)
.unwrap();
let ready = connection.send_ordered_request_to_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("ready", serde_json::json!({})).unwrap(),
future::ready(Ok(())),
);
let unready_after = connection.send_ordered_request_to_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("unready-after", serde_json::json!({})).unwrap(),
future::pending(),
);
let unready_after_id = unready_after.raw_id().clone();
send_raw_message(
&connection.message_tx,
OutgoingMessage::Notification {
untyped: UntypedMessage::new("last", serde_json::json!({})).unwrap(),
},
)
.unwrap();
let (done_tx, done_rx) = oneshot::channel();
send_raw_message(
&connection.message_tx,
OutgoingMessage::CloseAfterDraining { done: done_tx },
)
.unwrap();
let (transport_tx, transport_rx) = mpsc::unbounded();
futures::executor::block_on(outgoing_actor::outgoing_protocol_actor(
message_rx,
pending_replies.clone(),
transport_tx,
ProtocolCompat::new(ProtocolMode::disabled()),
future::ready(()).boxed().shared(),
))
.unwrap();
futures::executor::block_on(done_rx).unwrap();
let error = futures::executor::block_on(unready.block_task())
.expect_err("an unresolved gate must explicitly fail its consumer");
assert!(
error
.data
.unwrap()
.to_string()
.contains("foreground completed before outgoing request readiness")
);
assert!(!pending_replies.contains(&unready_id));
let error = futures::executor::block_on(unready_after.block_task())
.expect_err("each unresolved gate must fail without repolling a consumed signal");
assert!(
error
.data
.unwrap()
.to_string()
.contains("foreground completed before outgoing request readiness")
);
assert!(!pending_replies.contains(&unready_after_id));
assert!(pending_replies.contains(ready.raw_id()));
let frames = futures::executor::block_on(transport_rx.collect::<Vec<_>>());
let methods = frames
.into_iter()
.map(|frame| match frame {
TransportFrame::Single(RawJsonRpcMessage::Notification(message)) => {
message.method.to_string()
}
TransportFrame::Single(RawJsonRpcMessage::Request(message)) => {
message.method.to_string()
}
_ => panic!("expected ready request/notification output"),
})
.collect::<Vec<_>>();
assert_eq!(methods, ["first", "ready", "last"]);
}
#[test]
fn ordered_blocking_transform_precedes_response_acknowledgment() {
let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
let sent = connection.send_ordered_request_to::<Raw, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("ordered-transform", serde_json::json!({}))
.expect("test request should serialize"),
);
let request_id = sent.raw_id().clone();
let pending_reply = pending_replies
.remove(&request_id)
.expect("the request should have a pending reply");
let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
pending_reply,
Err(crate::Error::invalid_params()),
);
let Dispatch::Response(result, router) = dispatch else {
panic!("expected a response dispatch");
};
router
.route_with_result(result)
.expect("response should route to the pending request");
let acknowledgment = response_dispatch
.complete()
.expect("an ordered response should wait for acknowledgment");
let acknowledgment = Arc::new(Mutex::new(Some(acknowledgment)));
let acknowledgment_probe = acknowledgment.clone();
let error =
futures::executor::block_on(sent.block_task_with_ordered_result(move |result| {
assert_eq!(
acknowledgment_probe
.lock()
.expect("acknowledgment mutex poisoned")
.as_mut()
.expect("acknowledgment receiver should remain available")
.try_recv()
.expect("acknowledgment sender should remain open"),
None,
"the ordered response was acknowledged before its transform"
);
result
}))
.expect_err("the peer error should survive the ordered transform");
assert_eq!(error.code, crate::ErrorCode::INVALID_PARAMS);
let acknowledgment = acknowledgment
.lock()
.expect("acknowledgment mutex poisoned")
.take()
.expect("acknowledgment receiver should remain available");
futures::executor::block_on(acknowledgment)
.expect("the transform should release the ordered response");
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn outgoing_request_readiness_failure_rejects_without_publication() {
let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
let hook_ran = Arc::new(AtomicBool::new(false));
let sent = connection.send_request_to_with_response_hook_after::<Raw, _, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("never-published", serde_json::json!({}))
.expect("test request should serialize"),
future::ready(Err(crate::Error::internal_error().data("readiness failed"))),
{
let hook_ran = hook_ran.clone();
move |_| {
hook_ran.store(true, Ordering::Release);
Ok(())
}
},
);
let (transport_tx, mut transport_rx) = mpsc::unbounded();
let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
message_rx,
pending_replies,
transport_tx,
ProtocolCompat::new(ProtocolMode::disabled()),
future::pending::<()>().boxed().shared(),
));
assert!(
actor.as_mut().now_or_never().is_none(),
"the outgoing actor should continue serving after rejecting the request"
);
assert!(
transport_rx.next().now_or_never().is_none(),
"a request whose readiness failed must not be published"
);
let error = futures::executor::block_on(sent.block_task())
.expect_err("the readiness error should reach the request consumer");
assert_eq!(error.code, crate::ErrorCode::INTERNAL_ERROR);
assert_eq!(error.data, Some(serde_json::json!("readiness failed")));
assert!(!hook_ran.load(Ordering::Acquire));
}
#[test]
fn ordered_request_is_marked_before_entering_outgoing_queue() {
let (message_tx, mut message_rx) = mpsc::unbounded();
let (task_tx, mut task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
let connection = ConnectionTo::new(
crate::role::UntypedRole,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::disabled(),
);
let sent = connection.send_ordered_request_to::<Raw, _, _>(
crate::role::UntypedRole,
UntypedMessage::new("ordered", serde_json::json!({}))
.expect("test request should serialize"),
);
let request_id = sent.raw_id().clone();
let message = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut message_rx))
.expect("outgoing request should already be queued")
.expect("outgoing request queue should remain open");
let OutgoingMessage::Request { id, .. } = message else {
panic!("expected an outgoing request");
};
assert_eq!(id, request_id);
let pending_reply = pending_replies
.remove(&request_id)
.expect("the request should have a pending reply");
assert!(
pending_reply.ordering.is_ordered(),
"the response ordering barrier must be installed before publication"
);
let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
pending_reply,
Ok(serde_json::json!({"ok": true})),
);
let Dispatch::Response(result, router) = dispatch else {
panic!("expected a response dispatch");
};
router
.route_with_result(result)
.expect("response should route to the pending request");
let acknowledgment = response_dispatch
.complete()
.expect("an ordered response should require acknowledgment");
let callback_ran = Arc::new(AtomicBool::new(false));
sent.on_receiving_result({
let callback_ran = callback_ran.clone();
async move |result| {
assert_eq!(result?, serde_json::json!({"ok": true}));
callback_ran.store(true, Ordering::Release);
Ok(())
}
})
.expect("ordered callback should be scheduled");
let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
.expect("callback task should already be queued")
.expect("callback task queue should remain open");
futures::executor::block_on(task.run_for_test()).expect("callback task should succeed");
futures::executor::block_on(acknowledgment)
.expect("callback completion should acknowledge dispatch");
assert!(callback_ran.load(Ordering::Acquire));
}
fn next_dynamic_handler_message<Counterpart: Role>(
receiver: &mut mpsc::UnboundedReceiver<DynamicHandlerMessage<Counterpart>>,
) -> Option<DynamicHandlerMessage<Counterpart>> {
futures::FutureExt::now_or_never(futures::StreamExt::next(receiver))
.expect("dynamic-handler receiver should be ready")
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_dynamic_handler_guard_registers_and_removes_handler() {
let (message_tx, _message_rx) = mpsc::unbounded();
let (task_tx, _task_rx) = mpsc::unbounded();
let (dynamic_handler_tx, mut dynamic_handler_rx) = mpsc::unbounded();
let transport_completion: SharedTransportCompletion =
future::ready(Ok::<(), crate::Error>(())).boxed().shared();
let pending_replies = PendingReplies::default();
let connection = crate::v2::ConnectionTo {
inner: ConnectionTo::new(
Agent,
message_tx,
task_tx,
dynamic_handler_tx,
transport_completion,
pending_replies.registrar(),
ProtocolMode::v2_client(),
),
};
let guard = connection
.add_dynamic_handler(NullHandler)
.expect("v2 dynamic handler should register");
let added_uuid = match next_dynamic_handler_message(&mut dynamic_handler_rx) {
Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _, _)) => uuid,
other => panic!("expected v2 handler registration, got {other:?}"),
};
drop(guard);
match next_dynamic_handler_message(&mut dynamic_handler_rx) {
Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
assert_eq!(uuid, added_uuid);
}
other => panic!("expected v2 handler removal, got {other:?}"),
}
}
#[test]
fn dropping_dynamic_handler_guard_unregisters_handler() {
let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
let guard = connection.add_dynamic_handler(NullHandler).unwrap();
let added_uuid = match next_dynamic_handler_message(&mut receiver) {
Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _, _)) => uuid,
other => panic!("expected handler registration, got {other:?}"),
};
drop(guard);
match next_dynamic_handler_message(&mut receiver) {
Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
assert_eq!(uuid, added_uuid);
}
other => panic!("expected handler removal, got {other:?}"),
}
}
#[test]
fn dropping_dynamic_handler_guard_deactivates_queued_handler_immediately() {
let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
let guard = connection
.add_dynamic_handler(ClaimingDynamicHandler)
.expect("dynamic handler should register");
let mut handler = match next_dynamic_handler_message(&mut receiver) {
Some(DynamicHandlerMessage::AddDynamicHandler(_, handler, _)) => handler,
other => panic!("expected handler registration, got {other:?}"),
};
drop(guard);
let message = Dispatch::Notification(
UntypedMessage::new("stale", serde_json::json!({}))
.expect("test notification should serialize"),
);
let handled =
futures::executor::block_on(handler.dyn_handle_dispatch_from(message, connection))
.expect("inactive handler should decline cleanly");
assert!(matches!(handled, Handled::No { retry: false, .. }));
}
#[test]
fn dynamic_handler_barrier_acknowledges_prior_messages() {
let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
let _guard = connection.add_dynamic_handler(NullHandler).unwrap();
let mut barrier = Box::pin(connection.dynamic_handler_barrier());
assert!(matches!(
next_dynamic_handler_message(&mut receiver),
Some(DynamicHandlerMessage::AddDynamicHandler(_, _, _))
));
assert!(
barrier.as_mut().now_or_never().is_none(),
"the barrier must wait for the incoming actor"
);
let acknowledgment = match next_dynamic_handler_message(&mut receiver) {
Some(DynamicHandlerMessage::AcknowledgedBarrier(acknowledgment)) => acknowledgment,
other => panic!("expected acknowledged barrier, got {other:?}"),
};
acknowledgment
.send(())
.expect("the barrier receiver should remain active");
futures::executor::block_on(barrier)
.expect("the acknowledged dynamic-handler barrier should complete");
}
#[test]
fn detaching_dynamic_handler_guard_does_not_leak_connection() {
let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
let guard = connection.add_dynamic_handler(NullHandler).unwrap();
assert!(matches!(
next_dynamic_handler_message(&mut receiver),
Some(DynamicHandlerMessage::AddDynamicHandler(_, _, _))
));
drop(connection);
guard.detach();
assert!(
next_dynamic_handler_message(&mut receiver).is_none(),
"detach should retain the handler without retaining a connection sender"
);
}
#[tokio::test]
async fn write_line_flushes_buffered_writers() {
let mut writer =
futures::io::BufWriter::with_capacity(4096, futures::io::Cursor::new(Vec::new()));
write_line(&mut writer, "message".into()).await.unwrap();
assert_eq!(writer.into_inner().into_inner(), b"message\n");
}
#[test]
fn peel_successor_envelopes_returns_plain_messages_unchanged() {
let params = serde_json::json!({ "key": "value" });
let (method, peeled) = peel_successor_envelopes("session/update", ¶ms);
assert_eq!(method, "session/update");
assert_eq!(peeled, ¶ms);
}
#[test]
fn peel_successor_envelopes_unwraps_nested_envelopes() {
let params = serde_json::json!({
"method": "proxy/successor",
"params": {
"method": "$/cancel_request",
"params": { "requestId": "req-1" }
}
});
let (method, peeled) = peel_successor_envelopes("proxy/successor", ¶ms);
assert_eq!(method, "$/cancel_request");
assert_eq!(peeled, &serde_json::json!({ "requestId": "req-1" }));
}
#[test]
fn peel_successor_envelopes_leaves_malformed_envelopes_intact() {
let params = serde_json::json!({ "unexpected": true });
let (method, peeled) = peel_successor_envelopes("proxy/successor", ¶ms);
assert_eq!(method, "proxy/successor");
assert_eq!(peeled, ¶ms);
}
mod cancel_request {
use super::super::*;
fn notification(method: &str, params: serde_json::Value) -> UntypedMessage {
UntypedMessage::new(method, params).expect("well-formed JSON")
}
#[test]
fn cancellation_request_id_is_extracted_from_wrapped_notifications() {
let message = notification(
"proxy/successor",
serde_json::json!({
"method": "$/cancel_request",
"params": { "requestId": "req-1" }
}),
);
let request_id = cancellation_request_id_from_message(&message)
.expect("wrapped cancel should parse");
assert_eq!(request_id, Some(RawJsonRpcId::Str("req-1".into())));
}
#[test]
fn malformed_successor_envelope_is_not_treated_as_cancellation() {
let message = notification("proxy/successor", serde_json::json!({ "bogus": true }));
let request_id = cancellation_request_id_from_message(&message)
.expect("malformed envelope should be left to the handler chain");
assert_eq!(request_id, None);
}
#[test]
fn cancel_request_notifications_are_detected_even_when_wrapped() {
let plain = notification("$/cancel_request", serde_json::json!({ "requestId": 1 }));
assert!(is_cancel_request_notification(&plain));
let wrapped = notification(
"proxy/successor",
serde_json::json!({
"method": "$/cancel_request",
"params": { "requestId": 1 }
}),
);
assert!(is_cancel_request_notification(&wrapped));
let other_wrapped = notification(
"proxy/successor",
serde_json::json!({
"method": "session/update",
"params": {}
}),
);
assert!(!is_cancel_request_notification(&other_wrapped));
let malformed_envelope =
notification("proxy/successor", serde_json::json!({ "bogus": true }));
assert!(!is_cancel_request_notification(&malformed_envelope));
}
#[test]
fn malformed_cancel_request_params_error() {
let message = notification(
"$/cancel_request",
serde_json::json!({ "requestId": { "not": "an id" } }),
);
cancellation_request_id_from_message(&message)
.expect_err("malformed cancel params should error");
}
#[test]
fn registry_marks_and_removes_requests() {
let registry = RequestCancellationRegistry::new();
let id = RawJsonRpcId::Str("req-1".into());
let responder_cancellation = registry.register(&id);
let marker = responder_cancellation.cancellation();
assert!(!marker.is_cancelled());
assert!(registry.cancel(&id));
assert!(marker.is_cancelled());
assert!(responder_cancellation.cancellation().is_cancelled());
drop(responder_cancellation);
assert!(!registry.cancel(&id), "slot should be removed on drop");
}
#[test]
fn reused_request_id_does_not_cross_wire_cancellation_state() {
let registry = RequestCancellationRegistry::new();
let id = RawJsonRpcId::Str("dup".into());
let first = registry.register(&id);
let first_marker = first.cancellation();
let second = registry.register(&id);
let second_marker = second.cancellation();
assert!(registry.cancel(&id));
assert!(second_marker.is_cancelled());
assert!(
!first_marker.is_cancelled(),
"the stale request must not observe the newer request's cancellation"
);
assert!(!first.cancellation().is_cancelled());
drop(first);
assert!(registry.cancel(&id), "newer slot should still be present");
drop(second);
assert!(!registry.cancel(&id), "slot should be removed on drop");
}
}
}