Move additional crates to the crates folder

This commit is contained in:
Patrick Förster 2019-07-30 14:12:55 +02:00
parent 5e612219d1
commit 383fff5a0c
12 changed files with 5 additions and 5 deletions

View 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"

View 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
View 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
View 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"

View 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
View 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();
}
}
}
}
}
}

View 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
View 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
View 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"

View 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"

View 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
}