use std::io::BufRead;
use serde::de::DeserializeOwned;
use serde_json::{self, Error as JsonError, Value};
use xi_trace;
use crate::error::{ReadError, RemoteError};
type RequestId = u64;
pub type Response = Result<Value, RemoteError>;
#[derive(Debug, Default)]
pub struct MessageReader(String);
#[derive(Debug, Clone)]
pub struct RpcObject(pub Value);
#[derive(Debug, Clone, PartialEq)]
pub enum Call<N, R> {
Request(RequestId, R),
Notification(N),
InvalidRequest(RequestId, RemoteError),
}
impl MessageReader {
pub fn next<R: BufRead>(&mut self, reader: &mut R) -> Result<RpcObject, ReadError> {
self.0.clear();
let _ = reader.read_line(&mut self.0)?;
if self.0.is_empty() {
Err(ReadError::Disconnect)
} else {
self.parse(&self.0)
}
}
#[doc(hidden)]
pub fn parse(&self, s: &str) -> Result<RpcObject, ReadError> {
let _trace = xi_trace::trace_block("parse", &["rpc"]);
let val = serde_json::from_str::<Value>(&s)?;
if !val.is_object() {
Err(ReadError::NotObject)
} else {
Ok(val.into())
}
}
}
impl RpcObject {
pub fn get_id(&self) -> Option<RequestId> {
self.0.get("id").and_then(Value::as_u64)
}
pub fn get_method(&self) -> Option<&str> {
self.0.get("method").and_then(Value::as_str)
}
pub fn is_response(&self) -> bool {
self.0.get("id").is_some() && self.0.get("method").is_none()
}
pub fn into_response(mut self) -> Result<Response, String> {
let _ = self.get_id().ok_or("Response requires 'id' field.".to_string())?;
if self.0.get("result").is_some() == self.0.get("error").is_some() {
return Err("RPC response must contain exactly one of\
'error' or 'result' fields."
.into());
}
let result = self.0.as_object_mut().and_then(|obj| obj.remove("result"));
match result {
Some(r) => Ok(Ok(r)),
None => {
let error = self.0.as_object_mut().and_then(|obj| obj.remove("error")).unwrap();
match serde_json::from_value::<RemoteError>(error) {
Ok(e) => Ok(Err(e)),
Err(e) => Err(format!("Error handling response: {:?}", e)),
}
}
}
}
pub fn into_rpc<N, R>(self) -> Result<Call<N, R>, JsonError>
where
N: DeserializeOwned,
R: DeserializeOwned,
{
let id = self.get_id();
match id {
Some(id) => match serde_json::from_value::<R>(self.0) {
Ok(resp) => Ok(Call::Request(id, resp)),
Err(err) => Ok(Call::InvalidRequest(id, err.into())),
},
None => {
let result = serde_json::from_value::<N>(self.0)?;
Ok(Call::Notification(result))
}
}
}
}
impl From<Value> for RpcObject {
fn from(v: Value) -> RpcObject {
RpcObject(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
enum TestR {
NewView { file_path: Option<String> },
OldView { file_path: usize },
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "method", content = "params")]
enum TestN {
CloseView { view_id: String },
Save { view_id: String, file_path: String },
}
#[test]
fn request_success() {
let json = r#"{"id":0,"method":"new_view","params":{}}"#;
let p: RpcObject = serde_json::from_str::<Value>(json).unwrap().into();
assert!(!p.is_response());
let req = p.into_rpc::<TestN, TestR>().unwrap();
assert_eq!(req, Call::Request(0, TestR::NewView { file_path: None }));
}
#[test]
fn request_failure() {
let json = r#"{"id":0,"method":"new_truth","params":{}}"#;
let p: RpcObject = serde_json::from_str::<Value>(json).unwrap().into();
let req = p.into_rpc::<TestN, TestR>().unwrap();
let is_ok = match req {
Call::InvalidRequest(0, _) => true,
_ => false,
};
if !is_ok {
panic!("{:?}", req);
}
}
#[test]
fn notif_with_id() {
let json = r#"{"id":0,"method":"close_view","params":{"view_id": "view-id-1"}}"#;
let p: RpcObject = serde_json::from_str::<Value>(json).unwrap().into();
let req = p.into_rpc::<TestN, TestR>().unwrap();
let is_ok = match req {
Call::InvalidRequest(0, _) => true,
_ => false,
};
if !is_ok {
panic!("{:?}", req);
}
}
#[test]
fn test_resp_err() {
let json = r#"{"id":5,"error":{"code":420, "message":"chill out"}}"#;
let p: RpcObject = serde_json::from_str::<Value>(json).unwrap().into();
assert!(p.is_response());
let resp = p.into_response().unwrap();
assert_eq!(resp, Err(RemoteError::custom(420, "chill out", None)));
}
#[test]
fn test_resp_result() {
let json = r#"{"id":5,"result":"success!"}"#;
let p: RpcObject = serde_json::from_str::<Value>(json).unwrap().into();
assert!(p.is_response());
let resp = p.into_response().unwrap();
assert_eq!(resp, Ok(json!("success!")));
}
#[test]
fn test_err() {
let json = r#"{"code": -32600, "message": "Invalid Request"}"#;
let e = serde_json::from_str::<RemoteError>(json).unwrap();
assert_eq!(e, RemoteError::InvalidRequest(None));
}
}