#[cfg(all(test, feature = "runtime-tokio", not(target_arch = "wasm32")))]
mod conformance_support {
pub(crate) use crate::{Context, LspError, Outcome, Result, Server, TaskSend};
#[cfg(all(not(target_arch = "wasm32"), any(feature = "stdio", feature = "tcp")))]
pub(crate) use crate::transport::framing::ContentLengthCodec;
}
#[cfg(all(test, feature = "runtime-tokio", not(target_arch = "wasm32")))]
mod conformance;
#[cfg_attr(
not(any(
test,
feature = "stdio",
feature = "tcp",
feature = "websocket",
feature = "worker-channel"
)),
allow(dead_code)
)]
pub(crate) mod envelope;
#[cfg(any(feature = "stdio", feature = "tcp"))]
pub mod framing;
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
mod stdio;
#[cfg(all(feature = "tcp", not(target_arch = "wasm32")))]
mod tcp;
#[cfg(all(feature = "websocket", not(target_arch = "wasm32")))]
mod websocket;
#[cfg(all(feature = "worker-channel", target_arch = "wasm32"))]
mod worker_channel;
use std::future::Future;
use std::io;
use thiserror::Error;
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
use crate::builder::Server;
use crate::raw::RawMessage;
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
pub use stdio::{StdioReader, StdioTransport, StdioWriter};
#[cfg(all(feature = "tcp", not(target_arch = "wasm32")))]
pub use tcp::{TcpBuilder, TcpReader, TcpTransport, TcpWriter, tcp};
#[cfg(all(feature = "websocket", not(target_arch = "wasm32")))]
pub use websocket::{
WebSocketBuilder, WebSocketReader, WebSocketTransport, WebSocketWriter, websocket,
};
#[cfg(all(feature = "worker-channel", target_arch = "wasm32"))]
pub use worker_channel::{
WorkerChannelBuilder, WorkerChannelReader, WorkerChannelTransport, WorkerChannelWriter,
worker_channel,
};
#[cfg(all(
any(feature = "stdio", feature = "tcp", feature = "websocket"),
not(target_arch = "wasm32")
))]
pub(crate) fn classify_io_error(error: io::Error) -> TransportError {
match error.kind() {
io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::ConnectionReset
| io::ErrorKind::NotConnected
| io::ErrorKind::UnexpectedEof => TransportError::Closed,
_ => TransportError::Io(error),
}
}
#[derive(Debug, Error)]
pub enum TransportError {
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("connection closed by peer")]
Closed,
#[error("malformed message: {0}")]
Malformed(String),
#[error("message exceeds size limit ({length} > {limit} bytes)")]
OversizedMessage {
length: usize,
limit: usize,
},
#[error("serialization error: {0}")]
Serde(#[from] serde_json::Error),
}
#[cfg(not(target_arch = "wasm32"))]
pub trait Transport: Send + 'static {
type Reader: TransportReader;
type Writer: TransportWriter;
fn split(self) -> (Self::Reader, Self::Writer);
}
#[cfg(target_arch = "wasm32")]
pub trait Transport: 'static {
type Reader: TransportReader;
type Writer: TransportWriter;
fn split(self) -> (Self::Reader, Self::Writer);
}
#[cfg(not(target_arch = "wasm32"))]
pub trait TransportReader: Send + 'static {
fn recv(
&mut self,
) -> impl Future<Output = std::result::Result<RawMessage, TransportError>> + Send;
}
#[cfg(target_arch = "wasm32")]
pub trait TransportReader: 'static {
fn recv(&mut self) -> impl Future<Output = std::result::Result<RawMessage, TransportError>>;
}
#[cfg(not(target_arch = "wasm32"))]
pub trait TransportWriter: Send + 'static {
fn send(
&mut self,
msg: RawMessage,
) -> impl Future<Output = std::result::Result<(), TransportError>> + Send;
fn shutdown(self) -> impl Future<Output = std::result::Result<(), TransportError>> + Send;
}
#[cfg(target_arch = "wasm32")]
pub trait TransportWriter: 'static {
fn send(
&mut self,
msg: RawMessage,
) -> impl Future<Output = std::result::Result<(), TransportError>>;
fn shutdown(self) -> impl Future<Output = std::result::Result<(), TransportError>>;
}
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
pub fn stdio<S>(server: Server<S>) -> StdioBuilder<S>
where
S: Send + Sync + 'static,
{
StdioBuilder { server }
}
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
pub struct StdioBuilder<S> {
server: Server<S>,
}
#[cfg(all(feature = "stdio", not(target_arch = "wasm32")))]
impl<S> StdioBuilder<S>
where
S: Send + Sync + 'static,
{
pub async fn serve(self) -> crate::Result<crate::Outcome> {
self.server.serve(StdioTransport::new()).await
}
}
#[cfg(all(
test,
not(target_arch = "wasm32"),
any(
feature = "stdio",
feature = "tcp",
feature = "websocket",
feature = "worker-channel"
)
))]
mod tests {
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use bytes::Bytes;
use super::{
RawMessage, Transport, TransportError, TransportReader, TransportWriter, envelope,
};
use crate::{Outcome, RequestId, Server};
struct FrameTransport {
frames: VecDeque<Bytes>,
outbox: Arc<Mutex<Vec<RawMessage>>>,
}
struct FrameReader {
frames: VecDeque<Bytes>,
}
struct FrameWriter {
outbox: Arc<Mutex<Vec<RawMessage>>>,
}
impl Transport for FrameTransport {
type Reader = FrameReader;
type Writer = FrameWriter;
fn split(self) -> (Self::Reader, Self::Writer) {
(
FrameReader {
frames: self.frames,
},
FrameWriter {
outbox: self.outbox,
},
)
}
}
impl TransportReader for FrameReader {
async fn recv(&mut self) -> Result<RawMessage, TransportError> {
self.frames
.pop_front()
.map(envelope::parse)
.ok_or(TransportError::Closed)
}
}
impl TransportWriter for FrameWriter {
async fn send(&mut self, msg: RawMessage) -> Result<(), TransportError> {
self.outbox.lock().unwrap().push(msg);
Ok(())
}
async fn shutdown(self) -> Result<(), TransportError> {
Ok(())
}
}
struct TestState;
#[tokio::test]
async fn complete_protocol_error_frames_do_not_close_the_connection() {
let outbox = Arc::new(Mutex::new(Vec::new()));
let transport = FrameTransport {
frames: VecDeque::from([
Bytes::from_static(br#"{"jsonrpc":"2.0","method":"initialize""#),
Bytes::from_static(br#"{"jsonrpc":"1.0","method":"initialize"}"#),
Bytes::from_static(
br#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootUri":null,"capabilities":{}}}"#,
),
]),
outbox: outbox.clone(),
};
let server = Server::builder(TestState)
.build()
.expect("an empty server builds");
let outcome = server
.serve(transport)
.await
.expect("complete protocol errors do not become transport errors");
assert_eq!(
outcome,
Outcome::TransportClosed,
"the frames run out, so the connection ends on reader EOF"
);
let outbox = outbox.lock().unwrap();
let error_codes: Vec<_> = outbox
.iter()
.filter_map(|message| match message {
RawMessage::ProtocolError { error } => Some(error.code),
_ => None,
})
.collect();
assert_eq!(error_codes, vec![-32700, -32600]);
assert!(
outbox.iter().any(|message| {
matches!(message, RawMessage::Response { id, result: Ok(_) } if *id == RequestId::Number(1))
}),
"initialize after protocol errors should still be processed, got outbox {outbox:#?}"
);
}
}