mirror of
https://github.com/latex-lsp/texlab.git
synced 2025-08-04 10:49:55 +00:00
Move additional crates to the crates folder
This commit is contained in:
parent
5e612219d1
commit
383fff5a0c
12 changed files with 5 additions and 5 deletions
14
crates/futures_boxed/Cargo.toml
Normal file
14
crates/futures_boxed/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "futures-boxed"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Eric Förster <efoerster@users.noreply.github.com>",
|
||||
"Patrick Förster <pfoerster@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "0.15", features = ["full"] }
|
||||
quote = "0.6"
|
72
crates/futures_boxed/src/lib.rs
Normal file
72
crates/futures_boxed/src/lib.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
#![feature(async_await)]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use quote::quote;
|
||||
use quote::ToTokens;
|
||||
use std::iter::FromIterator;
|
||||
use syn::export::TokenStream2;
|
||||
use syn::*;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn boxed(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
match parse::<ItemFn>(item.clone()) {
|
||||
Ok(fn_) => boxed_fn(fn_),
|
||||
Err(_) => {
|
||||
let item = TokenStream::from_iter(item.into_iter().filter(|x| match x {
|
||||
TokenTree::Ident(x) if x.to_string() == "async" => false,
|
||||
_ => true,
|
||||
}));
|
||||
|
||||
let method: TraitItemMethod = parse(item).unwrap();
|
||||
boxed_trait_method(method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn boxed_fn(fn_: ItemFn) -> TokenStream {
|
||||
let attrs = &fn_.attrs;
|
||||
let vis = &fn_.vis;
|
||||
let decl = boxed_fn_decl(&fn_.decl, &fn_.constness, &fn_.ident);
|
||||
let block = &fn_.block;
|
||||
let tokens = quote! {
|
||||
#(#attrs)*
|
||||
#vis #decl {
|
||||
use futures::future::FutureExt;
|
||||
let task = async move #block;
|
||||
task.boxed()
|
||||
}
|
||||
};
|
||||
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
fn boxed_trait_method(method: TraitItemMethod) -> TokenStream {
|
||||
let attrs = &method.attrs;
|
||||
let decl = boxed_fn_decl(&method.sig.decl, &method.sig.constness, &method.sig.ident);
|
||||
let tokens = quote! {
|
||||
#(#attrs)*
|
||||
#decl;
|
||||
};
|
||||
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
fn boxed_fn_decl(
|
||||
decl: &FnDecl,
|
||||
constness: &Option<syn::token::Const>,
|
||||
ident: &Ident,
|
||||
) -> TokenStream2 {
|
||||
let generics = &decl.generics;
|
||||
let inputs = &decl.inputs;
|
||||
let return_ty = match &decl.output {
|
||||
ReturnType::Default => quote!(()),
|
||||
ReturnType::Type(_, ty) => ty.into_token_stream(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
#constness fn #ident #generics(#inputs) -> futures::future::BoxFuture<'_, #return_ty>
|
||||
}
|
||||
}
|
235
crates/jsonrpc/Cargo.lock
generated
Normal file
235
crates/jsonrpc/Cargo.lock
generated
Normal file
|
@ -0,0 +1,235 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-channel-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-util-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-channel-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-executor-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-io-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-sink-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-util-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-channel-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util-preview"
|
||||
version = "0.3.0-alpha.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-channel-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-io-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-sink-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_repr 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981"
|
||||
"checksum futures-channel-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "edf150887ba490560f3d732e479a383ca4b8696af98651806d3f4edc1d968585"
|
||||
"checksum futures-core-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "10a3833d58fd08b3a40203613ed3a93c8bc0bc0181af5dd6422a0e08df1bfa68"
|
||||
"checksum futures-executor-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0a75c64f20734619b4668e87f902544ce8c108dfcb6c9b6b2fcefdd1a848c15a"
|
||||
"checksum futures-io-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "b24891994ce1445f7e0cd494e4f57fd79f5bd9d37e9cc90a31d109e9a06d9073"
|
||||
"checksum futures-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f5b8da0ac7e67b6341b8c971918f2bb6540f57b06a8644a76edf17dda0728709"
|
||||
"checksum futures-sink-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f64fa75a0ce02dee949c8c9447abfc117df214054d6e96755d329c9053baf2fd"
|
||||
"checksum futures-util-preview 0.3.0-alpha.15 (registry+https://github.com/rust-lang/crates.io-index)" = "ca958da50f4073c475d9f7ec6ce405451e06707bfd69686e83abd76cb4e1e7fb"
|
||||
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
|
||||
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6"
|
||||
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
|
||||
"checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba"
|
||||
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
||||
"checksum proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)" = "64c827cea7a7ab30ce4593e5e04d7a11617ad6ece2fa230605a78b00ff965316"
|
||||
"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
|
||||
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
|
||||
"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4"
|
||||
"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79"
|
||||
"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
|
||||
"checksum serde_repr 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "29a734c298df0346c4cd5919595981c266dabbf12dc747c85e1a95e96077a52b"
|
||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
"checksum syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)" = "ec52cd796e5f01d0067225a5392e70084acc4c0013fa71d55166d38a8b307836"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
15
crates/jsonrpc/Cargo.toml
Normal file
15
crates/jsonrpc/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[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]
|
||||
futures-boxed = { path = "../futures_boxed" }
|
||||
futures-preview = { version = "0.3.0-alpha.15", features = ["compat"] }
|
||||
runtime = "0.3.0-alpha.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
88
crates/jsonrpc/src/client.rs
Normal file
88
crates/jsonrpc/src/client.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use crate::types::*;
|
||||
use futures::channel::oneshot;
|
||||
use futures::lock::Mutex;
|
||||
use futures::prelude::*;
|
||||
use futures_boxed::boxed;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub trait ResponseHandler {
|
||||
#[boxed]
|
||||
async fn handle(&self, response: Response);
|
||||
}
|
||||
|
||||
pub struct Client<O> {
|
||||
output: Arc<Mutex<O>>,
|
||||
request_id: AtomicI32,
|
||||
queue: Mutex<HashMap<Id, oneshot::Sender<Result<serde_json::Value>>>>,
|
||||
}
|
||||
|
||||
impl<O> Client<O>
|
||||
where
|
||||
O: Output,
|
||||
{
|
||||
pub fn new(output: Arc<Mutex<O>>) -> Self {
|
||||
Client {
|
||||
output,
|
||||
request_id: AtomicI32::new(0),
|
||||
queue: Mutex::new(HashMap::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);
|
||||
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
{
|
||||
let mut queue = self.queue.lock().await;
|
||||
queue.insert(request.id, sender);
|
||||
}
|
||||
|
||||
self.send(Message::Request(request)).await;
|
||||
receiver.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 json = serde_json::to_string(&message).unwrap();
|
||||
{
|
||||
let mut output = self.output.lock().await;
|
||||
output.send(json).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<O> ResponseHandler for Client<O>
|
||||
where
|
||||
O: Output,
|
||||
{
|
||||
#[boxed]
|
||||
async fn handle(&self, response: Response) {
|
||||
let id = response.id.expect("Expected response with id");
|
||||
let sender = {
|
||||
let mut queue = self.queue.lock().await;
|
||||
queue.remove(&id).expect("Unexpected response received")
|
||||
};
|
||||
|
||||
let result = match response.error {
|
||||
Some(why) => Err(why),
|
||||
None => Ok(response.result.unwrap_or(serde_json::Value::Null)),
|
||||
};
|
||||
|
||||
sender.send(result).unwrap();
|
||||
}
|
||||
}
|
74
crates/jsonrpc/src/lib.rs
Normal file
74
crates/jsonrpc/src/lib.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
#![feature(async_await, trait_alias)]
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
mod types;
|
||||
|
||||
pub use self::{
|
||||
client::{Client, ResponseHandler},
|
||||
server::{handle_notification, handle_request, ActionHandler, RequestHandler},
|
||||
types::*,
|
||||
};
|
||||
|
||||
use futures::lock::Mutex;
|
||||
use futures::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct MessageHandler<S, C, I, O> {
|
||||
pub server: Arc<S>,
|
||||
pub client: Arc<C>,
|
||||
pub input: I,
|
||||
pub output: Arc<Mutex<O>>,
|
||||
}
|
||||
|
||||
impl<S, C, I, O> MessageHandler<S, C, I, O>
|
||||
where
|
||||
S: RequestHandler + ActionHandler + Send + Sync + 'static,
|
||||
C: ResponseHandler + Send + Sync + 'static,
|
||||
I: Input,
|
||||
O: Output + 'static,
|
||||
{
|
||||
pub async fn listen(&mut self) {
|
||||
while let Some(json) = self.input.next().await {
|
||||
let message = serde_json::from_str(&json.expect("")).map_err(|_| Error {
|
||||
code: ErrorCode::ParseError,
|
||||
message: "Could not parse the input".to_owned(),
|
||||
data: serde_json::Value::Null,
|
||||
});
|
||||
|
||||
match message {
|
||||
Ok(Message::Request(request)) => {
|
||||
let server = Arc::clone(&self.server);
|
||||
let output = Arc::clone(&self.output);
|
||||
runtime::spawn(async move {
|
||||
let response = server.handle_request(request).await;
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
{
|
||||
let mut output = output.lock().await;
|
||||
output.send(json).await.unwrap();
|
||||
}
|
||||
server.execute_actions().await;
|
||||
});
|
||||
}
|
||||
Ok(Message::Notification(notification)) => {
|
||||
self.server.handle_notification(notification);
|
||||
let server = Arc::clone(&self.server);
|
||||
runtime::spawn(async move {
|
||||
server.execute_actions().await;
|
||||
});
|
||||
}
|
||||
Ok(Message::Response(response)) => {
|
||||
self.client.handle(response).await;
|
||||
}
|
||||
Err(why) => {
|
||||
let response = Response::error(why, None);
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
{
|
||||
let mut output = self.output.lock().await;
|
||||
output.send(json).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
132
crates/jsonrpc/src/server.rs
Normal file
132
crates/jsonrpc/src/server.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
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 ActionHandler {
|
||||
#[boxed]
|
||||
async fn execute_actions(&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 = async move |json| -> std::result::Result<O, Error> {
|
||||
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: 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);
|
||||
}
|
||||
}
|
145
crates/jsonrpc/src/types.rs
Normal file
145
crates/jsonrpc/src/types.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use futures::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::*;
|
||||
use std::io;
|
||||
|
||||
pub trait Input = Stream<Item = io::Result<String>> + Unpin;
|
||||
|
||||
pub trait Output = Sink<String, SinkError = io::Error> + Unpin + Send;
|
||||
|
||||
pub const PROTOCOL_VERSION: &str = "2.0";
|
||||
|
||||
pub type Id = i32;
|
||||
|
||||
#[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),
|
||||
}
|
6
crates/jsonrpc_derive/Cargo.lock
generated
Normal file
6
crates/jsonrpc_derive/Cargo.lock
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "jsonrpc"
|
||||
version = "0.1.0"
|
||||
|
15
crates/jsonrpc_derive/Cargo.toml
Normal file
15
crates/jsonrpc_derive/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "jsonrpc-derive"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Eric Förster <efoerster@users.noreply.github.com>",
|
||||
"Patrick Förster <pfoerster@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
futures-boxed = { path = "../futures_boxed" }
|
||||
syn = { version = "0.15", features = ["full"] }
|
||||
quote = "0.6"
|
218
crates/jsonrpc_derive/src/lib.rs
Normal file
218
crates/jsonrpc_derive/src/lib.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
#![feature(async_await)]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::str::FromStr;
|
||||
use syn::export::TokenStream2;
|
||||
use syn::*;
|
||||
|
||||
macro_rules! unwrap {
|
||||
($input:expr, $arm:pat => $value:expr) => {{
|
||||
match $input {
|
||||
$arm => $value,
|
||||
_ => panic!(),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
enum MethodKind {
|
||||
Request,
|
||||
Notification,
|
||||
}
|
||||
|
||||
struct MethodMeta {
|
||||
pub name: String,
|
||||
pub kind: MethodKind,
|
||||
}
|
||||
|
||||
impl MethodMeta {
|
||||
pub fn parse(attr: &Attribute) -> Self {
|
||||
let meta = attr.parse_meta().unwrap();
|
||||
if meta.name() != "jsonrpc_method" {
|
||||
panic!("Expected jsonrpc_method attribute");
|
||||
}
|
||||
|
||||
let nested = unwrap!(meta, Meta::List(x) => x.nested);
|
||||
let name = unwrap!(&nested[0], NestedMeta::Literal(Lit::Str(x)) => x.value());
|
||||
let kind = {
|
||||
let lit = unwrap!(&nested[1], NestedMeta::Meta(Meta::NameValue(x)) => &x.lit);
|
||||
let kind = unwrap!(lit, Lit::Str(x) => x.value());
|
||||
match kind.as_str() {
|
||||
"request" => MethodKind::Request,
|
||||
"notification" => MethodKind::Notification,
|
||||
_ => panic!(
|
||||
"Invalid method kind. Valid options are \"request\" and \"notification\""
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
Self { name, kind }
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn jsonrpc_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
item
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn jsonrpc_server(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let impl_: ItemImpl = parse_macro_input!(item);
|
||||
let generics = &impl_.generics;
|
||||
let self_ty = &impl_.self_ty;
|
||||
let (requests, notifications) = generate_server_skeletons(&impl_.items);
|
||||
|
||||
let tokens = quote! {
|
||||
#impl_
|
||||
|
||||
impl #generics jsonrpc::RequestHandler for #self_ty {
|
||||
#[boxed]
|
||||
async fn handle_request(&self, request: jsonrpc::Request) -> jsonrpc::Response {
|
||||
use jsonrpc::*;
|
||||
|
||||
match request.method.as_str() {
|
||||
#(#requests),*,
|
||||
_ => {
|
||||
Response::error(Error::method_not_found_error(), Some(request.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_notification(&self, notification: jsonrpc::Notification) {
|
||||
match notification.method.as_str() {
|
||||
#(#notifications),*,
|
||||
_ => log::warn!("{}: {}", "Method not found", notification.method),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn jsonrpc_client(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let item = TokenStream::from_str(&item.to_string().replace("async ", "")).unwrap();
|
||||
let trait_: ItemTrait = parse_macro_input!(item);
|
||||
let trait_ident = &trait_.ident;
|
||||
let stubs = generate_client_stubs(&trait_.items);
|
||||
let attr: AttributeArgs = parse_macro_input!(attr);
|
||||
let struct_ident = unwrap!(attr.first().unwrap(), NestedMeta::Meta(Meta::Word(x)) => x);
|
||||
|
||||
let tokens = quote! {
|
||||
#trait_
|
||||
|
||||
pub struct #struct_ident<O> {
|
||||
client: jsonrpc::Client<O>
|
||||
}
|
||||
|
||||
impl<O> #struct_ident<O>
|
||||
where
|
||||
O: jsonrpc::Output,
|
||||
{
|
||||
pub fn new(output: std::sync::Arc<futures::lock::Mutex<O>>) -> Self {
|
||||
Self {
|
||||
client: jsonrpc::Client::new(output),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<O> #trait_ident for #struct_ident<O>
|
||||
where
|
||||
O: jsonrpc::Output,
|
||||
{
|
||||
#(#stubs)*
|
||||
}
|
||||
|
||||
impl<O> jsonrpc::ResponseHandler for #struct_ident<O>
|
||||
where
|
||||
O: jsonrpc::Output,
|
||||
{
|
||||
#[boxed]
|
||||
async fn handle(&self, response: jsonrpc::Response) -> () {
|
||||
self.client.handle(response).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
fn generate_server_skeletons(items: &Vec<ImplItem>) -> (Vec<TokenStream2>, Vec<TokenStream2>) {
|
||||
let mut requests = Vec::new();
|
||||
let mut notifications = Vec::new();
|
||||
for item in items {
|
||||
let method = unwrap!(item, ImplItem::Method(x) => x);
|
||||
if method.attrs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ident = &method.sig.ident;
|
||||
let return_ty = &method.sig.decl.output;
|
||||
let param_ty = unwrap!(&method.sig.decl.inputs[1], FnArg::Captured(x) => &x.ty);
|
||||
let meta = MethodMeta::parse(method.attrs.first().unwrap());
|
||||
let name = &meta.name.as_str();
|
||||
|
||||
match meta.kind {
|
||||
MethodKind::Request => {
|
||||
requests.push(quote!(
|
||||
#name => {
|
||||
let handler = async move |param: #param_ty| #return_ty {
|
||||
self.#ident(param).await
|
||||
};
|
||||
|
||||
jsonrpc::handle_request(request, handler).await
|
||||
}
|
||||
));
|
||||
}
|
||||
MethodKind::Notification => {
|
||||
notifications.push(quote!(
|
||||
#name => {
|
||||
let handler = move |param: #param_ty| {
|
||||
self.#ident(param);
|
||||
};
|
||||
|
||||
jsonrpc::handle_notification(notification, handler);
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(requests, notifications)
|
||||
}
|
||||
|
||||
fn generate_client_stubs(items: &Vec<TraitItem>) -> Vec<TokenStream2> {
|
||||
let mut stubs = Vec::new();
|
||||
for item in items {
|
||||
let method = unwrap!(item, TraitItem::Method(x) => x);
|
||||
let attrs = &method.attrs;
|
||||
let sig = &method.sig;
|
||||
let param = unwrap!(&sig.decl.inputs[1], FnArg::Captured(x) => &x.pat);
|
||||
let meta = MethodMeta::parse(attrs.first().unwrap());
|
||||
let name = &meta.name;
|
||||
|
||||
let stub = match meta.kind {
|
||||
MethodKind::Request => quote!(
|
||||
#[boxed]
|
||||
#sig {
|
||||
let result = self.client.send_request(#name.to_owned(), #param).await?;
|
||||
serde_json::from_value(result).map_err(|_| jsonrpc::Error::deserialize_error())
|
||||
}
|
||||
),
|
||||
MethodKind::Notification => quote!(
|
||||
#[boxed]
|
||||
#sig {
|
||||
self.client.send_notification(#name.to_owned(), #param).await
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
stubs.push(stub);
|
||||
}
|
||||
|
||||
stubs
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue