use serde_json::{Value, json};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
enum Probe {
Forward,
Answer(Value),
Ignore,
}
fn classify(line: &[u8]) -> Probe {
let Ok(msg) = serde_json::from_slice::<Value>(line) else {
return Probe::Forward; };
let Some(method) = msg.get("method").and_then(Value::as_str) else {
return Probe::Forward; };
match method {
"initialize" | "ping" => Probe::Forward,
_ => match msg.get("id") {
Some(id) => Probe::Answer(id.clone()),
None => Probe::Ignore,
},
}
}
pub(super) async fn read_until_forwardable() -> Option<Vec<u8>> {
let mut stdin = tokio::io::stdin();
let mut stdout = tokio::io::stdout();
loop {
let mut line = Vec::new();
loop {
let mut byte = [0u8; 1];
match stdin.read(&mut byte).await {
Ok(0) | Err(_) => return None,
Ok(_) => {
line.push(byte[0]);
if byte[0] == b'\n' {
break;
}
}
}
}
match classify(&line) {
Probe::Forward => return Some(line),
Probe::Ignore => continue,
Probe::Answer(id) => {
let resp = json!({
"jsonrpc": "2.0",
"id": id,
"error": {"code": -32601, "message": "Method not found"},
});
let _ = stdout.write_all(resp.to_string().as_bytes()).await;
let _ = stdout.write_all(b"\n").await;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forwards_initialize_and_ping() {
let init = br#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
assert!(matches!(classify(init), Probe::Forward));
let ping = br#"{"jsonrpc":"2.0","id":2,"method":"ping"}"#;
assert!(matches!(classify(ping), Probe::Forward));
}
#[test]
fn answers_unknown_request_with_id() {
let probe = br#"{"jsonrpc":"2.0","id":7,"method":"server/discover","params":{}}"#;
match classify(probe) {
Probe::Answer(id) => assert_eq!(id, Value::from(7)),
_ => panic!("expected Answer"),
}
}
#[test]
fn ignores_unknown_notification() {
let note = br#"{"jsonrpc":"2.0","method":"server/discover"}"#;
assert!(matches!(classify(note), Probe::Ignore));
}
#[test]
fn forwards_garbage_and_responses() {
assert!(matches!(classify(b"not json\n"), Probe::Forward));
let resp = br#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
assert!(matches!(classify(resp), Probe::Forward));
}
}