use serde_json::{Value, json};
use std::io;
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub const PARSE_ERROR: i64 = -32700;
pub const INVALID_REQUEST: i64 = -32600;
pub const METHOD_NOT_FOUND: i64 = -32601;
pub const INVALID_PARAMS: i64 = -32602;
pub const INTERNAL_ERROR: i64 = -32603;
pub const MAX_FRAME_BYTES: usize = 64 << 20;
#[derive(Debug, Clone, PartialEq)]
pub struct RpcError {
pub code: i64,
pub message: String,
pub data: Option<Value>,
}
impl RpcError {
pub fn new(code: i64, message: impl Into<String>) -> Self {
RpcError {
code,
message: message.into(),
data: None,
}
}
pub fn invalid_params(message: impl Into<String>) -> Self {
Self::new(INVALID_PARAMS, message)
}
pub fn method_not_found(method: &str) -> Self {
Self::new(METHOD_NOT_FOUND, format!("method not found: {method}"))
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new(INTERNAL_ERROR, message)
}
pub fn to_value(&self) -> Value {
let mut v = json!({ "code": self.code, "message": self.message });
if let Some(d) = &self.data {
v["data"] = d.clone();
}
v
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Framing {
Lines,
ContentLength,
}
#[derive(Debug)]
pub enum Message {
Request {
id: Value,
method: String,
params: Value,
},
Notification {
method: String,
params: Value,
},
Invalid {
id: Value,
error: RpcError,
},
}
pub fn classify(v: Value) -> Message {
let Value::Object(mut obj) = v else {
return Message::Invalid {
id: Value::Null,
error: RpcError::new(INVALID_REQUEST, "a request is a JSON object"),
};
};
let id = obj.get("id").cloned();
let id_ok = matches!(
id,
None | Some(Value::Null) | Some(Value::String(_)) | Some(Value::Number(_))
);
if !id_ok {
return Message::Invalid {
id: Value::Null,
error: RpcError::new(INVALID_REQUEST, "id must be a string, a number or null"),
};
}
if obj.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
return Message::Invalid {
id: id.unwrap_or(Value::Null),
error: RpcError::new(INVALID_REQUEST, "jsonrpc must be \"2.0\""),
};
}
let method = match obj.remove("method") {
Some(Value::String(m)) => m,
_ => {
return Message::Invalid {
id: id.unwrap_or(Value::Null),
error: RpcError::new(INVALID_REQUEST, "method must be a string"),
};
}
};
let params = obj.remove("params").unwrap_or(Value::Null);
match id {
Some(id) => Message::Request { id, method, params },
None => Message::Notification { method, params },
}
}
pub fn response(id: &Value, result: Value) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "result": result })
}
pub fn error_response(id: &Value, error: &RpcError) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "error": error.to_value() })
}
fn trim_ascii(b: &[u8]) -> &[u8] {
let start = b.iter().position(|c| !c.is_ascii_whitespace());
let end = b.iter().rposition(|c| !c.is_ascii_whitespace());
match (start, end) {
(Some(s), Some(e)) => &b[s..=e],
_ => &[],
}
}
fn content_length(line: &[u8]) -> Option<usize> {
let line = std::str::from_utf8(line).ok()?;
let (name, value) = line.split_once(':')?;
if !name.trim().eq_ignore_ascii_case("content-length") {
return None;
}
value.trim().parse().ok()
}
#[derive(Debug)]
pub enum Frame {
Body(Vec<u8>, Framing),
Refused(RpcError, Framing),
}
pub async fn read_frame<R: AsyncBufRead + Unpin>(r: &mut R) -> io::Result<Option<Frame>> {
loop {
let mut line = Vec::new();
let n = r.read_until(b'\n', &mut line).await?;
if n == 0 {
return Ok(None);
}
if line.len() > MAX_FRAME_BYTES {
return Ok(Some(Frame::Refused(
RpcError::new(
INVALID_REQUEST,
format!(
"frame of {} bytes exceeds the {MAX_FRAME_BYTES} byte limit",
line.len()
),
),
Framing::Lines,
)));
}
let trimmed = trim_ascii(&line);
if trimmed.is_empty() {
continue;
}
if let Some(len) = content_length(trimmed) {
loop {
let mut h = Vec::new();
let n = r.read_until(b'\n', &mut h).await?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"stream ended inside a Content-Length header block",
));
}
if trim_ascii(&h).is_empty() {
break;
}
}
if len > MAX_FRAME_BYTES {
return Ok(Some(Frame::Refused(
RpcError::new(
INVALID_REQUEST,
format!("Content-Length {len} exceeds the {MAX_FRAME_BYTES} byte limit"),
),
Framing::ContentLength,
)));
}
let mut body = vec![0u8; len];
r.read_exact(&mut body).await?;
return Ok(Some(Frame::Body(body, Framing::ContentLength)));
}
return Ok(Some(Frame::Body(trimmed.to_vec(), Framing::Lines)));
}
}
pub async fn write_frame<W: AsyncWrite + Unpin>(
w: &mut W,
framing: Framing,
msg: &Value,
) -> io::Result<()> {
let body = serde_json::to_vec(msg).map_err(io::Error::other)?;
debug_assert!(
!body.contains(&b'\n'),
"compact JSON must not contain a raw newline"
);
match framing {
Framing::Lines => {
w.write_all(&body).await?;
w.write_all(b"\n").await?;
}
Framing::ContentLength => {
w.write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes())
.await?;
w.write_all(&body).await?;
}
}
w.flush().await
}