diff options
Diffstat (limited to 'support/texlab/crates/jsonrpc')
-rw-r--r-- | support/texlab/crates/jsonrpc/Cargo.toml | 17 | ||||
-rw-r--r-- | support/texlab/crates/jsonrpc/src/client.rs | 74 | ||||
-rw-r--r-- | support/texlab/crates/jsonrpc/src/lib.rs | 72 | ||||
-rw-r--r-- | support/texlab/crates/jsonrpc/src/server.rs | 137 | ||||
-rw-r--r-- | support/texlab/crates/jsonrpc/src/types.rs | 144 |
5 files changed, 444 insertions, 0 deletions
diff --git a/support/texlab/crates/jsonrpc/Cargo.toml b/support/texlab/crates/jsonrpc/Cargo.toml new file mode 100644 index 0000000000..d806759f3f --- /dev/null +++ b/support/texlab/crates/jsonrpc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "jsonrpc" +version = "0.1.0" +authors = [ + "Eric Förster <efoerster@users.noreply.github.com>", + "Patrick Förster <pfoerster@users.noreply.github.com>"] +edition = "2018" + +[dependencies] +chashmap = "2.2" +futures-boxed = { path = "../futures_boxed" } +futures-preview = "0.3.0-alpha.18" +log = "0.4.6" +tokio = "0.2.0-alpha.6" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_repr = "0.1" diff --git a/support/texlab/crates/jsonrpc/src/client.rs b/support/texlab/crates/jsonrpc/src/client.rs new file mode 100644 index 0000000000..2cf18af1ff --- /dev/null +++ b/support/texlab/crates/jsonrpc/src/client.rs @@ -0,0 +1,74 @@ +use crate::types::*; +use chashmap::CHashMap; +use futures::channel::*; +use futures::prelude::*; +use futures_boxed::boxed; +use serde::Serialize; +use serde_json::json; +use std::sync::atomic::{AtomicU64, Ordering}; + +pub type Result<T> = std::result::Result<T, Error>; + +pub trait ResponseHandler { + #[boxed] + async fn handle(&self, response: Response); +} + +pub struct Client { + output: mpsc::Sender<String>, + request_id: AtomicU64, + senders_by_id: CHashMap<Id, oneshot::Sender<Result<serde_json::Value>>>, +} + +impl Client { + pub fn new(output: mpsc::Sender<String>) -> Self { + Client { + output, + request_id: AtomicU64::new(0), + senders_by_id: CHashMap::new(), + } + } + + pub async fn send_request<T: Serialize>( + &self, + method: String, + params: T, + ) -> Result<serde_json::Value> { + let id = self.request_id.fetch_add(1, Ordering::SeqCst); + let request = Request::new(method, json!(params), Id::Number(id)); + + let (result_tx, result_rx) = oneshot::channel(); + self.senders_by_id.insert(request.id.clone(), result_tx); + self.send(Message::Request(request)).await; + + result_rx.await.unwrap() + } + + pub async fn send_notification<T: Serialize>(&self, method: String, params: T) { + let notification = Notification::new(method, json!(params)); + self.send(Message::Notification(notification)).await; + } + + async fn send(&self, message: Message) { + let mut output = self.output.clone(); + let json = serde_json::to_string(&message).unwrap(); + output.send(json).await.unwrap(); + } +} + +impl ResponseHandler for Client { + #[boxed] + async fn handle(&self, response: Response) { + let id = response.id.expect("Expected response with id"); + let result = match response.error { + Some(why) => Err(why), + None => Ok(response.result.unwrap_or(serde_json::Value::Null)), + }; + + let result_tx = self + .senders_by_id + .remove(&id) + .expect("Unexpected response received"); + result_tx.send(result).unwrap(); + } +} diff --git a/support/texlab/crates/jsonrpc/src/lib.rs b/support/texlab/crates/jsonrpc/src/lib.rs new file mode 100644 index 0000000000..8786598d58 --- /dev/null +++ b/support/texlab/crates/jsonrpc/src/lib.rs @@ -0,0 +1,72 @@ +pub mod client; +pub mod server; +mod types; + +pub use self::{ + client::{Client, ResponseHandler}, + server::{handle_notification, handle_request, Middleware, RequestHandler}, + types::*, +}; + +use futures::channel::*; +use futures::prelude::*; +use log::error; +use std::io; +use std::sync::Arc; + +pub struct MessageHandler<S, C, I> { + pub server: Arc<S>, + pub client: Arc<C>, + pub input: I, + pub output: mpsc::Sender<String>, +} + +impl<S, C, I> MessageHandler<S, C, I> +where + S: RequestHandler + Middleware + Send + Sync + 'static, + C: ResponseHandler + Send + Sync + 'static, + I: Stream<Item = io::Result<String>> + Unpin, +{ + pub async fn listen(&mut self) { + while let Some(json) = self.input.next().await { + self.server.before_message().await; + + match serde_json::from_str(&json.unwrap()).map_err(|_| Error::parse_error()) { + Ok(Message::Request(request)) => { + let server = Arc::clone(&self.server); + let mut output = self.output.clone(); + tokio::spawn(async move { + let response = server.handle_request(request).await; + if let Some(error) = response.error.as_ref() { + error!("{:?}", error); + } + let json = serde_json::to_string(&response).unwrap(); + output.send(json).await.unwrap(); + server.after_message().await; + }); + } + Ok(Message::Notification(notification)) => { + self.server.handle_notification(notification); + self.after_message(); + } + Ok(Message::Response(response)) => { + self.client.handle(response).await; + self.after_message(); + } + Err(why) => { + let response = Response::error(why, None); + let json = serde_json::to_string(&response).unwrap(); + self.output.send(json).await.unwrap(); + self.after_message(); + } + }; + } + } + + fn after_message(&self) { + let server = Arc::clone(&self.server); + tokio::spawn(async move { + server.after_message().await; + }); + } +} diff --git a/support/texlab/crates/jsonrpc/src/server.rs b/support/texlab/crates/jsonrpc/src/server.rs new file mode 100644 index 0000000000..ccc04f286e --- /dev/null +++ b/support/texlab/crates/jsonrpc/src/server.rs @@ -0,0 +1,137 @@ +use crate::types::*; +use futures::prelude::*; +use futures_boxed::boxed; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json::json; + +pub type Result<T> = std::result::Result<T, String>; + +pub trait RequestHandler { + #[boxed] + async fn handle_request(&self, request: Request) -> Response; + + fn handle_notification(&self, notification: Notification); +} + +pub trait Middleware { + #[boxed] + async fn before_message(&self); + + #[boxed] + async fn after_message(&self); +} + +pub async fn handle_request<'a, H, F, I, O>(request: Request, handler: H) -> Response +where + H: Fn(I) -> F + Send + Sync + 'a, + F: Future<Output = Result<O>> + Send, + I: DeserializeOwned + Send, + O: Serialize, +{ + let handle = |json| { + async move { + let params: I = serde_json::from_value(json).map_err(|_| Error::deserialize_error())?; + let result = handler(params).await.map_err(Error::internal_error)?; + Ok(result) + } + }; + + match handle(request.params).await { + Ok(result) => Response::result(json!(result), request.id), + Err(error) => Response::error(error, Some(request.id)), + } +} + +pub fn handle_notification<'a, H, I>(notification: Notification, handler: H) +where + H: Fn(I) -> () + Send + Sync + 'a, + I: DeserializeOwned + Send, +{ + let params = + serde_json::from_value(notification.params).expect(&Error::deserialize_error().message); + handler(params); +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::executor::block_on; + + const METHOD_NAME: &str = "foo"; + + async fn increment(i: i32) -> Result<i32> { + Ok(i + 1) + } + + fn panic(_params: ()) { + panic!("success"); + } + + fn setup_request<T: Serialize>(value: T) -> Request { + Request { + jsonrpc: PROTOCOL_VERSION.to_owned(), + params: json!(value), + method: METHOD_NAME.to_owned(), + id: Id::Number(0), + } + } + + fn setup_notification() -> Notification { + Notification { + jsonrpc: PROTOCOL_VERSION.to_owned(), + method: METHOD_NAME.to_owned(), + params: json!(()), + } + } + + #[test] + fn test_request_valid() { + let value = 42; + let request = setup_request(value); + + let response = block_on(handle_request(request.clone(), increment)); + let expected = Response { + jsonrpc: request.jsonrpc, + result: Some(json!(block_on(increment(value)).unwrap())), + error: None, + id: Some(request.id), + }; + + assert_eq!(response, expected); + } + + #[test] + fn test_request_invalid_params() { + let request = setup_request((0, 0)); + + let response = block_on(handle_request(request.clone(), increment)); + let expected = Response { + jsonrpc: request.jsonrpc.clone(), + result: None, + error: Some(Error::deserialize_error()), + id: Some(request.id), + }; + + assert_eq!(response, expected); + } + + #[test] + #[should_panic(expected = "success")] + fn test_notification_valid() { + let notification = setup_notification(); + handle_notification(notification, panic); + } + + #[test] + #[should_panic] + fn test_notification_invalid_params() { + let notification = setup_notification(); + let notification = Notification { + params: json!(0), + ..notification + }; + + handle_notification(notification, panic); + } +} diff --git a/support/texlab/crates/jsonrpc/src/types.rs b/support/texlab/crates/jsonrpc/src/types.rs new file mode 100644 index 0000000000..90116ac793 --- /dev/null +++ b/support/texlab/crates/jsonrpc/src/types.rs @@ -0,0 +1,144 @@ +use serde::{Deserialize, Serialize}; +use serde_repr::*; + +pub const PROTOCOL_VERSION: &str = "2.0"; + +#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Id { + Number(u64), + String(String), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize_repr, Serialize_repr)] +#[repr(i32)] +pub enum ErrorCode { + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + RequestCancelled = -32800, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Error { + pub code: ErrorCode, + pub message: String, + + #[serde(skip_serializing_if = "serde_json::Value::is_null")] + pub data: serde_json::Value, +} + +impl Error { + pub fn parse_error() -> Self { + Self { + code: ErrorCode::ParseError, + message: "Could not parse the input".to_owned(), + data: serde_json::Value::Null, + } + } + + pub fn method_not_found_error() -> Self { + Self { + code: ErrorCode::MethodNotFound, + message: "Method not found".to_owned(), + data: serde_json::Value::Null, + } + } + + pub fn deserialize_error() -> Self { + Self { + code: ErrorCode::InvalidParams, + message: "Could not deserialize parameter object".to_owned(), + data: serde_json::Value::Null, + } + } + + pub fn internal_error(message: String) -> Self { + Self { + code: ErrorCode::InternalError, + message, + data: serde_json::Value::Null, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Request { + pub jsonrpc: String, + pub method: String, + pub params: serde_json::Value, + pub id: Id, +} + +impl Request { + pub fn new(method: String, params: serde_json::Value, id: Id) -> Self { + Request { + jsonrpc: PROTOCOL_VERSION.to_owned(), + method, + params, + id, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Response { + pub jsonrpc: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option<serde_json::Value>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option<Error>, + + pub id: Option<Id>, +} + +impl Response { + pub fn result(result: serde_json::Value, id: Id) -> Self { + Response { + jsonrpc: PROTOCOL_VERSION.to_owned(), + result: Some(result), + error: None, + id: Some(id), + } + } + + pub fn error(error: Error, id: Option<Id>) -> Self { + Response { + jsonrpc: PROTOCOL_VERSION.to_owned(), + result: None, + error: Some(error), + id, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Notification { + pub jsonrpc: String, + pub method: String, + pub params: serde_json::Value, +} + +impl Notification { + pub fn new(method: String, params: serde_json::Value) -> Self { + Notification { + jsonrpc: PROTOCOL_VERSION.to_owned(), + method, + params, + } + } +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Message { + Request(Request), + Notification(Notification), + Response(Response), +} |