From 69522626ff87433a0ca63e7816dcc8d5de301d36 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 12 May 2017 17:35:31 -0700 Subject: [PATCH] WIP --- .gitignore | 2 + Cargo.toml | 18 ++++ src/lib.rs | 205 +++++++++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 27 ++++++ 4 files changed, 252 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 tests/ruma_api_macros.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..62b3d253 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jimmy Cuadra "] +name = "ruma-api-macros" +version = "0.1.0" + +[dependencies] +quote = "0.3.15" +syn = { version = "0.11.11", features = ["full"] } + +[dependencies.ruma-api] +path = "../ruma-api" + +[dependencies.hyper] +git = "https://github.com/hyperium/hyper" +rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" + +[lib] +proc-macro = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..0727d79d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,205 @@ +#![feature(proc_macro)] + +extern crate hyper; +extern crate proc_macro; +extern crate quote; +extern crate ruma_api; +extern crate syn; + +use proc_macro::TokenStream; + +use hyper::Method; +use quote::{ToTokens, Tokens}; +use syn::{ExprKind, Item, ItemKind, Lit, parse_items}; + +#[proc_macro] +pub fn ruma_api(input: TokenStream) -> TokenStream { + let items = parse_items(&input.to_string()) + .expect("failed to parse input"); + + let api = Api::from(items); + + api.output().parse().expect("failed to parse output") +} + +struct Api { + metadata: Metadata, + request: Request, + response: Response, +} + +impl Api { + fn output(&self) -> Tokens { + Tokens::new() + } +} + +impl From> for Api { + fn from(items: Vec) -> Api { + if items.len() != 3 { + panic!("ruma_api! expects exactly three items: const METADATA, struct Request, and struct Response"); + } + + let mut metadata = None; + let mut request = None; + let mut response = None; + + for item in items { + match &item.ident.to_string()[..] { + "METADATA" => metadata = Some(Metadata::from(item)), + "Request" => request = Some(Request::from(item)), + "Response" => response = Some(Response::from(item)), + other => panic!("ruma_api! found unexpected item: {}", other), + } + } + + if metadata.is_none() { + panic!("ruma_api! requires item: const METADATA"); + } + + if request.is_none() { + panic!("ruma_api! requires item: struct Request"); + } + + if response.is_none() { + panic!("ruma_api! requires item: struct Response"); + } + + Api { + metadata: metadata.expect("metadata is missing"), + request: request.expect("request is missing"), + response: response.expect("response is missing"), + } + } +} + +struct Metadata { + description: Tokens, + method: Tokens, + name: Tokens, + path: Tokens, + rate_limited: Tokens, + requires_authentication: Tokens, +} + +impl From for Metadata { + fn from(item: Item) -> Self { + let expr = match item.node { + ItemKind::Const(_, expr) => expr, + _ => panic!("ruma_api! expects METADATA to be a const item"), + }; + + let field_values = match expr.node { + ExprKind::Struct(_, field_values, _) => field_values, + _ => panic!("ruma_api! expects METADATA to be a Metadata struct"), + }; + + let mut description = None; + let mut method = None; + let mut name = None; + let mut path = None; + let mut rate_limited = None; + let mut requires_authentication = None; + + for field_value in field_values { + match &field_value.ident.to_string()[..] { + "description" => { + match field_value.expr.node { + ExprKind::Lit(Lit::Str(value, _)) => description = Some(tokens_for(value)), + _ => panic!("ruma_api! expects metadata description to be a &'static str"), + } + } + "method" => { + match field_value.expr.node { + ExprKind::Path(_, value) => method = Some(tokens_for(value)), + _ => panic!("ruma_api! expects metadata method to be a path"), + } + } + "name" => { + match field_value.expr.node { + ExprKind::Lit(Lit::Str(value, _)) => name = Some(tokens_for(value)), + _ => panic!("ruma_api! expects metadata name to be a &'static str"), + } + } + "path" => { + match field_value.expr.node { + ExprKind::Lit(Lit::Str(value, _)) => path = Some(tokens_for(value)), + _ => panic!("ruma_api! expects metadata path to be a &'static str"), + } + } + "rate_limited" => { + match field_value.expr.node { + ExprKind::Lit(Lit::Bool(value)) => rate_limited = Some(tokens_for(value)), + _ => panic!("ruma_api! expects metadata rate_limited to be a bool"), + } + } + "requires_authentication" => { + match field_value.expr.node { + ExprKind::Lit(Lit::Bool(value)) => { + requires_authentication = Some(tokens_for(value)); + } + _ => panic!("ruma_api! expects metadata requires_authentication to be a bool"), + } + } + other => panic!("ruma_api! found unexpected metadata field: {}", other), + } + } + + if description.is_none() { + panic!("ruma_api! metadata requires field: description"); + } + + if method.is_none() { + panic!("ruma_api! metadata requires field: method"); + } + + if name.is_none() { + panic!("ruma_api! metadata requires field: name"); + } + + if path.is_none() { + panic!("ruma_api! metadata requires field: path"); + } + + if rate_limited.is_none() { + panic!("ruma_api! metadata requires field: rate_limited"); + } + + if requires_authentication.is_none() { + panic!("ruma_api! metadata requires field: requires_authentication"); + } + + Metadata { + description: description.expect("description is missing"), + method: method.expect("method is missing"), + name: name.expect("name is missing"), + path: path.expect("path is missing"), + rate_limited: rate_limited.expect("rate limited is missing"), + requires_authentication: requires_authentication.expect("requires_authentication is missing"), + } + } +} + +struct Request; + +impl From for Request { + fn from(item: Item) -> Self { + Request + // panic!("ruma_api! could not parse Request"); + } +} + +struct Response; + +impl From for Response { + fn from(item: Item) -> Self { + Response + // panic!("ruma_api! could not parse Response"); + } +} + +fn tokens_for(value: T) -> Tokens where T: ToTokens { + let mut tokens = Tokens::new(); + value.to_tokens(&mut tokens); + tokens +} diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs new file mode 100644 index 00000000..881989a7 --- /dev/null +++ b/tests/ruma_api_macros.rs @@ -0,0 +1,27 @@ +#![feature(proc_macro)] + +extern crate hyper; +extern crate ruma_api; +extern crate ruma_api_macros; + +pub mod get_supported_versions { + use ruma_api_macros::ruma_api; + + ruma_api! { + const METADATA: Metadata = Metadata { + description: "Get the versions of the client-server API supported by this homeserver.", + method: Method::Get, + name: "api_versions", + path: "/_matrix/client/versions", + rate_limited: false, + requires_authentication: true, + }; + + struct Request; + + struct Response { + /// A list of Matrix client API protocol versions supported by the homeserver. + pub versions: Vec, + } + } +}