From 69522626ff87433a0ca63e7816dcc8d5de301d36 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 12 May 2017 17:35:31 -0700 Subject: [PATCH 001/107] 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, + } + } +} From bf7189048a5354bf516e8c28ca565596eb75399e Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 00:19:14 -0700 Subject: [PATCH 002/107] Use custom synom parsing. --- Cargo.toml | 12 ++- src/lib.rs | 168 +++++---------------------------------- src/parse.rs | 144 +++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 10 +-- 4 files changed, 178 insertions(+), 156 deletions(-) create mode 100644 src/parse.rs diff --git a/Cargo.toml b/Cargo.toml index 62b3d253..8b16922b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,14 +5,18 @@ version = "0.1.0" [dependencies] quote = "0.3.15" -syn = { version = "0.11.11", features = ["full"] } - -[dependencies.ruma-api] -path = "../ruma-api" +synom = "0.11.3" [dependencies.hyper] git = "https://github.com/hyperium/hyper" rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" +[dependencies.ruma-api] +path = "../ruma-api" + +[dependencies.syn] +features = ["full"] +version = "0.11.11" + [lib] proc-macro = true diff --git a/src/lib.rs b/src/lib.rs index 0727d79d..3ad4079a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,27 @@ #![feature(proc_macro)] -extern crate hyper; extern crate proc_macro; extern crate quote; extern crate ruma_api; extern crate syn; +#[macro_use] extern crate synom; use proc_macro::TokenStream; -use hyper::Method; -use quote::{ToTokens, Tokens}; -use syn::{ExprKind, Item, ItemKind, Lit, parse_items}; +use quote::Tokens; +use syn::{Expr, Field, Ident, Item}; + +use parse::{Entry, parse_entries}; + +mod parse; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let items = parse_items(&input.to_string()) - .expect("failed to parse input"); + let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); - let api = Api::from(items); + let api = Api::from(entries); - api.output().parse().expect("failed to parse output") + api.output().parse().expect("ruma_api! failed to parse output as a TokenStream") } struct Api { @@ -34,41 +36,19 @@ impl Api { } } -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"); - } - +impl From> for Api { + fn from(entries: Vec) -> Api { Api { - metadata: metadata.expect("metadata is missing"), - request: request.expect("request is missing"), - response: response.expect("response is missing"), + metadata: Metadata { + description: Tokens::new(), + method: Tokens::new(), + name: Tokens::new(), + path: Tokens::new(), + rate_limited: Tokens::new(), + requires_authentication: Tokens::new(), + }, + request: Request, + response: Response, } } } @@ -82,104 +62,6 @@ struct Metadata { 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 { @@ -197,9 +79,3 @@ impl From for 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/src/parse.rs b/src/parse.rs new file mode 100644 index 00000000..b9651276 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,144 @@ +use syn::{ + Attribute, + AttrStyle, + Expr, + Field, + Ident, + Item, + Lit, + MetaItem, + NestedMetaItem, + StrStyle, + Token, + TokenTree, + Visibility, +}; +use syn::parse::{expr, ident, lit, ty}; +use synom::space::{block_comment, whitespace}; + +pub enum Entry { + Metadata(Vec<(Ident, Expr)>), + Request(Vec), + Response(Vec), +} + +named!(pub parse_entries -> Vec, do_parse!( + entries: many0!(entry) >> + (entries) +)); + +named!(entry -> Entry, alt!( + do_parse!( + keyword!("metadata") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_init_field) >> + (Entry::Metadata(fields)) + ) + | + do_parse!( + keyword!("request") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_field) >> + (Entry::Request(fields)) + ) + | + do_parse!( + keyword!("response") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_field) >> + (Entry::Response(fields)) + ) +)); + +// Everything below copy/pasted from syn 0.11.11. + +named!(struct_init_field -> (Ident, Expr), do_parse!( + ident: ident >> + punct!(":") >> + expr: expr >> + punct!(",") >> + (ident, expr) +)); + +named!(struct_field -> Field, do_parse!( + attrs: many0!(outer_attr) >> + id: ident >> + punct!(":") >> + ty: ty >> + (Field { + ident: Some(id), + vis: Visibility::Public, + attrs: attrs, + ty: ty, + }) +)); + +named!(outer_attr -> Attribute, alt!( + do_parse!( + punct!("#") >> + punct!("[") >> + meta_item: meta_item >> + punct!("]") >> + (Attribute { + style: AttrStyle::Outer, + value: meta_item, + is_sugared_doc: false, + }) + ) + | + do_parse!( + punct!("///") >> + not!(tag!("/")) >> + content: take_until!("\n") >> + (Attribute { + style: AttrStyle::Outer, + value: MetaItem::NameValue( + "doc".into(), + format!("///{}", content).into(), + ), + is_sugared_doc: true, + }) + ) + | + do_parse!( + option!(whitespace) >> + peek!(tuple!(tag!("/**"), not!(tag!("*")))) >> + com: block_comment >> + (Attribute { + style: AttrStyle::Outer, + value: MetaItem::NameValue( + "doc".into(), + com.into(), + ), + is_sugared_doc: true, + }) + ) +)); + +named!(meta_item -> MetaItem, alt!( + do_parse!( + id: ident >> + punct!("(") >> + inner: terminated_list!(punct!(","), nested_meta_item) >> + punct!(")") >> + (MetaItem::List(id, inner)) + ) + | + do_parse!( + name: ident >> + punct!("=") >> + value: lit >> + (MetaItem::NameValue(name, value)) + ) + | + map!(ident, MetaItem::Word) +)); + +named!(nested_meta_item -> NestedMetaItem, alt!( + meta_item => { NestedMetaItem::MetaItem } + | + lit => { NestedMetaItem::Literal } +)); diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 881989a7..1464de06 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -8,18 +8,16 @@ pub mod get_supported_versions { use ruma_api_macros::ruma_api; ruma_api! { - const METADATA: 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 { + }, + request: {}, + response: { /// A list of Matrix client API protocol versions supported by the homeserver. pub versions: Vec, } From d0a35341a2db1197232dc31fd5481d69ace8ea8b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:16:44 -0700 Subject: [PATCH 003/107] Use three block form for the macro, fix some bugs, construct metadata tokens. --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++-------- src/parse.rs | 27 +++++++++--- tests/ruma_api_macros.rs | 10 +++-- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ad4079a..e8d5119b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ extern crate syn; use proc_macro::TokenStream; -use quote::Tokens; +use quote::{ToTokens, Tokens}; use syn::{Expr, Field, Ident, Item}; use parse::{Entry, parse_entries}; @@ -38,17 +38,26 @@ impl Api { impl From> for Api { fn from(entries: Vec) -> Api { + if entries.len() != 3 { + panic!("ruma_api! expects 3 blocks: metadata, request, and response"); + } + + let mut metadata = None; + let mut request = None; + let mut response = None; + + for entry in entries { + match entry { + Entry::Metadata(fields) => metadata = Some(Metadata::from(fields)), + Entry::Request(fields) => request = Some(Request::from(fields)), + Entry::Response(fields) => response = Some(Response::from(fields)), + } + } + Api { - metadata: Metadata { - description: Tokens::new(), - method: Tokens::new(), - name: Tokens::new(), - path: Tokens::new(), - rate_limited: Tokens::new(), - requires_authentication: Tokens::new(), - }, - request: Request, - response: Response, + metadata: metadata.expect("ruma_api! is missing metadata"), + request: request.expect("ruma_api! is missing request"), + response: response.expect("ruma_api! is missing response"), } } } @@ -62,20 +71,68 @@ struct Metadata { requires_authentication: Tokens, } +impl From> for Metadata { + fn from(fields: Vec<(Ident, Expr)>) -> Self { + 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 in fields { + let (identifier, expression) = field; + + if identifier == Ident::new("description") { + description = Some(tokens_for(expression)); + } else if identifier == Ident::new("method") { + method = Some(tokens_for(expression)); + } else if identifier == Ident::new("name") { + name = Some(tokens_for(expression)); + } else if identifier == Ident::new("path") { + path = Some(tokens_for(expression)); + } else if identifier == Ident::new("rate_limited") { + rate_limited = Some(tokens_for(expression)); + } else if identifier == Ident::new("requires_authentication") { + requires_authentication = Some(tokens_for(expression)); + } else { + panic!("ruma_api! metadata included unexpected field: {}", identifier); + } + } + + Metadata { + description: description.expect("ruma_api! metadata is missing description"), + method: method.expect("ruma_api! metadata is missing method"), + name: name.expect("ruma_api! metadata is missing name"), + path: path.expect("ruma_api! metadata is missing path"), + rate_limited: rate_limited.expect("ruma_api! metadata is missing rate_limited"), + requires_authentication: requires_authentication + .expect("ruma_api! metadata is missing requires_authentication"), + } + } +} + struct Request; -impl From for Request { - fn from(item: Item) -> Self { +impl From> for Request { + fn from(fields: Vec) -> Self { Request - // panic!("ruma_api! could not parse Request"); } } struct Response; -impl From for Response { - fn from(item: Item) -> Self { +impl From> for Response { + fn from(fields: Vec) -> Self { Response - // panic!("ruma_api! could not parse Response"); } } + +/// Helper method for turning a value into tokens. +fn tokens_for(value: T) -> Tokens where T: ToTokens { + let mut tokens = Tokens::new(); + + value.to_tokens(&mut tokens); + + tokens +} diff --git a/src/parse.rs b/src/parse.rs index b9651276..03cc5ffe 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -16,6 +16,7 @@ use syn::{ use syn::parse::{expr, ident, lit, ty}; use synom::space::{block_comment, whitespace}; +#[derive(Debug)] pub enum Entry { Metadata(Vec<(Ident, Expr)>), Request(Vec), @@ -30,25 +31,25 @@ named!(pub parse_entries -> Vec, do_parse!( named!(entry -> Entry, alt!( do_parse!( keyword!("metadata") >> - punct!(":") >> punct!("{") >> fields: many0!(struct_init_field) >> + punct!("}") >> (Entry::Metadata(fields)) ) | do_parse!( keyword!("request") >> - punct!(":") >> punct!("{") >> - fields: many0!(struct_field) >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> (Entry::Request(fields)) ) | do_parse!( keyword!("response") >> - punct!(":") >> punct!("{") >> - fields: many0!(struct_field) >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> (Entry::Response(fields)) ) )); @@ -63,14 +64,22 @@ named!(struct_init_field -> (Ident, Expr), do_parse!( (ident, expr) )); +named!(pub struct_like_body -> Vec, do_parse!( + punct!("{") >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> + (fields) +)); + named!(struct_field -> Field, do_parse!( attrs: many0!(outer_attr) >> + vis: visibility >> id: ident >> punct!(":") >> ty: ty >> (Field { ident: Some(id), - vis: Visibility::Public, + vis: Visibility::Public, // Ignore declared visibility, always make fields public attrs: attrs, ty: ty, }) @@ -142,3 +151,9 @@ named!(nested_meta_item -> NestedMetaItem, alt!( | lit => { NestedMetaItem::Literal } )); + +named!(visibility -> Visibility, alt!( + keyword!("pub") => { |_| Visibility::Public } + | + epsilon!() => { |_| Visibility::Inherited } +)); diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 1464de06..b65a5aca 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -8,16 +8,18 @@ pub mod get_supported_versions { use ruma_api_macros::ruma_api; ruma_api! { - 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, - }, - request: {}, - response: { + } + + request {} + + response { /// A list of Matrix client API protocol versions supported by the homeserver. pub versions: Vec, } From 446ced1267401228c131d4479e8d5cfb5bc64d0a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:17:33 -0700 Subject: [PATCH 004/107] Disable doctests. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 8b16922b..90c5b376 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ features = ["full"] version = "0.11.11" [lib] +doctest = false proc-macro = true From 55d6b72a77a92aedc37fbcfe0ac058d1672a9924 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:27:55 -0700 Subject: [PATCH 005/107] Add struct fields to Request and Response. --- src/lib.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e8d5119b..24237402 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,19 +112,39 @@ impl From> for Metadata { } } -struct Request; +struct Request { + fields: Vec, + body_fields: Vec, + header_fields: Vec, + path_fields: Vec, + query_string_fields: Vec, +} impl From> for Request { fn from(fields: Vec) -> Self { - Request + Request { + fields: fields, + body_fields: vec![], + header_fields: vec![], + path_fields: vec![], + query_string_fields: vec![], + } } } -struct Response; +struct Response { + fields: Vec, + body_fields: Vec, + header_fields: Vec, +} impl From> for Response { fn from(fields: Vec) -> Self { - Response + Response { + fields: fields, + body_fields: vec![], + header_fields: vec![], + } } } From b1be0f411f174fa81313bc5d9c9205e8adcc1adf Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:41:33 -0700 Subject: [PATCH 006/107] Add docs and remove unused code/imports. --- src/lib.rs | 6 +++++- src/parse.rs | 16 +++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 24237402..6a397ba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! Crate `ruma-api-macros` provides a procedural macro for easily generating `ruma-api` endpoints. + +#![deny(missing_debug_implementations)] #![feature(proc_macro)] extern crate proc_macro; @@ -9,12 +12,13 @@ extern crate syn; use proc_macro::TokenStream; use quote::{ToTokens, Tokens}; -use syn::{Expr, Field, Ident, Item}; +use syn::{Expr, Field, Ident}; use parse::{Entry, parse_entries}; mod parse; +/// Generates a `ruma-api` endpoint. #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); diff --git a/src/parse.rs b/src/parse.rs index 03cc5ffe..4b4a0e80 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,16 +1,13 @@ +//! Implementation details of parsing proc macro input. + use syn::{ Attribute, AttrStyle, Expr, Field, Ident, - Item, - Lit, MetaItem, NestedMetaItem, - StrStyle, - Token, - TokenTree, Visibility, }; use syn::parse::{expr, ident, lit, ty}; @@ -64,16 +61,9 @@ named!(struct_init_field -> (Ident, Expr), do_parse!( (ident, expr) )); -named!(pub struct_like_body -> Vec, do_parse!( - punct!("{") >> - fields: terminated_list!(punct!(","), struct_field) >> - punct!("}") >> - (fields) -)); - named!(struct_field -> Field, do_parse!( attrs: many0!(outer_attr) >> - vis: visibility >> + visibility >> id: ident >> punct!(":") >> ty: ty >> From 27349e57abe154dc50db65e11a7e754e92ad36e5 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:55:32 -0700 Subject: [PATCH 007/107] Add initial implementation of macro expansion. --- src/lib.rs | 63 ++++++++++++++++++++++++++++++++++++++-- tests/ruma_api_macros.rs | 2 +- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6a397ba4..15a3e41c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,10 @@ #![deny(missing_debug_implementations)] #![feature(proc_macro)] +#![recursion_limit="128"] extern crate proc_macro; -extern crate quote; +#[macro_use] extern crate quote; extern crate ruma_api; extern crate syn; #[macro_use] extern crate synom; @@ -35,8 +36,64 @@ struct Api { } impl Api { - fn output(&self) -> Tokens { - Tokens::new() + fn output(self) -> Tokens { + let description = self.metadata.description; + let method = self.metadata.method; + let name = self.metadata.name; + let path = self.metadata.path; + let rate_limited = self.metadata.rate_limited; + let requires_authentication = self.metadata.requires_authentication; + + quote! { + use std::convert::TryFrom; + + /// The API endpoint. + #[derive(Debug)] + pub struct Endpoint; + + /// Data for a request to this API endpoint. + #[derive(Debug)] + pub struct Request; + + impl TryFrom for ::hyper::Request { + type Error = (); + + fn try_from(request: Request) -> Result { + Ok( + ::hyper::Request::new( + ::hyper::#method, + "/".parse().expect("failed to parse request URI"), + ) + ) + } + } + + /// Data in the response from this API endpoint. + #[derive(Debug)] + pub struct Response; + + impl TryFrom<::hyper::Response> for Response { + type Error = (); + + fn try_from(hyper_response: ::hyper::Response) -> Result { + Ok(Response) + } + } + + impl ::ruma_api::Endpoint for Endpoint { + type Request = Request; + type Response = Response; + + const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { + description: #description, + method: ::hyper::#method, + name: #name, + path: #path, + rate_limited: #rate_limited, + requires_authentication: #requires_authentication, + }; + } + } } } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index b65a5aca..05385fc9 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro)] +#![feature(associated_consts, proc_macro, try_from)] extern crate hyper; extern crate ruma_api; From a3c855835a5e11652ffc37c72afb1a35826e192b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 02:02:40 -0700 Subject: [PATCH 008/107] Add methods for generating request and response types. --- src/lib.rs | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 15a3e41c..e5eafd80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,13 +36,16 @@ struct Api { } impl Api { - fn output(self) -> Tokens { - let description = self.metadata.description; - let method = self.metadata.method; - let name = self.metadata.name; - let path = self.metadata.path; - let rate_limited = self.metadata.rate_limited; - let requires_authentication = self.metadata.requires_authentication; + fn output(&self) -> Tokens { + let description = &self.metadata.description; + let method = &self.metadata.method; + let name = &self.metadata.name; + let path = &self.metadata.path; + let rate_limited = &self.metadata.rate_limited; + let requires_authentication = &self.metadata.requires_authentication; + + let request_types = self.generate_request_types(); + let response_types = self.generate_response_types(); quote! { use std::convert::TryFrom; @@ -51,9 +54,7 @@ impl Api { #[derive(Debug)] pub struct Endpoint; - /// Data for a request to this API endpoint. - #[derive(Debug)] - pub struct Request; + #request_types impl TryFrom for ::hyper::Request { type Error = (); @@ -68,9 +69,7 @@ impl Api { } } - /// Data in the response from this API endpoint. - #[derive(Debug)] - pub struct Response; + #response_types impl TryFrom<::hyper::Response> for Response { type Error = (); @@ -95,6 +94,26 @@ impl Api { } } } + + fn generate_request_types(&self) -> Tokens { + let mut tokens = quote! { + /// Data for a request to this API endpoint. + #[derive(Debug)] + pub struct Request; + }; + + tokens + } + + fn generate_response_types(&self) -> Tokens { + let mut tokens = quote! { + /// Data in the response from this API endpoint. + #[derive(Debug)] + pub struct Response; + }; + + tokens + } } impl From> for Api { From 187a23670886a773cc799f0aef69a53d91a3fefc Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 17:20:19 -0700 Subject: [PATCH 009/107] Categorize request fields. --- src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e5eafd80..2b4251fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ extern crate syn; use proc_macro::TokenStream; use quote::{ToTokens, Tokens}; -use syn::{Expr, Field, Ident}; +use syn::{Expr, Field, Ident, Lit, MetaItem}; use parse::{Entry, parse_entries}; @@ -29,6 +29,7 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { api.output().parse().expect("ruma_api! failed to parse output as a TokenStream") } +#[derive(Debug)] struct Api { metadata: Metadata, request: Request, @@ -142,6 +143,7 @@ impl From> for Api { } } +#[derive(Debug)] struct Metadata { description: Tokens, method: Tokens, @@ -192,26 +194,58 @@ impl From> for Metadata { } } +#[derive(Debug)] struct Request { - fields: Vec, - body_fields: Vec, - header_fields: Vec, - path_fields: Vec, - query_string_fields: Vec, + fields: Vec, } impl From> for Request { fn from(fields: Vec) -> Self { + let request_fields = fields.into_iter().map(|field| { + for attr in field.attrs.clone().iter() { + match attr.value { + MetaItem::Word(ref ident) => { + if ident == "query" { + return RequestField::Query(field); + } + } + MetaItem::List(_, _) => {} + MetaItem::NameValue(ref ident, ref lit) => { + if ident == "header" { + if let Lit::Str(ref name, _) = *lit { + return RequestField::Header(name.clone(), field); + } else { + panic!("ruma_api! header attribute expects a string value"); + } + } else if ident == "path" { + if let Lit::Str(ref name, _) = *lit { + return RequestField::Path(name.clone(), field); + } else { + panic!("ruma_api! path attribute expects a string value"); + } + } + } + } + } + + return RequestField::Body(field); + }).collect(); + Request { - fields: fields, - body_fields: vec![], - header_fields: vec![], - path_fields: vec![], - query_string_fields: vec![], + fields: request_fields, } } } +#[derive(Debug)] +enum RequestField { + Body(Field), + Header(String, Field), + Path(String, Field), + Query(Field), +} + +#[derive(Debug)] struct Response { fields: Vec, body_fields: Vec, From b1d5d50e91f8062f586db1a87d346a7f14049983 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 17:22:53 -0700 Subject: [PATCH 010/107] Categorize response fields. --- src/lib.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2b4251fb..a0b53232 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,21 +247,42 @@ enum RequestField { #[derive(Debug)] struct Response { - fields: Vec, - body_fields: Vec, - header_fields: Vec, + fields: Vec, } impl From> for Response { fn from(fields: Vec) -> Self { + let response_fields = fields.into_iter().map(|field| { + for attr in field.attrs.clone().iter() { + match attr.value { + MetaItem::Word(_) | MetaItem::List(_, _) => {} + MetaItem::NameValue(ref ident, ref lit) => { + if ident == "header" { + if let Lit::Str(ref name, _) = *lit { + return ResponseField::Header(name.clone(), field); + } else { + panic!("ruma_api! header attribute expects a string value"); + } + } + } + } + } + + return ResponseField::Body(field); + }).collect(); + Response { - fields: fields, - body_fields: vec![], - header_fields: vec![], + fields: response_fields, } } } +#[derive(Debug)] +enum ResponseField { + Body(Field), + Header(String, Field), +} + /// Helper method for turning a value into tokens. fn tokens_for(value: T) -> Tokens where T: ToTokens { let mut tokens = Tokens::new(); From 029daf3e1221a1192ae3eab028f89579438f8ce0 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 17:52:17 -0700 Subject: [PATCH 011/107] Split code into more modules. --- src/api.rs | 154 +++++++++++++++++++++++++++ src/lib.rs | 273 ++---------------------------------------------- src/metadata.rs | 62 +++++++++++ src/request.rs | 52 +++++++++ src/response.rs | 39 +++++++ 5 files changed, 313 insertions(+), 267 deletions(-) create mode 100644 src/api.rs create mode 100644 src/metadata.rs create mode 100644 src/request.rs create mode 100644 src/response.rs diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000..c812f941 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,154 @@ +use quote::{ToTokens, Tokens}; + +use metadata::Metadata; +use parse::Entry; +use request::{Request, RequestField}; +use response::{Response, ResponseField}; + +#[derive(Debug)] +pub struct Api { + metadata: Metadata, + request: Request, + response: Response, +} + +impl Api { + pub fn output(&self) -> Tokens { + let description = &self.metadata.description; + let method = &self.metadata.method; + let name = &self.metadata.name; + let path = &self.metadata.path; + let rate_limited = &self.metadata.rate_limited; + let requires_authentication = &self.metadata.requires_authentication; + + let request_types = self.generate_request_types(); + let response_types = self.generate_response_types(); + + quote! { + use std::convert::TryFrom; + + /// The API endpoint. + #[derive(Debug)] + pub struct Endpoint; + + #request_types + + impl TryFrom for ::hyper::Request { + type Error = (); + + fn try_from(request: Request) -> Result { + Ok( + ::hyper::Request::new( + ::hyper::#method, + "/".parse().expect("failed to parse request URI"), + ) + ) + } + } + + #response_types + + impl TryFrom<::hyper::Response> for Response { + type Error = (); + + fn try_from(hyper_response: ::hyper::Response) -> Result { + Ok(Response) + } + } + + impl ::ruma_api::Endpoint for Endpoint { + type Request = Request; + type Response = Response; + + const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { + description: #description, + method: ::hyper::#method, + name: #name, + path: #path, + rate_limited: #rate_limited, + requires_authentication: #requires_authentication, + }; + } + } + } + + fn generate_request_types(&self) -> Tokens { + let mut tokens = quote! { + /// Data for a request to this API endpoint. + #[derive(Debug)] + pub struct Request + }; + + if self.request.fields.len() == 0 { + tokens.append(";"); + } else { + tokens.append("{"); + + for request_field in self.request.fields.iter() { + match *request_field { + RequestField::Body(ref field) => field.to_tokens(&mut tokens), + RequestField::Header(_, ref field) => field.to_tokens(&mut tokens), + RequestField::Path(_, ref field) => field.to_tokens(&mut tokens), + RequestField::Query(ref field) => field.to_tokens(&mut tokens), + } + } + + tokens.append("}"); + } + + tokens + } + + fn generate_response_types(&self) -> Tokens { + let mut tokens = quote! { + /// Data in the response from this API endpoint. + #[derive(Debug)] + pub struct Response + }; + + if self.response.fields.len() == 0 { + tokens.append(";"); + } else { + tokens.append("{"); + + for response in self.response.fields.iter() { + match *response { + ResponseField::Body(ref field) => field.to_tokens(&mut tokens), + ResponseField::Header(_, ref field) => field.to_tokens(&mut tokens), + } + } + + tokens.append("}"); + } + + tokens + } +} + +impl From> for Api { + fn from(entries: Vec) -> Api { + if entries.len() != 3 { + panic!("ruma_api! expects 3 blocks: metadata, request, and response"); + } + + let mut metadata = None; + let mut request = None; + let mut response = None; + + for entry in entries { + match entry { + Entry::Metadata(fields) => metadata = Some(Metadata::from(fields)), + Entry::Request(fields) => request = Some(Request::from(fields)), + Entry::Response(fields) => response = Some(Response::from(fields)), + } + } + + Api { + metadata: metadata.expect("ruma_api! is missing metadata"), + request: request.expect("ruma_api! is missing request"), + response: response.expect("ruma_api! is missing response"), + } + } +} + + diff --git a/src/lib.rs b/src/lib.rs index a0b53232..0041bae1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,14 @@ extern crate syn; use proc_macro::TokenStream; -use quote::{ToTokens, Tokens}; -use syn::{Expr, Field, Ident, Lit, MetaItem}; - -use parse::{Entry, parse_entries}; +use api::Api; +use parse::parse_entries; +mod api; +mod metadata; mod parse; +mod request; +mod response; /// Generates a `ruma-api` endpoint. #[proc_macro] @@ -28,266 +30,3 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { api.output().parse().expect("ruma_api! failed to parse output as a TokenStream") } - -#[derive(Debug)] -struct Api { - metadata: Metadata, - request: Request, - response: Response, -} - -impl Api { - fn output(&self) -> Tokens { - let description = &self.metadata.description; - let method = &self.metadata.method; - let name = &self.metadata.name; - let path = &self.metadata.path; - let rate_limited = &self.metadata.rate_limited; - let requires_authentication = &self.metadata.requires_authentication; - - let request_types = self.generate_request_types(); - let response_types = self.generate_response_types(); - - quote! { - use std::convert::TryFrom; - - /// The API endpoint. - #[derive(Debug)] - pub struct Endpoint; - - #request_types - - impl TryFrom for ::hyper::Request { - type Error = (); - - fn try_from(request: Request) -> Result { - Ok( - ::hyper::Request::new( - ::hyper::#method, - "/".parse().expect("failed to parse request URI"), - ) - ) - } - } - - #response_types - - impl TryFrom<::hyper::Response> for Response { - type Error = (); - - fn try_from(hyper_response: ::hyper::Response) -> Result { - Ok(Response) - } - } - - impl ::ruma_api::Endpoint for Endpoint { - type Request = Request; - type Response = Response; - - const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { - description: #description, - method: ::hyper::#method, - name: #name, - path: #path, - rate_limited: #rate_limited, - requires_authentication: #requires_authentication, - }; - } - } - } - - fn generate_request_types(&self) -> Tokens { - let mut tokens = quote! { - /// Data for a request to this API endpoint. - #[derive(Debug)] - pub struct Request; - }; - - tokens - } - - fn generate_response_types(&self) -> Tokens { - let mut tokens = quote! { - /// Data in the response from this API endpoint. - #[derive(Debug)] - pub struct Response; - }; - - tokens - } -} - -impl From> for Api { - fn from(entries: Vec) -> Api { - if entries.len() != 3 { - panic!("ruma_api! expects 3 blocks: metadata, request, and response"); - } - - let mut metadata = None; - let mut request = None; - let mut response = None; - - for entry in entries { - match entry { - Entry::Metadata(fields) => metadata = Some(Metadata::from(fields)), - Entry::Request(fields) => request = Some(Request::from(fields)), - Entry::Response(fields) => response = Some(Response::from(fields)), - } - } - - Api { - metadata: metadata.expect("ruma_api! is missing metadata"), - request: request.expect("ruma_api! is missing request"), - response: response.expect("ruma_api! is missing response"), - } - } -} - -#[derive(Debug)] -struct Metadata { - description: Tokens, - method: Tokens, - name: Tokens, - path: Tokens, - rate_limited: Tokens, - requires_authentication: Tokens, -} - -impl From> for Metadata { - fn from(fields: Vec<(Ident, Expr)>) -> Self { - 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 in fields { - let (identifier, expression) = field; - - if identifier == Ident::new("description") { - description = Some(tokens_for(expression)); - } else if identifier == Ident::new("method") { - method = Some(tokens_for(expression)); - } else if identifier == Ident::new("name") { - name = Some(tokens_for(expression)); - } else if identifier == Ident::new("path") { - path = Some(tokens_for(expression)); - } else if identifier == Ident::new("rate_limited") { - rate_limited = Some(tokens_for(expression)); - } else if identifier == Ident::new("requires_authentication") { - requires_authentication = Some(tokens_for(expression)); - } else { - panic!("ruma_api! metadata included unexpected field: {}", identifier); - } - } - - Metadata { - description: description.expect("ruma_api! metadata is missing description"), - method: method.expect("ruma_api! metadata is missing method"), - name: name.expect("ruma_api! metadata is missing name"), - path: path.expect("ruma_api! metadata is missing path"), - rate_limited: rate_limited.expect("ruma_api! metadata is missing rate_limited"), - requires_authentication: requires_authentication - .expect("ruma_api! metadata is missing requires_authentication"), - } - } -} - -#[derive(Debug)] -struct Request { - fields: Vec, -} - -impl From> for Request { - fn from(fields: Vec) -> Self { - let request_fields = fields.into_iter().map(|field| { - for attr in field.attrs.clone().iter() { - match attr.value { - MetaItem::Word(ref ident) => { - if ident == "query" { - return RequestField::Query(field); - } - } - MetaItem::List(_, _) => {} - MetaItem::NameValue(ref ident, ref lit) => { - if ident == "header" { - if let Lit::Str(ref name, _) = *lit { - return RequestField::Header(name.clone(), field); - } else { - panic!("ruma_api! header attribute expects a string value"); - } - } else if ident == "path" { - if let Lit::Str(ref name, _) = *lit { - return RequestField::Path(name.clone(), field); - } else { - panic!("ruma_api! path attribute expects a string value"); - } - } - } - } - } - - return RequestField::Body(field); - }).collect(); - - Request { - fields: request_fields, - } - } -} - -#[derive(Debug)] -enum RequestField { - Body(Field), - Header(String, Field), - Path(String, Field), - Query(Field), -} - -#[derive(Debug)] -struct Response { - fields: Vec, -} - -impl From> for Response { - fn from(fields: Vec) -> Self { - let response_fields = fields.into_iter().map(|field| { - for attr in field.attrs.clone().iter() { - match attr.value { - MetaItem::Word(_) | MetaItem::List(_, _) => {} - MetaItem::NameValue(ref ident, ref lit) => { - if ident == "header" { - if let Lit::Str(ref name, _) = *lit { - return ResponseField::Header(name.clone(), field); - } else { - panic!("ruma_api! header attribute expects a string value"); - } - } - } - } - } - - return ResponseField::Body(field); - }).collect(); - - Response { - fields: response_fields, - } - } -} - -#[derive(Debug)] -enum ResponseField { - Body(Field), - Header(String, Field), -} - -/// Helper method for turning a value into tokens. -fn tokens_for(value: T) -> Tokens where T: ToTokens { - let mut tokens = Tokens::new(); - - value.to_tokens(&mut tokens); - - tokens -} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 00000000..e0de6841 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,62 @@ +use quote::{ToTokens, Tokens}; +use syn::{Expr, Ident}; + +#[derive(Debug)] +pub struct Metadata { + pub description: Tokens, + pub method: Tokens, + pub name: Tokens, + pub path: Tokens, + pub rate_limited: Tokens, + pub requires_authentication: Tokens, +} + +impl From> for Metadata { + fn from(fields: Vec<(Ident, Expr)>) -> Self { + 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 in fields { + let (identifier, expression) = field; + + if identifier == Ident::new("description") { + description = Some(tokens_for(expression)); + } else if identifier == Ident::new("method") { + method = Some(tokens_for(expression)); + } else if identifier == Ident::new("name") { + name = Some(tokens_for(expression)); + } else if identifier == Ident::new("path") { + path = Some(tokens_for(expression)); + } else if identifier == Ident::new("rate_limited") { + rate_limited = Some(tokens_for(expression)); + } else if identifier == Ident::new("requires_authentication") { + requires_authentication = Some(tokens_for(expression)); + } else { + panic!("ruma_api! metadata included unexpected field: {}", identifier); + } + } + + Metadata { + description: description.expect("ruma_api! metadata is missing description"), + method: method.expect("ruma_api! metadata is missing method"), + name: name.expect("ruma_api! metadata is missing name"), + path: path.expect("ruma_api! metadata is missing path"), + rate_limited: rate_limited.expect("ruma_api! metadata is missing rate_limited"), + requires_authentication: requires_authentication + .expect("ruma_api! metadata is missing requires_authentication"), + } + } +} + +/// Helper method for turning a value into tokens. +fn tokens_for(value: T) -> Tokens where T: ToTokens { + let mut tokens = Tokens::new(); + + value.to_tokens(&mut tokens); + + tokens +} diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 00000000..bfd0f6ee --- /dev/null +++ b/src/request.rs @@ -0,0 +1,52 @@ +use syn::{Field, Lit, MetaItem}; + +#[derive(Debug)] +pub struct Request { + pub fields: Vec, +} + +impl From> for Request { + fn from(fields: Vec) -> Self { + let request_fields = fields.into_iter().map(|field| { + for attr in field.attrs.clone().iter() { + match attr.value { + MetaItem::Word(ref ident) => { + if ident == "query" { + return RequestField::Query(field); + } + } + MetaItem::List(_, _) => {} + MetaItem::NameValue(ref ident, ref lit) => { + if ident == "header" { + if let Lit::Str(ref name, _) = *lit { + return RequestField::Header(name.clone(), field); + } else { + panic!("ruma_api! header attribute expects a string value"); + } + } else if ident == "path" { + if let Lit::Str(ref name, _) = *lit { + return RequestField::Path(name.clone(), field); + } else { + panic!("ruma_api! path attribute expects a string value"); + } + } + } + } + } + + return RequestField::Body(field); + }).collect(); + + Request { + fields: request_fields, + } + } +} + +#[derive(Debug)] +pub enum RequestField { + Body(Field), + Header(String, Field), + Path(String, Field), + Query(Field), +} diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 00000000..16a45e0d --- /dev/null +++ b/src/response.rs @@ -0,0 +1,39 @@ +use syn::{Field, Lit, MetaItem}; + +#[derive(Debug)] +pub struct Response { + pub fields: Vec, +} + +impl From> for Response { + fn from(fields: Vec) -> Self { + let response_fields = fields.into_iter().map(|field| { + for attr in field.attrs.clone().iter() { + match attr.value { + MetaItem::Word(_) | MetaItem::List(_, _) => {} + MetaItem::NameValue(ref ident, ref lit) => { + if ident == "header" { + if let Lit::Str(ref name, _) = *lit { + return ResponseField::Header(name.clone(), field); + } else { + panic!("ruma_api! header attribute expects a string value"); + } + } + } + } + } + + return ResponseField::Body(field); + }).collect(); + + Response { + fields: response_fields, + } + } +} + +#[derive(Debug)] +pub enum ResponseField { + Body(Field), + Header(String, Field), +} From 06388333af8d9364dbbe78c815e7d0743cd7a4d5 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 18:16:43 -0700 Subject: [PATCH 012/107] Implement ToTokens for Api, Request, and Response. --- src/api.rs | 78 ++++++++++--------------------------------------- src/lib.rs | 8 ++++- src/request.rs | 30 ++++++++++++++++++- src/response.rs | 28 +++++++++++++++++- 4 files changed, 79 insertions(+), 65 deletions(-) diff --git a/src/api.rs b/src/api.rs index c812f941..8af8229d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,8 +2,8 @@ use quote::{ToTokens, Tokens}; use metadata::Metadata; use parse::Entry; -use request::{Request, RequestField}; -use response::{Response, ResponseField}; +use request::Request; +use response::Response; #[derive(Debug)] pub struct Api { @@ -12,8 +12,8 @@ pub struct Api { response: Response, } -impl Api { - pub fn output(&self) -> Tokens { +impl ToTokens for Api { + fn to_tokens(&self, tokens: &mut Tokens) { let description = &self.metadata.description; let method = &self.metadata.method; let name = &self.metadata.name; @@ -21,10 +21,18 @@ impl Api { let rate_limited = &self.metadata.rate_limited; let requires_authentication = &self.metadata.requires_authentication; - let request_types = self.generate_request_types(); - let response_types = self.generate_response_types(); + let request_types = { + let mut tokens = Tokens::new(); + self.request.to_tokens(&mut tokens); + tokens + }; + let response_types = { + let mut tokens = Tokens::new(); + self.response.to_tokens(&mut tokens); + tokens + }; - quote! { + tokens.append(quote! { use std::convert::TryFrom; /// The API endpoint. @@ -69,59 +77,7 @@ impl Api { requires_authentication: #requires_authentication, }; } - } - } - - fn generate_request_types(&self) -> Tokens { - let mut tokens = quote! { - /// Data for a request to this API endpoint. - #[derive(Debug)] - pub struct Request - }; - - if self.request.fields.len() == 0 { - tokens.append(";"); - } else { - tokens.append("{"); - - for request_field in self.request.fields.iter() { - match *request_field { - RequestField::Body(ref field) => field.to_tokens(&mut tokens), - RequestField::Header(_, ref field) => field.to_tokens(&mut tokens), - RequestField::Path(_, ref field) => field.to_tokens(&mut tokens), - RequestField::Query(ref field) => field.to_tokens(&mut tokens), - } - } - - tokens.append("}"); - } - - tokens - } - - fn generate_response_types(&self) -> Tokens { - let mut tokens = quote! { - /// Data in the response from this API endpoint. - #[derive(Debug)] - pub struct Response - }; - - if self.response.fields.len() == 0 { - tokens.append(";"); - } else { - tokens.append("{"); - - for response in self.response.fields.iter() { - match *response { - ResponseField::Body(ref field) => field.to_tokens(&mut tokens), - ResponseField::Header(_, ref field) => field.to_tokens(&mut tokens), - } - } - - tokens.append("}"); - } - - tokens + }); } } @@ -150,5 +106,3 @@ impl From> for Api { } } } - - diff --git a/src/lib.rs b/src/lib.rs index 0041bae1..80e3805f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ extern crate syn; use proc_macro::TokenStream; +use quote::{ToTokens, Tokens}; + use api::Api; use parse::parse_entries; @@ -28,5 +30,9 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { let api = Api::from(entries); - api.output().parse().expect("ruma_api! failed to parse output as a TokenStream") + let mut tokens = Tokens::new(); + + api.to_tokens(&mut tokens); + + tokens.parse().expect("ruma_api! failed to parse output tokens as a TokenStream") } diff --git a/src/request.rs b/src/request.rs index bfd0f6ee..214498e5 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,8 +1,9 @@ +use quote::{ToTokens, Tokens}; use syn::{Field, Lit, MetaItem}; #[derive(Debug)] pub struct Request { - pub fields: Vec, + fields: Vec, } impl From> for Request { @@ -43,6 +44,33 @@ impl From> for Request { } } +impl ToTokens for Request { + fn to_tokens(&self, mut tokens: &mut Tokens) { + tokens.append(quote! { + /// Data for a request to this API endpoint. + #[derive(Debug)] + pub struct Request + }); + + if self.fields.len() == 0 { + tokens.append(";"); + } else { + tokens.append("{"); + + for request_field in self.fields.iter() { + match *request_field { + RequestField::Body(ref field) => field.to_tokens(&mut tokens), + RequestField::Header(_, ref field) => field.to_tokens(&mut tokens), + RequestField::Path(_, ref field) => field.to_tokens(&mut tokens), + RequestField::Query(ref field) => field.to_tokens(&mut tokens), + } + } + + tokens.append("}"); + } + } +} + #[derive(Debug)] pub enum RequestField { Body(Field), diff --git a/src/response.rs b/src/response.rs index 16a45e0d..35bfcc06 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,8 +1,9 @@ +use quote::{ToTokens, Tokens}; use syn::{Field, Lit, MetaItem}; #[derive(Debug)] pub struct Response { - pub fields: Vec, + fields: Vec, } impl From> for Response { @@ -32,6 +33,31 @@ impl From> for Response { } } +impl ToTokens for Response { + fn to_tokens(&self, mut tokens: &mut Tokens) { + tokens.append(quote! { + /// Data in the response from this API endpoint. + #[derive(Debug)] + pub struct Response + }); + + if self.fields.len() == 0 { + tokens.append(";"); + } else { + tokens.append("{"); + + for response in self.fields.iter() { + match *response { + ResponseField::Body(ref field) => field.to_tokens(&mut tokens), + ResponseField::Header(_, ref field) => field.to_tokens(&mut tokens), + } + } + + tokens.append("}"); + } + } +} + #[derive(Debug)] pub enum ResponseField { Body(Field), From ef3ee2d2f3b2ecbf911a1a55b46dbf5f84b6d663 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 18:30:19 -0700 Subject: [PATCH 013/107] Add RequestBody and ResponseBody structs. --- src/request.rs | 34 ++++++++++++++++++++++++++++++++++ src/response.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/request.rs b/src/request.rs index 214498e5..dbb45cd2 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,6 +6,12 @@ pub struct Request { fields: Vec, } +impl Request { + pub fn has_body_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_body()) + } +} + impl From> for Request { fn from(fields: Vec) -> Self { let request_fields = fields.into_iter().map(|field| { @@ -68,6 +74,25 @@ impl ToTokens for Request { tokens.append("}"); } + + if self.has_body_fields() { + tokens.append(quote! { + /// Data in the request body. + #[derive(Debug, Serialize)] + struct RequestBody + }); + + tokens.append("{"); + + for request_field in self.fields.iter() { + match *request_field { + RequestField::Body(ref field) => field.to_tokens(&mut tokens), + _ => {} + } + } + + tokens.append("}"); + } } } @@ -78,3 +103,12 @@ pub enum RequestField { Path(String, Field), Query(Field), } + +impl RequestField { + fn is_body(&self) -> bool { + match *self { + RequestField::Body(_) => true, + _ => false, + } + } +} diff --git a/src/response.rs b/src/response.rs index 35bfcc06..0f1b15b3 100644 --- a/src/response.rs +++ b/src/response.rs @@ -6,6 +6,12 @@ pub struct Response { fields: Vec, } +impl Response { + pub fn has_body_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_body()) + } +} + impl From> for Response { fn from(fields: Vec) -> Self { let response_fields = fields.into_iter().map(|field| { @@ -55,6 +61,25 @@ impl ToTokens for Response { tokens.append("}"); } + + if self.has_body_fields() { + tokens.append(quote! { + /// Data in the response body. + #[derive(Debug, Deserialize)] + struct ResponseBody + }); + + tokens.append("{"); + + for response_field in self.fields.iter() { + match *response_field { + ResponseField::Body(ref field) => field.to_tokens(&mut tokens), + _ => {} + } + } + + tokens.append("}"); + } } } @@ -63,3 +88,12 @@ pub enum ResponseField { Body(Field), Header(String, Field), } + +impl ResponseField { + fn is_body(&self) -> bool { + match *self { + ResponseField::Body(_) => true, + _ => false, + } + } +} From f48f1c1bee1669a2b385cd2d6e3e19242223d874 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 22:01:09 -0700 Subject: [PATCH 014/107] Add request body to hyper requests. --- Cargo.toml | 5 ++++ src/api.rs | 31 ++++++++++++++++++++----- src/request.rs | 50 ++++++++++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 2 ++ 4 files changed, 82 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90c5b376..d1964b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,11 @@ path = "../ruma-api" features = ["full"] version = "0.11.11" +[dev-dependencies] +serde = "1.0.4" +serde_derive = "1.0.4" +serde_json = "1.0.2" + [lib] doctest = false proc-macro = true diff --git a/src/api.rs b/src/api.rs index 8af8229d..382e0eec 100644 --- a/src/api.rs +++ b/src/api.rs @@ -32,6 +32,23 @@ impl ToTokens for Api { tokens }; + let add_body_to_request = if self.request.has_body_fields() { + let request_body_init_fields = self.request.request_body_init_fields(); + + quote! { + let request_body = RequestBody { + #request_body_init_fields + }; + + hyper_request.set_body( + ::serde_json::to_vec(&request_body) + .expect("failed to serialize request body to JSON") + ); + } + } else { + Tokens::new() + }; + tokens.append(quote! { use std::convert::TryFrom; @@ -45,12 +62,14 @@ impl ToTokens for Api { type Error = (); fn try_from(request: Request) -> Result { - Ok( - ::hyper::Request::new( - ::hyper::#method, - "/".parse().expect("failed to parse request URI"), - ) - ) + let mut hyper_request = ::hyper::Request::new( + ::hyper::#method, + "/".parse().expect("failed to parse request URI"), + ); + + #add_body_to_request + + Ok(hyper_request) } } diff --git a/src/request.rs b/src/request.rs index dbb45cd2..8b04786f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,6 +10,29 @@ impl Request { pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + + pub fn request_body_init_fields(&self) -> Tokens { + let mut tokens = Tokens::new(); + + for request_field in self.body_fields() { + let field = match *request_field { + RequestField::Body(ref field) => field, + _ => panic!("expected body field"), + }; + + let field_name = field.ident.as_ref().expect("expected body field to have a name"); + + tokens.append(quote! { + #field_name: request.#field_name, + }); + } + + tokens + } + + fn body_fields(&self) -> RequestBodyFields { + RequestBodyFields::new(&self.fields) + } } impl From> for Request { @@ -112,3 +135,30 @@ impl RequestField { } } } + +#[derive(Debug)] +pub struct RequestBodyFields<'a> { + fields: &'a [RequestField], + index: usize, +} + +impl<'a> RequestBodyFields<'a> { + pub fn new(fields: &'a [RequestField]) -> Self { + RequestBodyFields { + fields, + index: 0, + } + } +} + +impl<'a> Iterator for RequestBodyFields<'a> { + type Item = &'a RequestField; + + fn next(&mut self) -> Option<&'a RequestField> { + let value = self.fields.get(self.index); + + self.index += 1; + + value + } +} diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 05385fc9..9364d28a 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -3,6 +3,8 @@ extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; +extern crate serde; +#[macro_use] extern crate serde_derive; pub mod get_supported_versions { use ruma_api_macros::ruma_api; From 13c9daf21b6f33340d2de98d5f095bb05d6fcd78 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 23:03:56 -0700 Subject: [PATCH 015/107] Deserialize response body. --- Cargo.toml | 1 + src/api.rs | 35 ++++++++++++++++++++++++++++++++++- src/response.rs | 33 +++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 2 ++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d1964b7b..f35ccca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ features = ["full"] version = "0.11.11" [dev-dependencies] +futures = "0.1.13" serde = "1.0.4" serde_derive = "1.0.4" serde_json = "1.0.2" diff --git a/src/api.rs b/src/api.rs index 382e0eec..5efe2a9b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -49,8 +49,34 @@ impl ToTokens for Api { Tokens::new() }; + let deserialize_response_body = if self.response.has_body_fields() { + quote! { + let bytes = hyper_response.body().fold::<_, _, Result<_, ::hyper::Error>>( + Vec::new(), + |mut bytes, chunk| { + bytes.write_all(&chunk).expect("failed to append body chunk"); + + Ok(bytes) + }).wait().expect("failed to read response body chunks into byte vector"); + + let response_body: ResponseBody = ::serde_json::from_slice(bytes.as_slice()) + .expect("failed to deserialize body"); + } + } else { + Tokens::new() + }; + + let response_init_fields = if self.response.has_fields() { + self.response.init_fields() + } else { + Tokens::new() + }; + tokens.append(quote! { use std::convert::TryFrom; + use std::io::Write; + + use ::futures::{Future, Stream}; /// The API endpoint. #[derive(Debug)] @@ -61,6 +87,7 @@ impl ToTokens for Api { impl TryFrom for ::hyper::Request { type Error = (); + #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { let mut hyper_request = ::hyper::Request::new( ::hyper::#method, @@ -79,7 +106,13 @@ impl ToTokens for Api { type Error = (); fn try_from(hyper_response: ::hyper::Response) -> Result { - Ok(Response) + #deserialize_response_body + + let response = Response { + #response_init_fields + }; + + Ok(response) } } diff --git a/src/response.rs b/src/response.rs index 0f1b15b3..11067c35 100644 --- a/src/response.rs +++ b/src/response.rs @@ -10,6 +10,39 @@ impl Response { pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + + pub fn has_fields(&self) -> bool { + self.fields.len() != 0 + } + + pub fn init_fields(&self) -> Tokens { + let mut tokens = Tokens::new(); + + for response_field in self.fields.iter() { + match *response_field { + ResponseField::Body(ref field) => { + let field_name = field.ident.as_ref() + .expect("expected body field to have a name"); + + tokens.append(quote! { + #field_name: response_body.#field_name, + }); + } + ResponseField::Header(ref name, ref field) => { + let field_name = field.ident.as_ref() + .expect("expected body field to have a name"); + + tokens.append(quote! { + #field_name: hyper_response.headers() + .get_raw(#name) + .expect("missing expected request header: {}", #name), + }); + } + } + } + + tokens + } } impl From> for Response { diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 9364d28a..9b477386 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,10 +1,12 @@ #![feature(associated_consts, proc_macro, try_from)] +extern crate futures; extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate serde_json; pub mod get_supported_versions { use ruma_api_macros::ruma_api; From b6064d1e01ec86a480354039396c0337528bfad7 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 23:09:50 -0700 Subject: [PATCH 016/107] Use the real endpoint path for the hyper request. --- src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.rs b/src/api.rs index 5efe2a9b..80ca93c9 100644 --- a/src/api.rs +++ b/src/api.rs @@ -91,7 +91,7 @@ impl ToTokens for Api { fn try_from(request: Request) -> Result { let mut hyper_request = ::hyper::Request::new( ::hyper::#method, - "/".parse().expect("failed to parse request URI"), + #path.parse().expect("failed to parse request URI"), ); #add_body_to_request From 1d1ae0410ee49b62e314c3777876422b11fb26f7 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 23:14:07 -0700 Subject: [PATCH 017/107] Obfuscate imported traits and use the Endpoint trait to access metadata for hyper request construction. --- src/api.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/api.rs b/src/api.rs index 80ca93c9..7d753c32 100644 --- a/src/api.rs +++ b/src/api.rs @@ -73,10 +73,10 @@ impl ToTokens for Api { }; tokens.append(quote! { - use std::convert::TryFrom; - use std::io::Write; + use std::io::Write as _Write; - use ::futures::{Future, Stream}; + use ::futures::{Future as _Future, Stream as _Stream}; + use ::ruma_api::Endpoint as _RumaApiEndpoint; /// The API endpoint. #[derive(Debug)] @@ -84,14 +84,16 @@ impl ToTokens for Api { #request_types - impl TryFrom for ::hyper::Request { + impl ::std::convert::TryFrom for ::hyper::Request { type Error = (); #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { + let metadata = Endpoint::METADATA; + let mut hyper_request = ::hyper::Request::new( - ::hyper::#method, - #path.parse().expect("failed to parse request URI"), + metadata.method, + metadata.path.parse().expect("failed to parse request URI"), ); #add_body_to_request @@ -102,7 +104,7 @@ impl ToTokens for Api { #response_types - impl TryFrom<::hyper::Response> for Response { + impl ::std::convert::TryFrom<::hyper::Response> for Response { type Error = (); fn try_from(hyper_response: ::hyper::Response) -> Result { From 1a56b35f1786877e91f94d37e144dd0f13b1172c Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 03:12:47 -0700 Subject: [PATCH 018/107] Propagate errors instead of panicking. --- src/api.rs | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/api.rs b/src/api.rs index 7d753c32..8bd97866 100644 --- a/src/api.rs +++ b/src/api.rs @@ -40,32 +40,47 @@ impl ToTokens for Api { #request_body_init_fields }; - hyper_request.set_body( - ::serde_json::to_vec(&request_body) - .expect("failed to serialize request body to JSON") - ); + hyper_request.set_body(::serde_json::to_vec(&request_body)?); } } else { Tokens::new() }; let deserialize_response_body = if self.response.has_body_fields() { - quote! { - let bytes = hyper_response.body().fold::<_, _, Result<_, ::hyper::Error>>( - Vec::new(), - |mut bytes, chunk| { - bytes.write_all(&chunk).expect("failed to append body chunk"); + let mut tokens = Tokens::new(); + + tokens.append(quote! { + hyper_response.body() + .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { + bytes.write_all(&chunk)?; Ok(bytes) - }).wait().expect("failed to read response body chunks into byte vector"); + }) + .map_err(::ruma_api::Error::from) + .and_then(|bytes| { + ::serde_json::from_slice::(bytes.as_slice()) + .map_err(::ruma_api::Error::from) + }) + }); - let response_body: ResponseBody = ::serde_json::from_slice(bytes.as_slice()) - .expect("failed to deserialize body"); - } + tokens.append(".and_then(|response_body| {"); + + tokens } else { - Tokens::new() + let mut tokens = Tokens::new(); + + tokens.append(quote! { + ::futures::future::ok(()) + }); + + tokens.append(".and_then(|_| {"); + + tokens }; + let mut closure_end = Tokens::new(); + closure_end.append("})"); + let response_init_fields = if self.response.has_fields() { self.response.init_fields() } else { @@ -85,7 +100,7 @@ impl ToTokens for Api { #request_types impl ::std::convert::TryFrom for ::hyper::Request { - type Error = (); + type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { @@ -93,7 +108,7 @@ impl ToTokens for Api { let mut hyper_request = ::hyper::Request::new( metadata.method, - metadata.path.parse().expect("failed to parse request URI"), + metadata.path.parse()?, ); #add_body_to_request @@ -105,7 +120,7 @@ impl ToTokens for Api { #response_types impl ::std::convert::TryFrom<::hyper::Response> for Response { - type Error = (); + type Error = ::ruma_api::Error; fn try_from(hyper_response: ::hyper::Response) -> Result { #deserialize_response_body @@ -115,6 +130,8 @@ impl ToTokens for Api { }; Ok(response) + #closure_end + .wait() } } From c55c71dd564128452a38b2f6da4690f6daebe4bc Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 04:11:35 -0700 Subject: [PATCH 019/107] Use FutureFrom instead of TryFrom for responses. --- src/api.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/api.rs b/src/api.rs index 8bd97866..bf5900f5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -50,7 +50,7 @@ impl ToTokens for Api { let mut tokens = Tokens::new(); tokens.append(quote! { - hyper_response.body() + let future_response = hyper_response.body() .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { bytes.write_all(&chunk)?; @@ -79,7 +79,7 @@ impl ToTokens for Api { }; let mut closure_end = Tokens::new(); - closure_end.append("})"); + closure_end.append("});"); let response_init_fields = if self.response.has_fields() { self.response.init_fields() @@ -119,10 +119,12 @@ impl ToTokens for Api { #response_types - impl ::std::convert::TryFrom<::hyper::Response> for Response { + impl ::futures::future::FutureFrom<::hyper::Response> for Response { + type Future = Box<_Future>; type Error = ::ruma_api::Error; - fn try_from(hyper_response: ::hyper::Response) -> Result { + fn future_from(hyper_response: ::hyper::Response) + -> Box<_Future> { #deserialize_response_body let response = Response { @@ -131,7 +133,8 @@ impl ToTokens for Api { Ok(response) #closure_end - .wait() + + Box::new(future_response) } } From 10f4647037c81cc3c35097f6156dd9706d38964a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 04:13:07 -0700 Subject: [PATCH 020/107] Use a Git version of ruma-api. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f35ccca2..756ede3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ git = "https://github.com/hyperium/hyper" rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" [dependencies.ruma-api] -path = "../ruma-api" +git = "https://github.com/ruma/ruma-api" +rev = "3635fe51ac31b9ff899c70a0d1218caa8cf6a8dc" [dependencies.syn] features = ["full"] From 3893ab00229c1360c16851b6c0aeda6f41636fc6 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 16:17:35 -0700 Subject: [PATCH 021/107] Add commas after struct fields. --- src/api.rs | 2 +- src/request.rs | 8 +++++++- src/response.rs | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/api.rs b/src/api.rs index bf5900f5..757d3205 100644 --- a/src/api.rs +++ b/src/api.rs @@ -70,7 +70,7 @@ impl ToTokens for Api { let mut tokens = Tokens::new(); tokens.append(quote! { - ::futures::future::ok(()) + let future_response = ::futures::future::ok(()) }); tokens.append(".and_then(|_| {"); diff --git a/src/request.rs b/src/request.rs index 8b04786f..a08c3e1e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -93,6 +93,8 @@ impl ToTokens for Request { RequestField::Path(_, ref field) => field.to_tokens(&mut tokens), RequestField::Query(ref field) => field.to_tokens(&mut tokens), } + + tokens.append(","); } tokens.append("}"); @@ -109,7 +111,11 @@ impl ToTokens for Request { for request_field in self.fields.iter() { match *request_field { - RequestField::Body(ref field) => field.to_tokens(&mut tokens), + RequestField::Body(ref field) => { + field.to_tokens(&mut tokens); + + tokens.append(","); + } _ => {} } } diff --git a/src/response.rs b/src/response.rs index 11067c35..beb83428 100644 --- a/src/response.rs +++ b/src/response.rs @@ -90,6 +90,8 @@ impl ToTokens for Response { ResponseField::Body(ref field) => field.to_tokens(&mut tokens), ResponseField::Header(_, ref field) => field.to_tokens(&mut tokens), } + + tokens.append(","); } tokens.append("}"); @@ -109,6 +111,8 @@ impl ToTokens for Response { ResponseField::Body(ref field) => field.to_tokens(&mut tokens), _ => {} } + + tokens.append(","); } tokens.append("}"); From f624e1ff50d55bca1205109bf3b139732b06b567 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 16:41:25 -0700 Subject: [PATCH 022/107] Yield only body fields from RequestBodyFields. --- src/request.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/request.rs b/src/request.rs index a08c3e1e..0e4d4992 100644 --- a/src/request.rs +++ b/src/request.rs @@ -161,10 +161,14 @@ impl<'a> Iterator for RequestBodyFields<'a> { type Item = &'a RequestField; fn next(&mut self) -> Option<&'a RequestField> { - let value = self.fields.get(self.index); + while let Some(value) = self.fields.get(self.index) { + self.index += 1; - self.index += 1; + if value.is_body() { + return Some(value); + } + } - value + None } } From 24370f3d4c03d3b5d0209a074cd8e48e2b89d340 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 17:34:21 -0700 Subject: [PATCH 023/107] Change syntax for meta items and remove them after use. --- src/request.rs | 85 +++++++++++++++++++++++++++++++++---------------- src/response.rs | 70 +++++++++++++++++++++++++++++----------- 2 files changed, 109 insertions(+), 46 deletions(-) diff --git a/src/request.rs b/src/request.rs index 0e4d4992..90dba2fc 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, Lit, MetaItem}; +use syn::{Field, MetaItem, NestedMetaItem}; #[derive(Debug)] pub struct Request { @@ -37,34 +37,56 @@ impl Request { impl From> for Request { fn from(fields: Vec) -> Self { - let request_fields = fields.into_iter().map(|field| { - for attr in field.attrs.clone().iter() { - match attr.value { - MetaItem::Word(ref ident) => { - if ident == "query" { - return RequestField::Query(field); - } - } - MetaItem::List(_, _) => {} - MetaItem::NameValue(ref ident, ref lit) => { - if ident == "header" { - if let Lit::Str(ref name, _) = *lit { - return RequestField::Header(name.clone(), field); - } else { - panic!("ruma_api! header attribute expects a string value"); - } - } else if ident == "path" { - if let Lit::Str(ref name, _) = *lit { - return RequestField::Path(name.clone(), field); - } else { - panic!("ruma_api! path attribute expects a string value"); + let request_fields = fields.into_iter().map(|mut field| { + let mut request_field_kind = RequestFieldKind::Body; + + field.attrs = field.attrs.into_iter().filter(|attr| { + let (attr_ident, nested_meta_items) = match attr.value { + MetaItem::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), + _ => return true, + }; + + if attr_ident != "ruma_api" { + return true; + } + + for nested_meta_item in nested_meta_items { + match *nested_meta_item { + NestedMetaItem::MetaItem(ref meta_item) => { + match *meta_item { + MetaItem::Word(ref ident) => { + if ident == "header" { + request_field_kind = RequestFieldKind::Header; + } else if ident == "path" { + request_field_kind = RequestFieldKind::Path; + } else if ident == "query" { + request_field_kind = RequestFieldKind::Query; + } else { + panic!( + "ruma_api! attribute meta item on requests must be: header, path, or query" + ); + } + } + _ => panic!( + "ruma_api! attribute meta item on requests cannot be a list or name/value pair" + ), } } + NestedMetaItem::Literal(_) => panic!( + "ruma_api! attribute meta item on requests must be: header, path, or query" + ), } } - } - return RequestField::Body(field); + false + }).collect(); + + match request_field_kind { + RequestFieldKind::Body => RequestField::Body(field), + RequestFieldKind::Header => RequestField::Header(field), + RequestFieldKind::Path => RequestField::Path(field), + RequestFieldKind::Query => RequestField::Query(field), + } }).collect(); Request { @@ -89,8 +111,8 @@ impl ToTokens for Request { for request_field in self.fields.iter() { match *request_field { RequestField::Body(ref field) => field.to_tokens(&mut tokens), - RequestField::Header(_, ref field) => field.to_tokens(&mut tokens), - RequestField::Path(_, ref field) => field.to_tokens(&mut tokens), + RequestField::Header(ref field) => field.to_tokens(&mut tokens), + RequestField::Path(ref field) => field.to_tokens(&mut tokens), RequestField::Query(ref field) => field.to_tokens(&mut tokens), } @@ -128,8 +150,8 @@ impl ToTokens for Request { #[derive(Debug)] pub enum RequestField { Body(Field), - Header(String, Field), - Path(String, Field), + Header(Field), + Path(Field), Query(Field), } @@ -142,6 +164,13 @@ impl RequestField { } } +enum RequestFieldKind { + Body, + Header, + Path, + Query, +} + #[derive(Debug)] pub struct RequestBodyFields<'a> { fields: &'a [RequestField], diff --git a/src/response.rs b/src/response.rs index beb83428..e2068e26 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, Lit, MetaItem}; +use syn::{Field, MetaItem, NestedMetaItem}; #[derive(Debug)] pub struct Response { @@ -28,14 +28,14 @@ impl Response { #field_name: response_body.#field_name, }); } - ResponseField::Header(ref name, ref field) => { + ResponseField::Header(ref field) => { let field_name = field.ident.as_ref() .expect("expected body field to have a name"); tokens.append(quote! { #field_name: hyper_response.headers() - .get_raw(#name) - .expect("missing expected request header: {}", #name), + .get_raw(#field_name) + .expect("missing expected request header: {}", #field_name), }); } } @@ -47,23 +47,52 @@ impl Response { impl From> for Response { fn from(fields: Vec) -> Self { - let response_fields = fields.into_iter().map(|field| { - for attr in field.attrs.clone().iter() { - match attr.value { - MetaItem::Word(_) | MetaItem::List(_, _) => {} - MetaItem::NameValue(ref ident, ref lit) => { - if ident == "header" { - if let Lit::Str(ref name, _) = *lit { - return ResponseField::Header(name.clone(), field); - } else { - panic!("ruma_api! header attribute expects a string value"); + let response_fields = fields.into_iter().map(|mut field| { + let mut response_field_kind = ResponseFieldKind::Body; + + field.attrs = field.attrs.into_iter().filter(|attr| { + let (attr_ident, nested_meta_items) = match attr.value { + MetaItem::List(ref attr_ident, ref nested_meta_items) => { + (attr_ident, nested_meta_items) + } + _ => return true, + }; + + if attr_ident != "ruma_api" { + return true; + } + + for nested_meta_item in nested_meta_items { + match *nested_meta_item { + NestedMetaItem::MetaItem(ref meta_item) => { + match *meta_item { + MetaItem::Word(ref ident) => { + if ident == "header" { + response_field_kind = ResponseFieldKind::Header; + } else { + panic!( + "ruma_api! attribute meta item on responses must be: header" + ); + } + } + _ => panic!( + "ruma_api! attribute meta item on requests cannot be a list or name/value pair" + ), } } + NestedMetaItem::Literal(_) => panic!( + "ruma_api! attribute meta item on responses must be: header" + ), } } - } - return ResponseField::Body(field); + false + }).collect(); + + match response_field_kind { + ResponseFieldKind::Body => ResponseField::Body(field), + ResponseFieldKind::Header => ResponseField::Header(field), + } }).collect(); Response { @@ -88,7 +117,7 @@ impl ToTokens for Response { for response in self.fields.iter() { match *response { ResponseField::Body(ref field) => field.to_tokens(&mut tokens), - ResponseField::Header(_, ref field) => field.to_tokens(&mut tokens), + ResponseField::Header(ref field) => field.to_tokens(&mut tokens), } tokens.append(","); @@ -123,7 +152,7 @@ impl ToTokens for Response { #[derive(Debug)] pub enum ResponseField { Body(Field), - Header(String, Field), + Header(Field), } impl ResponseField { @@ -134,3 +163,8 @@ impl ResponseField { } } } + +enum ResponseFieldKind { + Body, + Header, +} From 44164a7299e5f7302bad8bc424667dfb4ed27e93 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 17:36:57 -0700 Subject: [PATCH 024/107] Derive serde traits for the main request/response structs. --- src/request.rs | 2 +- src/response.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/request.rs b/src/request.rs index 90dba2fc..45bd1e38 100644 --- a/src/request.rs +++ b/src/request.rs @@ -99,7 +99,7 @@ impl ToTokens for Request { fn to_tokens(&self, mut tokens: &mut Tokens) { tokens.append(quote! { /// Data for a request to this API endpoint. - #[derive(Debug)] + #[derive(Debug, Serialize)] pub struct Request }); diff --git a/src/response.rs b/src/response.rs index e2068e26..1bb14741 100644 --- a/src/response.rs +++ b/src/response.rs @@ -105,7 +105,7 @@ impl ToTokens for Response { fn to_tokens(&self, mut tokens: &mut Tokens) { tokens.append(quote! { /// Data in the response from this API endpoint. - #[derive(Debug)] + #[derive(Debug, Deserialize)] pub struct Response }); From fc46b9a58b11d468d8b1ef51414d57d5e39f3332 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 17:56:55 -0700 Subject: [PATCH 025/107] Silence warnings for conditionally used traits and variables. --- src/api.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api.rs b/src/api.rs index 757d3205..c4692f66 100644 --- a/src/api.rs +++ b/src/api.rs @@ -88,8 +88,10 @@ impl ToTokens for Api { }; tokens.append(quote! { + #[allow(unused_imports)] use std::io::Write as _Write; + #[allow(unused_imports)] use ::futures::{Future as _Future, Stream as _Stream}; use ::ruma_api::Endpoint as _RumaApiEndpoint; @@ -123,6 +125,7 @@ impl ToTokens for Api { type Future = Box<_Future>; type Error = ::ruma_api::Error; + #[allow(unused_variables)] fn future_from(hyper_response: ::hyper::Response) -> Box<_Future> { #deserialize_response_body From 90c36542543f8bfa034e88f38a7097a17e4a8f2f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 19 May 2017 05:45:23 -0700 Subject: [PATCH 026/107] Allow a single field to be specified as the entire request body. --- src/request.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/request.rs b/src/request.rs index 45bd1e38..d431fa75 100644 --- a/src/request.rs +++ b/src/request.rs @@ -33,10 +33,29 @@ impl Request { fn body_fields(&self) -> RequestBodyFields { RequestBodyFields::new(&self.fields) } + + fn newtype_body_field(&self) -> Option { + for request_field in self.fields.iter() { + match *request_field { + RequestField::NewtypeBody(ref field) => { + let mut newtype_field = field.clone(); + + newtype_field.ident = None; + + return Some(newtype_field); + } + _ => continue, + } + } + + None + } } impl From> for Request { fn from(fields: Vec) -> Self { + let mut has_newtype_body = false; + let request_fields = fields.into_iter().map(|mut field| { let mut request_field_kind = RequestFieldKind::Body; @@ -55,7 +74,10 @@ impl From> for Request { NestedMetaItem::MetaItem(ref meta_item) => { match *meta_item { MetaItem::Word(ref ident) => { - if ident == "header" { + if ident == "body" { + has_newtype_body = true; + request_field_kind = RequestFieldKind::NewtypeBody; + } else if ident == "header" { request_field_kind = RequestFieldKind::Header; } else if ident == "path" { request_field_kind = RequestFieldKind::Path; @@ -63,7 +85,7 @@ impl From> for Request { request_field_kind = RequestFieldKind::Query; } else { panic!( - "ruma_api! attribute meta item on requests must be: header, path, or query" + "ruma_api! attribute meta item on requests must be: body, header, path, or query" ); } } @@ -73,7 +95,7 @@ impl From> for Request { } } NestedMetaItem::Literal(_) => panic!( - "ruma_api! attribute meta item on requests must be: header, path, or query" + "ruma_api! attribute meta item on requests must be: body, header, path, or query" ), } } @@ -82,8 +104,15 @@ impl From> for Request { }).collect(); match request_field_kind { - RequestFieldKind::Body => RequestField::Body(field), + RequestFieldKind::Body => { + if has_newtype_body { + panic!("ruma_api! requests cannot have both normal body fields and a newtype body field"); + } else { + return RequestField::Body(field); + } + } RequestFieldKind::Header => RequestField::Header(field), + RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), } @@ -112,6 +141,7 @@ impl ToTokens for Request { match *request_field { RequestField::Body(ref field) => field.to_tokens(&mut tokens), RequestField::Header(ref field) => field.to_tokens(&mut tokens), + RequestField::NewtypeBody(ref field) => field.to_tokens(&mut tokens), RequestField::Path(ref field) => field.to_tokens(&mut tokens), RequestField::Query(ref field) => field.to_tokens(&mut tokens), } @@ -122,7 +152,19 @@ impl ToTokens for Request { tokens.append("}"); } - if self.has_body_fields() { + if let Some(newtype_body_field) = self.newtype_body_field() { + tokens.append(quote! { + /// Data in the request body. + #[derive(Debug, Serialize)] + struct RequestBody + }); + + tokens.append("("); + + newtype_body_field.to_tokens(&mut tokens); + + tokens.append(");"); + } else if self.has_body_fields() { tokens.append(quote! { /// Data in the request body. #[derive(Debug, Serialize)] @@ -151,6 +193,7 @@ impl ToTokens for Request { pub enum RequestField { Body(Field), Header(Field), + NewtypeBody(Field), Path(Field), Query(Field), } @@ -167,6 +210,7 @@ impl RequestField { enum RequestFieldKind { Body, Header, + NewtypeBody, Path, Query, } From 58fab938b00d01aeb5e3e8c31731b4e479d5553d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 19 May 2017 05:58:04 -0700 Subject: [PATCH 027/107] Add newtype body fields to the hyper request. --- src/api.rs | 10 +++++++++- src/request.rs | 37 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/api.rs b/src/api.rs index c4692f66..376e7b50 100644 --- a/src/api.rs +++ b/src/api.rs @@ -32,7 +32,15 @@ impl ToTokens for Api { tokens }; - let add_body_to_request = if self.request.has_body_fields() { + let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { + let field_name = field.ident.as_ref().expect("expected body field to have a name"); + + quote! { + let request_body = RequestBody(request.#field_name); + + hyper_request.set_body(::serde_json::to_vec(&request_body)?); + } + } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); quote! { diff --git a/src/request.rs b/src/request.rs index d431fa75..43f7fcd6 100644 --- a/src/request.rs +++ b/src/request.rs @@ -11,6 +11,20 @@ impl Request { self.fields.iter().any(|field| field.is_body()) } + pub fn newtype_body_field(&self) -> Option<&Field> { + for request_field in self.fields.iter() { + match *request_field { + RequestField::NewtypeBody(ref field) => { + + return Some(field); + } + _ => continue, + } + } + + None + } + pub fn request_body_init_fields(&self) -> Tokens { let mut tokens = Tokens::new(); @@ -33,23 +47,6 @@ impl Request { fn body_fields(&self) -> RequestBodyFields { RequestBodyFields::new(&self.fields) } - - fn newtype_body_field(&self) -> Option { - for request_field in self.fields.iter() { - match *request_field { - RequestField::NewtypeBody(ref field) => { - let mut newtype_field = field.clone(); - - newtype_field.ident = None; - - return Some(newtype_field); - } - _ => continue, - } - } - - None - } } impl From> for Request { @@ -153,6 +150,10 @@ impl ToTokens for Request { } if let Some(newtype_body_field) = self.newtype_body_field() { + let mut field = newtype_body_field.clone(); + + field.ident = None; + tokens.append(quote! { /// Data in the request body. #[derive(Debug, Serialize)] @@ -161,7 +162,7 @@ impl ToTokens for Request { tokens.append("("); - newtype_body_field.to_tokens(&mut tokens); + field.to_tokens(&mut tokens); tokens.append(");"); } else if self.has_body_fields() { From 35362e78a6cfef346ad74c4b38c6e0611402231b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 21 May 2017 01:52:16 -0700 Subject: [PATCH 028/107] Add newtype body field support for responses. --- src/api.rs | 23 ++++++++++++++++++++++- src/response.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/api.rs b/src/api.rs index 376e7b50..277818c7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -54,7 +54,28 @@ impl ToTokens for Api { Tokens::new() }; - let deserialize_response_body = if self.response.has_body_fields() { + let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { + let field_type = &field.ty; + let mut tokens = Tokens::new(); + + tokens.append(quote! { + let future_response = hyper_response.body() + .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { + bytes.write_all(&chunk)?; + + Ok(bytes) + }) + .map_err(::ruma_api::Error::from) + .and_then(|bytes| { + ::serde_json::from_slice::<#field_type>(bytes.as_slice()) + .map_err(::ruma_api::Error::from) + }) + }); + + tokens.append(".and_then(|response_body| {"); + + tokens + } else if self.response.has_body_fields() { let mut tokens = Tokens::new(); tokens.append(quote! { diff --git a/src/response.rs b/src/response.rs index 1bb14741..d3c7f434 100644 --- a/src/response.rs +++ b/src/response.rs @@ -38,15 +38,40 @@ impl Response { .expect("missing expected request header: {}", #field_name), }); } + ResponseField::NewtypeBody(ref field) => { + let field_name = field.ident.as_ref() + .expect("expected body field to have a name"); + + tokens.append(quote! { + #field_name: response_body, + }); + } } } tokens } + + pub fn newtype_body_field(&self) -> Option<&Field> { + for response_field in self.fields.iter() { + match *response_field { + ResponseField::NewtypeBody(ref field) => { + + return Some(field); + } + _ => continue, + } + } + + None + } + } impl From> for Response { fn from(fields: Vec) -> Self { + let mut has_newtype_body = false; + let response_fields = fields.into_iter().map(|mut field| { let mut response_field_kind = ResponseFieldKind::Body; @@ -67,7 +92,10 @@ impl From> for Response { NestedMetaItem::MetaItem(ref meta_item) => { match *meta_item { MetaItem::Word(ref ident) => { - if ident == "header" { + if ident == "body" { + has_newtype_body = true; + response_field_kind = ResponseFieldKind::NewtypeBody; + } else if ident == "header" { response_field_kind = ResponseFieldKind::Header; } else { panic!( @@ -90,8 +118,15 @@ impl From> for Response { }).collect(); match response_field_kind { - ResponseFieldKind::Body => ResponseField::Body(field), + ResponseFieldKind::Body => { + if has_newtype_body { + panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); + } else { + return ResponseField::Body(field); + } + } ResponseFieldKind::Header => ResponseField::Header(field), + ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } }).collect(); @@ -118,6 +153,7 @@ impl ToTokens for Response { match *response { ResponseField::Body(ref field) => field.to_tokens(&mut tokens), ResponseField::Header(ref field) => field.to_tokens(&mut tokens), + ResponseField::NewtypeBody(ref field) => field.to_tokens(&mut tokens), } tokens.append(","); @@ -153,6 +189,7 @@ impl ToTokens for Response { pub enum ResponseField { Body(Field), Header(Field), + NewtypeBody(Field), } impl ResponseField { @@ -167,4 +204,5 @@ impl ResponseField { enum ResponseFieldKind { Body, Header, + NewtypeBody, } From fb2082237b76d447f28d2d44417e75814c99e45e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 14 Jun 2017 10:21:15 +0200 Subject: [PATCH 029/107] Bump dependency versions --- Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 756ede3f..60a81c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,16 +4,13 @@ name = "ruma-api-macros" version = "0.1.0" [dependencies] +hyper = "0.11" quote = "0.3.15" synom = "0.11.3" -[dependencies.hyper] -git = "https://github.com/hyperium/hyper" -rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" - [dependencies.ruma-api] git = "https://github.com/ruma/ruma-api" -rev = "3635fe51ac31b9ff899c70a0d1218caa8cf6a8dc" +rev = "211cf5e3531cd1862136129d5f73caaac7b4eb43" [dependencies.syn] features = ["full"] From e40496b460d8f114cd6a0aec21960b5e736516f8 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 29 Jun 2017 04:19:45 -0700 Subject: [PATCH 030/107] Update dependencies and reorganize modules. --- Cargo.toml | 6 +++--- src/{ => api}/metadata.rs | 0 src/{api.rs => api/mod.rs} | 10 +++++++--- src/{ => api}/request.rs | 0 src/{ => api}/response.rs | 0 src/lib.rs | 3 --- 6 files changed, 10 insertions(+), 9 deletions(-) rename src/{ => api}/metadata.rs (100%) rename src/{api.rs => api/mod.rs} (98%) rename src/{ => api}/request.rs (100%) rename src/{ => api}/response.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 60a81c83..8b0239a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ features = ["full"] version = "0.11.11" [dev-dependencies] -futures = "0.1.13" -serde = "1.0.4" -serde_derive = "1.0.4" +futures = "0.1.14" +serde = "1.0.8" +serde_derive = "1.0.8" serde_json = "1.0.2" [lib] diff --git a/src/metadata.rs b/src/api/metadata.rs similarity index 100% rename from src/metadata.rs rename to src/api/metadata.rs diff --git a/src/api.rs b/src/api/mod.rs similarity index 98% rename from src/api.rs rename to src/api/mod.rs index 277818c7..64ee55ff 100644 --- a/src/api.rs +++ b/src/api/mod.rs @@ -1,9 +1,13 @@ use quote::{ToTokens, Tokens}; -use metadata::Metadata; +mod metadata; +mod request; +mod response; + use parse::Entry; -use request::Request; -use response::Response; +use self::metadata::Metadata; +use self::request::Request; +use self::response::Response; #[derive(Debug)] pub struct Api { diff --git a/src/request.rs b/src/api/request.rs similarity index 100% rename from src/request.rs rename to src/api/request.rs diff --git a/src/response.rs b/src/api/response.rs similarity index 100% rename from src/response.rs rename to src/api/response.rs diff --git a/src/lib.rs b/src/lib.rs index 80e3805f..f58ae1f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,10 +18,7 @@ use api::Api; use parse::parse_entries; mod api; -mod metadata; mod parse; -mod request; -mod response; /// Generates a `ruma-api` endpoint. #[proc_macro] From 170e00a48791f8a3bf588b6d46423af1f066362d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 29 Jun 2017 07:29:24 +1000 Subject: [PATCH 031/107] Implement setting of query parameters --- Cargo.toml | 4 +- src/api/mod.rs | 22 +++++++++- src/api/request.rs | 88 ++++++++++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 2 + 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b0239a7..2e823d57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ name = "ruma-api-macros" version = "0.1.0" [dependencies] -hyper = "0.11" quote = "0.3.15" synom = "0.11.3" @@ -18,9 +17,12 @@ version = "0.11.11" [dev-dependencies] futures = "0.1.14" +hyper = "0.11" serde = "1.0.8" serde_derive = "1.0.8" serde_json = "1.0.2" +serde_urlencoded = "0.5.1" +url = "1.5.1" [lib] doctest = false diff --git a/src/api/mod.rs b/src/api/mod.rs index 64ee55ff..f206a51e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -36,6 +36,20 @@ impl ToTokens for Api { tokens }; + let set_request_query = if self.request.has_query_fields() { + let request_query_init_fields = self.request.request_query_init_fields(); + + quote! { + let request_query = RequestQuery { + #request_query_init_fields + }; + + url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); + } + } else { + Tokens::new() + }; + let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { let field_name = field.ident.as_ref().expect("expected body field to have a name"); @@ -141,9 +155,15 @@ impl ToTokens for Api { fn try_from(request: Request) -> Result { let metadata = Endpoint::METADATA; + // The homeserver url has to be overwritten in the calling code. + let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); + url.set_path(metadata.path); + #set_request_query + let mut hyper_request = ::hyper::Request::new( metadata.method, - metadata.path.parse()?, + // Every valid URL is a valid URI + url.into_string().parse().unwrap(), ); #add_body_to_request diff --git a/src/api/request.rs b/src/api/request.rs index 43f7fcd6..6bce3eb7 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -11,6 +11,10 @@ impl Request { self.fields.iter().any(|field| field.is_body()) } + pub fn has_query_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_query()) + } + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -44,9 +48,32 @@ impl Request { tokens } + pub fn request_query_init_fields(&self) -> Tokens { + let mut tokens = Tokens::new(); + + for query_field in self.query_fields() { + let field = match *query_field { + RequestField::Query(ref field) => field, + _ => panic!("expected query field"), + }; + + let field_name = field.ident.as_ref().expect("expected query field to have a name"); + + tokens.append(quote! { + #field_name: request.#field_name, + }); + } + + tokens + } + fn body_fields(&self) -> RequestBodyFields { RequestBodyFields::new(&self.fields) } + + fn query_fields(&self) -> RequestQueryFields { + RequestQueryFields::new(&self.fields) + } } impl From> for Request { @@ -187,6 +214,29 @@ impl ToTokens for Request { tokens.append("}"); } + + if self.has_query_fields() { + tokens.append(quote! { + /// Data in the request url's query parameters + #[derive(Debug, Serialize)] + struct RequestQuery + }); + + tokens.append("{"); + + for request_field in self.fields.iter() { + match *request_field { + RequestField::Query(ref field) => { + field.to_tokens(&mut tokens); + + tokens.append(","); + } + _ => {} + } + } + + tokens.append("}"); + } } } @@ -206,6 +256,13 @@ impl RequestField { _ => false, } } + + fn is_query(&self) -> bool { + match *self { + RequestField::Query(_) => true, + _ => false, + } + } } enum RequestFieldKind { @@ -246,3 +303,34 @@ impl<'a> Iterator for RequestBodyFields<'a> { None } } + +#[derive(Debug)] +pub struct RequestQueryFields<'a> { + fields: &'a [RequestField], + index: usize, +} + +impl<'a> RequestQueryFields<'a> { + pub fn new(fields: &'a [RequestField]) -> Self { + RequestQueryFields { + fields, + index: 0, + } + } +} + +impl<'a> Iterator for RequestQueryFields<'a> { + type Item = &'a RequestField; + + fn next(&mut self) -> Option<&'a RequestField> { + while let Some(value) = self.fields.get(self.index) { + self.index += 1; + + if value.is_query() { + return Some(value); + } + } + + None + } +} diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 9b477386..fc7c4913 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -7,6 +7,8 @@ extern crate ruma_api_macros; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; +extern crate serde_urlencoded; +extern crate url; pub mod get_supported_versions { use ruma_api_macros::ruma_api; From 5180297d81e77456b554980712739cf2e1ce1e7c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 29 Jun 2017 19:22:13 +1000 Subject: [PATCH 032/107] Refactor request module to reduce code duplication --- src/api/mod.rs | 4 +- src/api/request.rs | 180 ++++++++++++++------------------------------- 2 files changed, 60 insertions(+), 124 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index f206a51e..caa2a7eb 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -155,7 +155,9 @@ impl ToTokens for Api { fn try_from(request: Request) -> Result { let metadata = Endpoint::METADATA; - // The homeserver url has to be overwritten in the calling code. + // Use dummy homeserver url which has to be overwritten in + // the calling code. Previously (with hyper::Uri) this was + // not required, but Url::parse only accepts absolute urls. let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); url.set_path(metadata.path); #set_request_query diff --git a/src/api/request.rs b/src/api/request.rs index 6bce3eb7..b47f6258 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -19,7 +19,6 @@ impl Request { for request_field in self.fields.iter() { match *request_field { RequestField::NewtypeBody(ref field) => { - return Some(field); } _ => continue, @@ -30,14 +29,17 @@ impl Request { } pub fn request_body_init_fields(&self) -> Tokens { + self.struct_init_fields(RequestFieldKind::Body) + } + + pub fn request_query_init_fields(&self) -> Tokens { + self.struct_init_fields(RequestFieldKind::Query) + } + + fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> Tokens { let mut tokens = Tokens::new(); - for request_field in self.body_fields() { - let field = match *request_field { - RequestField::Body(ref field) => field, - _ => panic!("expected body field"), - }; - + for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { let field_name = field.ident.as_ref().expect("expected body field to have a name"); tokens.append(quote! { @@ -47,33 +49,6 @@ impl Request { tokens } - - pub fn request_query_init_fields(&self) -> Tokens { - let mut tokens = Tokens::new(); - - for query_field in self.query_fields() { - let field = match *query_field { - RequestField::Query(ref field) => field, - _ => panic!("expected query field"), - }; - - let field_name = field.ident.as_ref().expect("expected query field to have a name"); - - tokens.append(quote! { - #field_name: request.#field_name, - }); - } - - tokens - } - - fn body_fields(&self) -> RequestBodyFields { - RequestBodyFields::new(&self.fields) - } - - fn query_fields(&self) -> RequestQueryFields { - RequestQueryFields::new(&self.fields) - } } impl From> for Request { @@ -127,19 +102,14 @@ impl From> for Request { false }).collect(); - match request_field_kind { - RequestFieldKind::Body => { - if has_newtype_body { - panic!("ruma_api! requests cannot have both normal body fields and a newtype body field"); - } else { - return RequestField::Body(field); - } - } - RequestFieldKind::Header => RequestField::Header(field), - RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), - RequestFieldKind::Path => RequestField::Path(field), - RequestFieldKind::Query => RequestField::Query(field), + if request_field_kind == RequestFieldKind::Body { + assert!( + !has_newtype_body, + "ruma_api! requests cannot have both normal body fields and a newtype body field" + ); } + + RequestField::new(request_field_kind, field) }).collect(); Request { @@ -162,14 +132,7 @@ impl ToTokens for Request { tokens.append("{"); for request_field in self.fields.iter() { - match *request_field { - RequestField::Body(ref field) => field.to_tokens(&mut tokens), - RequestField::Header(ref field) => field.to_tokens(&mut tokens), - RequestField::NewtypeBody(ref field) => field.to_tokens(&mut tokens), - RequestField::Path(ref field) => field.to_tokens(&mut tokens), - RequestField::Query(ref field) => field.to_tokens(&mut tokens), - } - + request_field.field().to_tokens(&mut tokens); tokens.append(","); } @@ -250,21 +213,54 @@ pub enum RequestField { } impl RequestField { - fn is_body(&self) -> bool { - match *self { - RequestField::Body(_) => true, - _ => false, + fn new(kind: RequestFieldKind, field: Field) -> RequestField { + match kind { + RequestFieldKind::Body => RequestField::Body(field), + RequestFieldKind::Header => RequestField::Header(field), + RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), + RequestFieldKind::Path => RequestField::Path(field), + RequestFieldKind::Query => RequestField::Query(field), } } - fn is_query(&self) -> bool { + fn kind(&self) -> RequestFieldKind { match *self { - RequestField::Query(_) => true, - _ => false, + RequestField::Body(_) => RequestFieldKind::Body, + RequestField::Header(_) => RequestFieldKind::Header, + RequestField::NewtypeBody(_) => RequestFieldKind::NewtypeBody, + RequestField::Path(_) => RequestFieldKind::Path, + RequestField::Query(_) => RequestFieldKind::Query, + } + } + + fn is_body(&self) -> bool { + self.kind() == RequestFieldKind::Body + } + + fn is_query(&self) -> bool { + self.kind() == RequestFieldKind::Query + } + + fn field(&self) -> &Field { + match *self { + RequestField::Body(ref field) => field, + RequestField::Header(ref field) => field, + RequestField::NewtypeBody(ref field) => field, + RequestField::Path(ref field) => field, + RequestField::Query(ref field) => field, + } + } + + fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { + if self.kind() == kind { + Some(self.field()) + } else { + None } } } +#[derive(Clone, Copy, PartialEq, Eq)] enum RequestFieldKind { Body, Header, @@ -272,65 +268,3 @@ enum RequestFieldKind { Path, Query, } - -#[derive(Debug)] -pub struct RequestBodyFields<'a> { - fields: &'a [RequestField], - index: usize, -} - -impl<'a> RequestBodyFields<'a> { - pub fn new(fields: &'a [RequestField]) -> Self { - RequestBodyFields { - fields, - index: 0, - } - } -} - -impl<'a> Iterator for RequestBodyFields<'a> { - type Item = &'a RequestField; - - fn next(&mut self) -> Option<&'a RequestField> { - while let Some(value) = self.fields.get(self.index) { - self.index += 1; - - if value.is_body() { - return Some(value); - } - } - - None - } -} - -#[derive(Debug)] -pub struct RequestQueryFields<'a> { - fields: &'a [RequestField], - index: usize, -} - -impl<'a> RequestQueryFields<'a> { - pub fn new(fields: &'a [RequestField]) -> Self { - RequestQueryFields { - fields, - index: 0, - } - } -} - -impl<'a> Iterator for RequestQueryFields<'a> { - type Item = &'a RequestField; - - fn next(&mut self) -> Option<&'a RequestField> { - while let Some(value) = self.fields.get(self.index) { - self.index += 1; - - if value.is_query() { - return Some(value); - } - } - - None - } -} From 62971e63cd437054407e33b20891c61df720dcea Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 30 Jun 2017 01:29:23 +1000 Subject: [PATCH 033/107] Implement substitution of variables in endpoint paths --- Cargo.toml | 1 - src/api/mod.rs | 62 +++++++++++++++++++++++++++++++++++++++++++--- src/api/request.rs | 41 +++++++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e823d57..54f122fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ serde = "1.0.8" serde_derive = "1.0.8" serde_json = "1.0.2" serde_urlencoded = "0.5.1" -url = "1.5.1" [lib] doctest = false diff --git a/src/api/mod.rs b/src/api/mod.rs index caa2a7eb..a85f7c39 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -36,6 +36,61 @@ impl ToTokens for Api { tokens }; + let set_request_path = if self.request.has_path_fields() { + let path_str_quoted = path.as_str(); + assert!( + path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), + "path needs to be a string literal" + ); + + let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + + assert!(path_str.starts_with('/'), "path needs to start with '/'"); + assert!( + path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), + "number of declared path parameters needs to match amount of placeholders in path" + ); + + let request_path_init_fields = self.request.request_path_init_fields(); + + let mut tokens = quote! { + let request_path = RequestPath { + #request_path_init_fields + }; + + // This `unwrap()` can only fail when the url is a + // cannot-be-base url like `mailto:` or `data:`, which is not + // the case for our placeholder url. + let mut path_segments = url.path_segments_mut().unwrap(); + }; + + for segment in path_str[1..].split('/') { + tokens.append(quote! { + path_segments.push + }); + + tokens.append("("); + + if segment.starts_with(':') { + tokens.append("&request_path."); + tokens.append(&segment[1..]); + tokens.append(".to_string()"); + } else { + tokens.append("\""); + tokens.append(segment); + tokens.append("\""); + } + + tokens.append(");"); + } + + tokens + } else { + quote! { + url.set_path(metadata.path); + } + }; + let set_request_query = if self.request.has_query_fields() { let request_query_init_fields = self.request.request_query_init_fields(); @@ -159,8 +214,9 @@ impl ToTokens for Api { // the calling code. Previously (with hyper::Uri) this was // not required, but Url::parse only accepts absolute urls. let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); - url.set_path(metadata.path); - #set_request_query + + { #set_request_path } + { #set_request_query } let mut hyper_request = ::hyper::Request::new( metadata.method, @@ -168,7 +224,7 @@ impl ToTokens for Api { url.into_string().parse().unwrap(), ); - #add_body_to_request + { #add_body_to_request } Ok(hyper_request) } diff --git a/src/api/request.rs b/src/api/request.rs index b47f6258..d1794509 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -11,10 +11,18 @@ impl Request { self.fields.iter().any(|field| field.is_body()) } + pub fn has_path_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_path()) + } + pub fn has_query_fields(&self) -> bool { self.fields.iter().any(|field| field.is_query()) } + pub fn path_field_count(&self) -> usize { + self.fields.iter().filter(|field| field.is_path()).count() + } + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -32,6 +40,10 @@ impl Request { self.struct_init_fields(RequestFieldKind::Body) } + pub fn request_path_init_fields(&self) -> Tokens { + self.struct_init_fields(RequestFieldKind::Path) + } + pub fn request_query_init_fields(&self) -> Tokens { self.struct_init_fields(RequestFieldKind::Query) } @@ -178,9 +190,32 @@ impl ToTokens for Request { tokens.append("}"); } + if self.has_path_fields() { + tokens.append(quote! { + /// Data in the request path. + #[derive(Debug)] + struct RequestPath + }); + + tokens.append("{"); + + for request_field in self.fields.iter() { + match *request_field { + RequestField::Path(ref field) => { + field.to_tokens(&mut tokens); + + tokens.append(","); + } + _ => {} + } + } + + tokens.append("}"); + } + if self.has_query_fields() { tokens.append(quote! { - /// Data in the request url's query parameters + /// Data in the request url's query parameters. #[derive(Debug, Serialize)] struct RequestQuery }); @@ -237,6 +272,10 @@ impl RequestField { self.kind() == RequestFieldKind::Body } + fn is_path(&self) -> bool { + self.kind() == RequestFieldKind::Path + } + fn is_query(&self) -> bool { self.kind() == RequestFieldKind::Query } From 6d82e06600126ff058ddde22a20e29fc74b436a0 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 30 Jun 2017 18:09:34 -0700 Subject: [PATCH 034/107] Derive Serialize for RequestPath so the serde attributes get stripped. --- src/api/request.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index d1794509..92e55e54 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -193,7 +193,7 @@ impl ToTokens for Request { if self.has_path_fields() { tokens.append(quote! { /// Data in the request path. - #[derive(Debug)] + #[derive(Debug, Serialize)] struct RequestPath }); @@ -215,7 +215,7 @@ impl ToTokens for Request { if self.has_query_fields() { tokens.append(quote! { - /// Data in the request url's query parameters. + /// Data in the request's query string. #[derive(Debug, Serialize)] struct RequestQuery }); From c0c4b0949aaaf30ea3bc93a440b335435481bb0d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 30 Jun 2017 18:33:43 -0700 Subject: [PATCH 035/107] Add missing dev dependency for url. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 54f122fb..38874d7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ synom = "0.11.3" [dependencies.ruma-api] git = "https://github.com/ruma/ruma-api" -rev = "211cf5e3531cd1862136129d5f73caaac7b4eb43" +rev = "4893be93f86543f9a9e3c5d7205afba769b84aa6" [dependencies.syn] features = ["full"] @@ -22,6 +22,7 @@ serde = "1.0.8" serde_derive = "1.0.8" serde_json = "1.0.2" serde_urlencoded = "0.5.1" +url = "1.5.1" [lib] doctest = false From dce17dbb64d350d53bede3f74bcbd8c9e1e98866 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 1 Jul 2017 11:29:23 -0700 Subject: [PATCH 036/107] Add support for header fields in responses. --- src/api/mod.rs | 16 +++++++++++++--- src/api/response.rs | 19 +++++++++++++++---- src/lib.rs | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index a85f7c39..53cacbe3 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -145,7 +145,7 @@ impl ToTokens for Api { }) }); - tokens.append(".and_then(|response_body| {"); + tokens.append(".and_then(move |response_body| {"); tokens } else if self.response.has_body_fields() { @@ -165,7 +165,7 @@ impl ToTokens for Api { }) }); - tokens.append(".and_then(|response_body| {"); + tokens.append(".and_then(move |response_body| {"); tokens } else { @@ -175,7 +175,7 @@ impl ToTokens for Api { let future_response = ::futures::future::ok(()) }); - tokens.append(".and_then(|_| {"); + tokens.append(".and_then(move |_| {"); tokens }; @@ -183,6 +183,14 @@ impl ToTokens for Api { let mut closure_end = Tokens::new(); closure_end.append("});"); + let extract_headers = if self.response.has_header_fields() { + quote! { + let mut headers = hyper_response.headers().clone(); + } + } else { + Tokens::new() + }; + let response_init_fields = if self.response.has_fields() { self.response.init_fields() } else { @@ -239,6 +247,8 @@ impl ToTokens for Api { #[allow(unused_variables)] fn future_from(hyper_response: ::hyper::Response) -> Box<_Future> { + #extract_headers + #deserialize_response_body let response = Response { diff --git a/src/api/response.rs b/src/api/response.rs index d3c7f434..591341b0 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -15,6 +15,10 @@ impl Response { self.fields.len() != 0 } + pub fn has_header_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_header()) + } + pub fn init_fields(&self) -> Tokens { let mut tokens = Tokens::new(); @@ -31,11 +35,11 @@ impl Response { ResponseField::Header(ref field) => { let field_name = field.ident.as_ref() .expect("expected body field to have a name"); + let field_type = &field.ty; tokens.append(quote! { - #field_name: hyper_response.headers() - .get_raw(#field_name) - .expect("missing expected request header: {}", #field_name), + #field_name: headers.remove::<#field_type>() + .expect("missing expected request header"), }); } ResponseField::NewtypeBody(ref field) => { @@ -140,7 +144,7 @@ impl ToTokens for Response { fn to_tokens(&self, mut tokens: &mut Tokens) { tokens.append(quote! { /// Data in the response from this API endpoint. - #[derive(Debug, Deserialize)] + #[derive(Debug)] pub struct Response }); @@ -199,6 +203,13 @@ impl ResponseField { _ => false, } } + + fn is_header(&self) -> bool { + match *self { + ResponseField::Header(_) => true, + _ => false, + } + } } enum ResponseFieldKind { diff --git a/src/lib.rs b/src/lib.rs index f58ae1f6..74738261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_debug_implementations)] #![feature(proc_macro)] -#![recursion_limit="128"] +#![recursion_limit="256"] extern crate proc_macro; #[macro_use] extern crate quote; From 84562c426044eef6b1c2f0e964e85ff9d100aa27 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 1 Jul 2017 15:26:03 -0700 Subject: [PATCH 037/107] Strip serde attributes on aggregate Request and Response types. --- src/api/mod.rs | 22 ++++++++++++++++++++++ src/api/request.rs | 7 +++++-- src/api/response.rs | 18 ++++++++++++------ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 53cacbe3..8ed40e76 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,5 @@ use quote::{ToTokens, Tokens}; +use syn::{Field, MetaItem}; mod metadata; mod request; @@ -9,6 +10,27 @@ use self::metadata::Metadata; use self::request::Request; use self::response::Response; +pub fn strip_serde_attrs(field: &Field) -> Field { + let mut field = field.clone(); + + field.attrs = field.attrs.into_iter().filter(|attr| { + let (attr_ident, _) = match attr.value { + MetaItem::List(ref attr_ident, _) => { + (attr_ident, ()) + } + _ => return true, + }; + + if attr_ident != "serde" { + return true; + } + + false + }).collect(); + + field +} + #[derive(Debug)] pub struct Api { metadata: Metadata, diff --git a/src/api/request.rs b/src/api/request.rs index 92e55e54..be9729b4 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,8 @@ use quote::{ToTokens, Tokens}; use syn::{Field, MetaItem, NestedMetaItem}; +use api::strip_serde_attrs; + #[derive(Debug)] pub struct Request { fields: Vec, @@ -134,7 +136,7 @@ impl ToTokens for Request { fn to_tokens(&self, mut tokens: &mut Tokens) { tokens.append(quote! { /// Data for a request to this API endpoint. - #[derive(Debug, Serialize)] + #[derive(Debug)] pub struct Request }); @@ -144,7 +146,8 @@ impl ToTokens for Request { tokens.append("{"); for request_field in self.fields.iter() { - request_field.field().to_tokens(&mut tokens); + strip_serde_attrs(request_field.field()).to_tokens(&mut tokens); + tokens.append(","); } diff --git a/src/api/response.rs b/src/api/response.rs index 591341b0..5a203e13 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,6 +1,8 @@ use quote::{ToTokens, Tokens}; use syn::{Field, MetaItem, NestedMetaItem}; +use api::strip_serde_attrs; + #[derive(Debug)] pub struct Response { fields: Vec, @@ -153,12 +155,8 @@ impl ToTokens for Response { } else { tokens.append("{"); - for response in self.fields.iter() { - match *response { - ResponseField::Body(ref field) => field.to_tokens(&mut tokens), - ResponseField::Header(ref field) => field.to_tokens(&mut tokens), - ResponseField::NewtypeBody(ref field) => field.to_tokens(&mut tokens), - } + for response_field in self.fields.iter() { + strip_serde_attrs(response_field.field()).to_tokens(&mut tokens); tokens.append(","); } @@ -197,6 +195,14 @@ pub enum ResponseField { } impl ResponseField { + fn field(&self) -> &Field { + match *self { + ResponseField::Body(ref field) => field, + ResponseField::Header(ref field) => field, + ResponseField::NewtypeBody(ref field) => field, + } + } + fn is_body(&self) -> bool { match *self { ResponseField::Body(_) => true, From b292a3e7764632a6763438da9ba1e2b86496c3f3 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 1 Jul 2017 16:24:44 -0700 Subject: [PATCH 038/107] Improve test coverage, fix a misplaced comma bug, implement missing newtype body support for responses. --- src/api/response.rs | 26 ++++++++++++++++++++++---- src/lib.rs | 3 ++- tests/ruma_api_macros.rs | 39 ++++++++++++++++++++++++++++++--------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/api/response.rs b/src/api/response.rs index 5a203e13..abe0905e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -164,7 +164,23 @@ impl ToTokens for Response { tokens.append("}"); } - if self.has_body_fields() { + if let Some(newtype_body_field) = self.newtype_body_field() { + let mut field = newtype_body_field.clone(); + + field.ident = None; + + tokens.append(quote! { + /// Data in the response body. + #[derive(Debug, Deserialize)] + struct ResponseBody + }); + + tokens.append("("); + + field.to_tokens(&mut tokens); + + tokens.append(");"); + } else if self.has_body_fields() { tokens.append(quote! { /// Data in the response body. #[derive(Debug, Deserialize)] @@ -175,11 +191,13 @@ impl ToTokens for Response { for response_field in self.fields.iter() { match *response_field { - ResponseField::Body(ref field) => field.to_tokens(&mut tokens), + ResponseField::Body(ref field) => { + field.to_tokens(&mut tokens); + + tokens.append(","); + } _ => {} } - - tokens.append(","); } tokens.append("}"); diff --git a/src/lib.rs b/src/lib.rs index 74738261..9ce90231 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -//! Crate `ruma-api-macros` provides a procedural macro for easily generating `ruma-api` endpoints. +//! Crate `ruma-api-macros` provides a procedural macro for easily generating +//! [ruma-api](https://github.com/ruma/ruma-api)-compatible endpoints. #![deny(missing_debug_implementations)] #![feature(proc_macro)] diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index fc7c4913..cc93423e 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -10,24 +10,45 @@ extern crate serde_json; extern crate serde_urlencoded; extern crate url; -pub mod get_supported_versions { +pub mod some_endpoint { + use hyper::header::ContentType; use ruma_api_macros::ruma_api; ruma_api! { metadata { - description: "Get the versions of the client-server API supported by this homeserver.", - method: Method::Get, - name: "api_versions", - path: "/_matrix/client/versions", + description: "Does something.", + method: Method::Get, // A `hyper::Method` value. No need to import the name. + name: "some_endpoint", + path: "/_matrix/some/endpoint/:baz", rate_limited: false, - requires_authentication: true, + requires_authentication: false, } - request {} + request { + // With no attribute on the field, it will be put into the body of the request. + pub foo: String, + + // This value will be put into the "Content-Type" HTTP header. + #[ruma_api(header)] + pub content_type: ContentType, + + // This value will be put into the query string of the request's URL. + #[ruma_api(query)] + pub bar: String, + + // This value will be inserted into the request's URL in place of the + // ":baz" path component. + #[ruma_api(path)] + pub baz: String, + } response { - /// A list of Matrix client API protocol versions supported by the homeserver. - pub versions: Vec, + // This value will be extracted from the "Content-Type" HTTP header. + #[ruma_api(header)] + pub content_type: ContentType, + + // With no attribute on the field, it will be extracted from the body of the response. + pub value: String, } } } From 0f32ca01dbf75aeab91937e421dfaa2d1f67351a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 1 Jul 2017 17:22:40 -0700 Subject: [PATCH 039/107] Add complete documentation. --- .travis.yml | 9 +++ Cargo.toml | 1 - LICENSE | 19 ++++++ README.md | 74 ++++++++++++++++++++++ src/lib.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4b55557f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: "rust" +notifications: + email: false + irc: + channels: + - secure: "FiHwNDkLqlzn+fZnn42uZ+GWm59S9OJreUIz9r7+nXrxUBeBcthQlqamJUiuYryVohzqLydBVv6xmT5wgS/LxRnj4f363eBpKejuSktglnf2rl8JjuSXZVgrPMDmrfgkBdC+aMCPzdw2fIHSWmvQMr/t9kGW9cHl0VlLxPAhnAsry+E1Kxrrz4IuOJmyb43VqPf/GO6VCDzTpHiKHKe5Rp7i2IkbGus2GiSD/UMrgUTWmMOFoejl7fWX7SH9kvSrN/SCYldVOYA4nazeZfaHv7mCX6G8U3GGXTHwjAVAluXyYgUCYpsYKC5KGkUJFcLhjaBu5qpmlI0EZd/rsgscOBzqfJ0D/WkahWiKtlQEKZ7UEAhA3SaAhcrSh2kSQFf2GW1T8kfzqlnBtjpqSvCFuOpY5XQcSYEEX7qxT1aiK2UBi9iAKgMnG1SDEfeFERekw0KJPKbwJDMV7NhCg9kYVBHG1hxvFeYqMmnFrjLlRDQQrbDHrP9Avdtg0FScolsFVmT+uatBuRXDcqunssolfnWguyrQ0Z9KGauv0iqkwFwO7jQSA9f87wgsuzqlzstHRxoGGlPtGt4J/+MhyA3lOEXwBa5eotjILI7iykK+ykJ33cOTGcqyXbkWoYRZ6+fS2guI+f2CxxsYWUOK2UgMyYKEwtraC3duVIGtQR+zuvc=" + use_notice: true +rust: + - "nightly" diff --git a/Cargo.toml b/Cargo.toml index 38874d7a..6fe48d5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,4 @@ serde_urlencoded = "0.5.1" url = "1.5.1" [lib] -doctest = false proc-macro = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8a75e6ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Jimmy Cuadra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..5b16942d --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# ruma-api-macros + +[![Build Status](https://travis-ci.org/ruma/ruma-api-macros.svg?branch=master)](https://travis-ci.org/ruma/ruma-api-macros) + +**ruma-api-macros** provides a procedural macro for easily generating [ruma-api](https://github.com/ruma/ruma-api)-compatible API endpoints. +You define the endpoint's metadata, request fields, and response fields, and the macro generates all the necessary types and implements all the necessary traits. + +## Usage + +Here is an example that shows most of the macro's functionality. + +``` rust +#![feature(associated_consts, proc_macro, try_from)] + +extern crate futures; +extern crate hyper; +extern crate ruma_api; +extern crate ruma_api_macros; +extern crate serde; +#[macro_use] extern crate serde_derive; +extern crate serde_json; +extern crate serde_urlencoded; +extern crate url; + +pub mod some_endpoint { + use ruma_api_macros::ruma_api; + + ruma_api! { + metadata { + description: "Does something.", + method: Method::Get, // A `hyper::Method` value. No need to import the name. + name: "some_endpoint", + path: "/_matrix/some/endpoint/:baz", + rate_limited: false, + requires_authentication: false, + } + + request { + // With no attribute on the field, it will be put into the body of the request. + pub foo: String, + + // This value will be put into the "Content-Type" HTTP header. + #[ruma_api(header)] + pub content_type: ContentType, + + // This value will be put into the query string of the request's URL. + #[ruma_api(query)] + pub bar: String, + + // This value will be inserted into the request's URL in place of the + // ":baz" path component. + #[ruma_api(path)] + pub baz: String, + } + + response { + // This value will be extracted from the "Content-Type" HTTP header. + #[ruma_api(header)] + pub content_type: ContentType, + + // With no attribute on the field, it will be extracted from the body of the response. + pub value: String, + } + } +} +``` + +## Documentation + +ruma-api-macros has [comprehensive documentation](https://docs.rs/ruma-api-macros) available on docs.rs. + +## License + +[MIT](http://opensource.org/licenses/MIT) diff --git a/src/lib.rs b/src/lib.rs index 9ce90231..539d8258 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ //! Crate `ruma-api-macros` provides a procedural macro for easily generating //! [ruma-api](https://github.com/ruma/ruma-api)-compatible endpoints. +//! +//! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] #![feature(proc_macro)] @@ -21,7 +23,177 @@ use parse::parse_entries; mod api; mod parse; -/// Generates a `ruma-api` endpoint. +/// Generates a `ruma_api::Endpoint` from a concise definition. +/// +/// The macro expects the following structure as input: +/// +/// ```text +/// ruma_api! { +/// metadata { +/// description: &'static str +/// method: hyper::Method, +/// name: &'static str, +/// path: &'static str, +/// rate_limited: bool, +/// requires_authentication: bool, +/// } +/// +/// request { +/// // Struct fields for each piece of data required +/// // to make a request to this API endpoint. +/// } +/// +/// response { +/// // Struct fields for each piece of data expected +/// // in the response from this API endpoint. +/// } +/// } +/// # } +/// ``` +/// +/// This will generate a `ruma_api::Metadata` value to be used for the `ruma_api::Endpoint`'s +/// associated constant, single `Request` and `Response` structs, and the necessary trait +/// implementations to convert the request into a `hyper::Request` and to create a response from a +/// `hyper::response`. +/// +/// The details of each of the three sections of the macros are documented below. +/// +/// ## Metadata +/// +/// * `description`: A short description of what the endpoint does. +/// * `method`: The HTTP method used for requests to the endpoint. +/// It's not necessary to import `hyper::Method`, you just write the value as if it was +/// imported, e.g. `Method::Get`. +/// * `name`: A unique name for the endpoint. +/// Generally this will be the same as the containing module. +/// * `path`: The path component of the URL for the endpoint, e.g. "/foo/bar". +/// Components of the path that are parameterized can indicate a varible by using a Rust +/// identifier prefixed with a colon, e.g. `/foo/:some_parameter`. +/// A corresponding query string parameter will be expected in the request struct (see below +/// for details). +/// * `rate_limited`: Whether or not the endpoint enforces rate limiting on requests. +/// * `requires_authentication`: Whether or not the endpoint requires a valid access token. +/// +/// ## Request +/// +/// The request block contains normal struct field definitions. +/// Doc comments and attributes are allowed as normal. +/// There are also a few special attributes available to control how the struct is converted into a +/// `hyper::Request`: +/// +/// * `#[ruma_api(header)]`: Fields with this attribute will be treated as HTTP headers on the +/// request. +/// The value must implement `hyper::header::Header`. +/// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path +/// component of the request URL. +/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query +/// string. +/// +/// Any field that does not include one of these attributes will be part of the request's JSON +/// body. +/// +/// ## Response +/// +/// Like the request block, the response block consists of normal struct field definitions. +/// Doc comments and attributes are allowed as normal. +/// There is also a special attribute available to control how the struct is created from a +/// `hyper::Request`: +/// +/// * `#[ruma_api(header)]`: Fields with this attribute will be treated as HTTP headers on the +/// response. +/// The value must implement `hyper::header::Header`. +/// +/// Any field that does not include the above attribute will be expected in the response's JSON +/// body. +/// +/// ## Newtype bodies +/// +/// Both the request and response block also support "newtype bodies" by using the +/// `#[ruma_api(body)]` attribute on a field. If present on a field, the entire request or response +/// body will be treated as the value of the field. This allows you to treat the entire request or +/// response body as a specific type, rather than a JSON object with named fields. Only one field in +/// each struct can be marked with this attribute. It is an error to have a newtype body field and +/// normal body fields within the same struct. +/// +/// # Examples +/// +/// ```rust,no_run +/// #![feature(associated_consts, proc_macro, try_from)] +/// +/// extern crate futures; +/// extern crate hyper; +/// extern crate ruma_api; +/// extern crate ruma_api_macros; +/// extern crate serde; +/// #[macro_use] extern crate serde_derive; +/// extern crate serde_json; +/// extern crate serde_urlencoded; +/// extern crate url; +/// +/// # fn main() { +/// pub mod some_endpoint { +/// use hyper::header::ContentType; +/// use ruma_api_macros::ruma_api; +/// +/// ruma_api! { +/// metadata { +/// description: "Does something.", +/// method: Method::Get, +/// name: "some_endpoint", +/// path: "/_matrix/some/endpoint/:baz", +/// rate_limited: false, +/// requires_authentication: false, +/// } +/// +/// request { +/// pub foo: String, +/// #[ruma_api(header)] +/// pub content_type: ContentType, +/// #[ruma_api(query)] +/// pub bar: String, +/// #[ruma_api(path)] +/// pub baz: String, +/// } +/// +/// response { +/// #[ruma_api(header)] +/// pub content_type: ContentType, +/// pub value: String, +/// } +/// } +/// } +/// +/// pub mod newtype_body_endpoint { +/// use ruma_api_macros::ruma_api; +/// +/// #[derive(Debug, Deserialize)] +/// pub struct MyCustomType { +/// pub foo: String, +/// } +/// +/// ruma_api! { +/// metadata { +/// description: "Does something.", +/// method: Method::Get, +/// name: "newtype_body_endpoint", +/// path: "/_matrix/some/newtype/body/endpoint", +/// rate_limited: false, +/// requires_authentication: false, +/// } +/// +/// request { +/// #[ruma_api(body)] +/// pub file: Vec, +/// } +/// +/// response { +/// #[ruma_api(body)] +/// pub my_custom_type: MyCustomType, +/// } +/// } +/// } +/// # } +/// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); From d3265f32517a9ba5ee9b27d27acf3d38ec501b2d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 7 Jul 2017 01:26:51 -0700 Subject: [PATCH 040/107] Use ruma-api 0.4.0 and add missing crate metadata. --- Cargo.toml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6fe48d5f..ffa6086b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,20 @@ [package] authors = ["Jimmy Cuadra "] +description = "A procedural macro for generating ruma-api Endpoints." +documentation = "https://docs.rs/ruma-api-macros" +homepage = "https://github.com/ruma/ruma-api-macros" +keywords = ["matrix", "chat", "messaging", "ruma"] +license = "MIT" name = "ruma-api-macros" +readme = "README.md" +repository = "https://github.com/ruma/ruma-api-macros" version = "0.1.0" [dependencies] quote = "0.3.15" +ruma-api = "0.4.0" synom = "0.11.3" -[dependencies.ruma-api] -git = "https://github.com/ruma/ruma-api" -rev = "4893be93f86543f9a9e3c5d7205afba769b84aa6" - [dependencies.syn] features = ["full"] version = "0.11.11" From 17b11d1a25ba19a4ab50c01d29c9e577f2112df6 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 03:33:08 -0700 Subject: [PATCH 041/107] WIP --- Cargo.toml | 22 +- src/api/metadata.rs | 50 ++++ src/api/mod.rs | 632 ++++++++++++++++++++++++-------------------- src/api/request.rs | 20 +- src/api/response.rs | 10 +- src/lib.rs | 22 +- src/parse.rs | 79 +++--- 7 files changed, 470 insertions(+), 365 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ffa6086b..debf9c7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,22 +11,24 @@ repository = "https://github.com/ruma/ruma-api-macros" version = "0.1.0" [dependencies] -quote = "0.3.15" -ruma-api = "0.4.0" -synom = "0.11.3" +quote = "0.5.2" +ruma-api = "0.5.0" [dependencies.syn] +version = "0.13.4" features = ["full"] -version = "0.11.11" + +[dependencies.proc-macro2] +version = "0.3.8" +features = ["nightly"] [dev-dependencies] -futures = "0.1.14" -hyper = "0.11" -serde = "1.0.8" -serde_derive = "1.0.8" -serde_json = "1.0.2" +futures = "0.1.21" +serde = "1.0.45" +serde_derive = "1.0.45" +serde_json = "1.0.17" serde_urlencoded = "0.5.1" -url = "1.5.1" +url = "1.7.0" [lib] proc-macro = true diff --git a/src/api/metadata.rs b/src/api/metadata.rs index e0de6841..5855314a 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,4 +1,5 @@ use quote::{ToTokens, Tokens}; +use syn::synom::Synom; use syn::{Expr, Ident}; #[derive(Debug)] @@ -11,6 +12,55 @@ pub struct Metadata { pub requires_authentication: Tokens, } +impl Synom for Metadata { + named!(parse -> Self, do_parse!( + ident: syn!(Ident) >> + cond_reduce!(ident == "description") >> + punct!(:) >> + description: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "method") >> + punct!(:) >> + method: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "name") >> + punct!(:) >> + name: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "path") >> + punct!(:) >> + path: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "rate_limited") >> + punct!(:) >> + rate_limited: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "requires_authentication") >> + punct!(:) >> + requires_authentication: syn!(Expr) >> + punct!(,) >> + + (Metadata { + description, + method, + name, + path, + rate_limited, + requires_authentication, + }) + )); +} + impl From> for Metadata { fn from(fields: Vec<(Ident, Expr)>) -> Self { let mut description = None; diff --git a/src/api/mod.rs b/src/api/mod.rs index 8ed40e76..c54aaa40 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,35 +1,39 @@ +use std::convert::{TryFrom, TryInto}; + use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem}; +use syn::punctuated::Pair; +use syn::synom::Synom; +use syn::{Expr, Field, Ident, Meta}; mod metadata; mod request; mod response; -use parse::Entry; +// use parse::Entry; use self::metadata::Metadata; use self::request::Request; use self::response::Response; -pub fn strip_serde_attrs(field: &Field) -> Field { - let mut field = field.clone(); +// pub fn strip_serde_attrs(field: &Field) -> Field { +// let mut field = field.clone(); - field.attrs = field.attrs.into_iter().filter(|attr| { - let (attr_ident, _) = match attr.value { - MetaItem::List(ref attr_ident, _) => { - (attr_ident, ()) - } - _ => return true, - }; +// field.attrs = field.attrs.into_iter().filter(|attr| { +// let (attr_ident, _) = match attr.value { +// Meta::List(ref attr_ident, _) => { +// (attr_ident, ()) +// } +// _ => return true, +// }; - if attr_ident != "serde" { - return true; - } +// if attr_ident != "serde" { +// return true; +// } - false - }).collect(); +// false +// }).collect(); - field -} +// field +// } #[derive(Debug)] pub struct Api { @@ -38,291 +42,333 @@ pub struct Api { response: Response, } -impl ToTokens for Api { - fn to_tokens(&self, tokens: &mut Tokens) { - let description = &self.metadata.description; - let method = &self.metadata.method; - let name = &self.metadata.name; - let path = &self.metadata.path; - let rate_limited = &self.metadata.rate_limited; - let requires_authentication = &self.metadata.requires_authentication; +impl TryFrom> for Api { + type Error = &'static str; - let request_types = { - let mut tokens = Tokens::new(); - self.request.to_tokens(&mut tokens); - tokens - }; - let response_types = { - let mut tokens = Tokens::new(); - self.response.to_tokens(&mut tokens); - tokens - }; - - let set_request_path = if self.request.has_path_fields() { - let path_str_quoted = path.as_str(); - assert!( - path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), - "path needs to be a string literal" - ); - - let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; - - assert!(path_str.starts_with('/'), "path needs to start with '/'"); - assert!( - path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), - "number of declared path parameters needs to match amount of placeholders in path" - ); - - let request_path_init_fields = self.request.request_path_init_fields(); - - let mut tokens = quote! { - let request_path = RequestPath { - #request_path_init_fields - }; - - // This `unwrap()` can only fail when the url is a - // cannot-be-base url like `mailto:` or `data:`, which is not - // the case for our placeholder url. - let mut path_segments = url.path_segments_mut().unwrap(); - }; - - for segment in path_str[1..].split('/') { - tokens.append(quote! { - path_segments.push - }); - - tokens.append("("); - - if segment.starts_with(':') { - tokens.append("&request_path."); - tokens.append(&segment[1..]); - tokens.append(".to_string()"); - } else { - tokens.append("\""); - tokens.append(segment); - tokens.append("\""); - } - - tokens.append(");"); - } - - tokens - } else { - quote! { - url.set_path(metadata.path); - } - }; - - let set_request_query = if self.request.has_query_fields() { - let request_query_init_fields = self.request.request_query_init_fields(); - - quote! { - let request_query = RequestQuery { - #request_query_init_fields - }; - - url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); - } - } else { - Tokens::new() - }; - - let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected body field to have a name"); - - quote! { - let request_body = RequestBody(request.#field_name); - - hyper_request.set_body(::serde_json::to_vec(&request_body)?); - } - } else if self.request.has_body_fields() { - let request_body_init_fields = self.request.request_body_init_fields(); - - quote! { - let request_body = RequestBody { - #request_body_init_fields - }; - - hyper_request.set_body(::serde_json::to_vec(&request_body)?); - } - } else { - Tokens::new() - }; - - let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { - let field_type = &field.ty; - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = hyper_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::<#field_type>(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) - }); - - tokens.append(".and_then(move |response_body| {"); - - tokens - } else if self.response.has_body_fields() { - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = hyper_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) - }); - - tokens.append(".and_then(move |response_body| {"); - - tokens - } else { - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = ::futures::future::ok(()) - }); - - tokens.append(".and_then(move |_| {"); - - tokens - }; - - let mut closure_end = Tokens::new(); - closure_end.append("});"); - - let extract_headers = if self.response.has_header_fields() { - quote! { - let mut headers = hyper_response.headers().clone(); - } - } else { - Tokens::new() - }; - - let response_init_fields = if self.response.has_fields() { - self.response.init_fields() - } else { - Tokens::new() - }; - - tokens.append(quote! { - #[allow(unused_imports)] - use std::io::Write as _Write; - - #[allow(unused_imports)] - use ::futures::{Future as _Future, Stream as _Stream}; - use ::ruma_api::Endpoint as _RumaApiEndpoint; - - /// The API endpoint. - #[derive(Debug)] - pub struct Endpoint; - - #request_types - - impl ::std::convert::TryFrom for ::hyper::Request { - type Error = ::ruma_api::Error; - - #[allow(unused_mut, unused_variables)] - fn try_from(request: Request) -> Result { - let metadata = Endpoint::METADATA; - - // Use dummy homeserver url which has to be overwritten in - // the calling code. Previously (with hyper::Uri) this was - // not required, but Url::parse only accepts absolute urls. - let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); - - { #set_request_path } - { #set_request_query } - - let mut hyper_request = ::hyper::Request::new( - metadata.method, - // Every valid URL is a valid URI - url.into_string().parse().unwrap(), - ); - - { #add_body_to_request } - - Ok(hyper_request) - } - } - - #response_types - - impl ::futures::future::FutureFrom<::hyper::Response> for Response { - type Future = Box<_Future>; - type Error = ::ruma_api::Error; - - #[allow(unused_variables)] - fn future_from(hyper_response: ::hyper::Response) - -> Box<_Future> { - #extract_headers - - #deserialize_response_body - - let response = Response { - #response_init_fields - }; - - Ok(response) - #closure_end - - Box::new(future_response) - } - } - - impl ::ruma_api::Endpoint for Endpoint { - type Request = Request; - type Response = Response; - - const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { - description: #description, - method: ::hyper::#method, - name: #name, - path: #path, - rate_limited: #rate_limited, - requires_authentication: #requires_authentication, - }; - } - }); - } -} - -impl From> for Api { - fn from(entries: Vec) -> Api { - if entries.len() != 3 { - panic!("ruma_api! expects 3 blocks: metadata, request, and response"); + fn try_from(exprs: Vec) -> Result { + if exprs.len() != 3 { + return Err("ruma_api! expects 3 blocks: metadata, request, and response"); } let mut metadata = None; let mut request = None; let mut response = None; - for entry in entries { - match entry { - Entry::Metadata(fields) => metadata = Some(Metadata::from(fields)), - Entry::Request(fields) => request = Some(Request::from(fields)), - Entry::Response(fields) => response = Some(Response::from(fields)), + for expr in exprs { + let expr = match expr { + Expr::Struct(expr) => expr, + _ => return Err("ruma_api! blocks should use struct syntax"), + }; + + let segments = expr.path.segments; + + if segments.len() != 1 { + return Err("ruma_api! blocks must be one of: metadata, request, or response"); + } + + let Pair::End(last_segment) = segments.last().unwrap(); + + match last_segment.ident.as_ref() { + "metadata" => metadata = Some(expr.try_into()?), + "request" => request = Some(expr.try_into()?), + "response" => response = Some(expr.try_into()?), + _ => return Err("ruma_api! blocks must be one of: metadata, request, or response"), } } - Api { - metadata: metadata.expect("ruma_api! is missing metadata"), - request: request.expect("ruma_api! is missing request"), - response: response.expect("ruma_api! is missing response"), + if metadata.is_none() { + return Err("ruma_api! is missing metadata"); } + + if request.is_none() { + return Err("ruma_api! is missing request"); + } + + if response.is_none() { + return Err("ruma_api! is missing response"); + } + + Ok(Api { + metadata: metadata.unwrap(), + request: request.unwrap(), + response: response.unwrap(), + }) + } } + +pub struct Exprs { + pub inner: Vec, +} + +impl Synom for Exprs { + named!(parse -> Self, do_parse!( + exprs: many0!(syn!(Expr)) >> + (Exprs { + inner: exprs, + }) + )); +} +// impl ToTokens for Api { +// fn to_tokens(&self, tokens: &mut Tokens) { +// let description = &self.metadata.description; +// let method = &self.metadata.method; +// let name = &self.metadata.name; +// let path = &self.metadata.path; +// let rate_limited = &self.metadata.rate_limited; +// let requires_authentication = &self.metadata.requires_authentication; + +// let request_types = { +// let mut tokens = Tokens::new(); +// self.request.to_tokens(&mut tokens); +// tokens +// }; +// let response_types = { +// let mut tokens = Tokens::new(); +// self.response.to_tokens(&mut tokens); +// tokens +// }; + +// let set_request_path = if self.request.has_path_fields() { +// let path_str_quoted = path.as_str(); +// assert!( +// path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), +// "path needs to be a string literal" +// ); + +// let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + +// assert!(path_str.starts_with('/'), "path needs to start with '/'"); +// assert!( +// path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), +// "number of declared path parameters needs to match amount of placeholders in path" +// ); + +// let request_path_init_fields = self.request.request_path_init_fields(); + +// let mut tokens = quote! { +// let request_path = RequestPath { +// #request_path_init_fields +// }; + +// // This `unwrap()` can only fail when the url is a +// // cannot-be-base url like `mailto:` or `data:`, which is not +// // the case for our placeholder url. +// let mut path_segments = url.path_segments_mut().unwrap(); +// }; + +// for segment in path_str[1..].split('/') { +// tokens.append(quote! { +// path_segments.push +// }); + +// tokens.append("("); + +// if segment.starts_with(':') { +// tokens.append("&request_path."); +// tokens.append(&segment[1..]); +// tokens.append(".to_string()"); +// } else { +// tokens.append("\""); +// tokens.append(segment); +// tokens.append("\""); +// } + +// tokens.append(");"); +// } + +// tokens +// } else { +// quote! { +// url.set_path(metadata.path); +// } +// }; + +// let set_request_query = if self.request.has_query_fields() { +// let request_query_init_fields = self.request.request_query_init_fields(); + +// quote! { +// let request_query = RequestQuery { +// #request_query_init_fields +// }; + +// url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); +// } +// } else { +// Tokens::new() +// }; + +// let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { +// let field_name = field.ident.as_ref().expect("expected body field to have a name"); + +// quote! { +// let request_body = RequestBody(request.#field_name); + +// hyper_request.set_body(::serde_json::to_vec(&request_body)?); +// } +// } else if self.request.has_body_fields() { +// let request_body_init_fields = self.request.request_body_init_fields(); + +// quote! { +// let request_body = RequestBody { +// #request_body_init_fields +// }; + +// hyper_request.set_body(::serde_json::to_vec(&request_body)?); +// } +// } else { +// Tokens::new() +// }; + +// let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { +// let field_type = &field.ty; +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = hyper_response.body() +// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { +// bytes.write_all(&chunk)?; + +// Ok(bytes) +// }) +// .map_err(::ruma_api::Error::from) +// .and_then(|bytes| { +// ::serde_json::from_slice::<#field_type>(bytes.as_slice()) +// .map_err(::ruma_api::Error::from) +// }) +// }); + +// tokens.append(".and_then(move |response_body| {"); + +// tokens +// } else if self.response.has_body_fields() { +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = hyper_response.body() +// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { +// bytes.write_all(&chunk)?; + +// Ok(bytes) +// }) +// .map_err(::ruma_api::Error::from) +// .and_then(|bytes| { +// ::serde_json::from_slice::(bytes.as_slice()) +// .map_err(::ruma_api::Error::from) +// }) +// }); + +// tokens.append(".and_then(move |response_body| {"); + +// tokens +// } else { +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = ::futures::future::ok(()) +// }); + +// tokens.append(".and_then(move |_| {"); + +// tokens +// }; + +// let mut closure_end = Tokens::new(); +// closure_end.append("});"); + +// let extract_headers = if self.response.has_header_fields() { +// quote! { +// let mut headers = hyper_response.headers().clone(); +// } +// } else { +// Tokens::new() +// }; + +// let response_init_fields = if self.response.has_fields() { +// self.response.init_fields() +// } else { +// Tokens::new() +// }; + +// tokens.append(quote! { +// #[allow(unused_imports)] +// use std::io::Write as _Write; + +// #[allow(unused_imports)] +// use ::futures::{Future as _Future, Stream as _Stream}; +// use ::ruma_api::Endpoint as _RumaApiEndpoint; + +// /// The API endpoint. +// #[derive(Debug)] +// pub struct Endpoint; + +// #request_types + +// impl ::std::convert::TryFrom for ::hyper::Request { +// type Error = ::ruma_api::Error; + +// #[allow(unused_mut, unused_variables)] +// fn try_from(request: Request) -> Result { +// let metadata = Endpoint::METADATA; + +// // Use dummy homeserver url which has to be overwritten in +// // the calling code. Previously (with hyper::Uri) this was +// // not required, but Url::parse only accepts absolute urls. +// let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); + +// { #set_request_path } +// { #set_request_query } + +// let mut hyper_request = ::hyper::Request::new( +// metadata.method, +// // Every valid URL is a valid URI +// url.into_string().parse().unwrap(), +// ); + +// { #add_body_to_request } + +// Ok(hyper_request) +// } +// } + +// #response_types + +// impl ::futures::future::FutureFrom<::hyper::Response> for Response { +// type Future = Box<_Future>; +// type Error = ::ruma_api::Error; + +// #[allow(unused_variables)] +// fn future_from(hyper_response: ::hyper::Response) +// -> Box<_Future> { +// #extract_headers + +// #deserialize_response_body + +// let response = Response { +// #response_init_fields +// }; + +// Ok(response) +// #closure_end + +// Box::new(future_response) +// } +// } + +// impl ::ruma_api::Endpoint for Endpoint { +// type Request = Request; +// type Response = Response; + +// const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { +// description: #description, +// method: ::hyper::#method, +// name: #name, +// path: #path, +// rate_limited: #rate_limited, +// requires_authentication: #requires_authentication, +// }; +// } +// }); +// } +// } + diff --git a/src/api/request.rs b/src/api/request.rs index be9729b4..163e1d31 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,5 +1,6 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem, NestedMetaItem}; +use syn::synom::Synom; +use syn::{Field, FieldsNamed, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -8,6 +9,15 @@ pub struct Request { fields: Vec, } +impl Synom for Request { + named!(parse -> Self, do_parse!( + fields: syn!(FieldsNamed) >> + (Request { + fields, + }) + )); +} + impl Request { pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) @@ -74,7 +84,7 @@ impl From> for Request { field.attrs = field.attrs.into_iter().filter(|attr| { let (attr_ident, nested_meta_items) = match attr.value { - MetaItem::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), + Meta::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), _ => return true, }; @@ -84,9 +94,9 @@ impl From> for Request { for nested_meta_item in nested_meta_items { match *nested_meta_item { - NestedMetaItem::MetaItem(ref meta_item) => { + NestedMeta::Meta(ref meta_item) => { match *meta_item { - MetaItem::Word(ref ident) => { + Meta::Word(ref ident) => { if ident == "body" { has_newtype_body = true; request_field_kind = RequestFieldKind::NewtypeBody; @@ -107,7 +117,7 @@ impl From> for Request { ), } } - NestedMetaItem::Literal(_) => panic!( + NestedMeta::Literal(_) => panic!( "ruma_api! attribute meta item on requests must be: body, header, path, or query" ), } diff --git a/src/api/response.rs b/src/api/response.rs index abe0905e..2fd1782a 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem, NestedMetaItem}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -83,7 +83,7 @@ impl From> for Response { field.attrs = field.attrs.into_iter().filter(|attr| { let (attr_ident, nested_meta_items) = match attr.value { - MetaItem::List(ref attr_ident, ref nested_meta_items) => { + Meta::List(ref attr_ident, ref nested_meta_items) => { (attr_ident, nested_meta_items) } _ => return true, @@ -95,9 +95,9 @@ impl From> for Response { for nested_meta_item in nested_meta_items { match *nested_meta_item { - NestedMetaItem::MetaItem(ref meta_item) => { + NestedMeta::Meta(ref meta_item) => { match *meta_item { - MetaItem::Word(ref ident) => { + Meta::Word(ref ident) => { if ident == "body" { has_newtype_body = true; response_field_kind = ResponseFieldKind::NewtypeBody; @@ -114,7 +114,7 @@ impl From> for Response { ), } } - NestedMetaItem::Literal(_) => panic!( + NestedMeta::Literal(_) => panic!( "ruma_api! attribute meta item on responses must be: header" ), } diff --git a/src/lib.rs b/src/lib.rs index 539d8258..2c4341f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,24 +4,24 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro)] +#![feature(proc_macro, try_from)] #![recursion_limit="256"] +#![allow(warnings)] extern crate proc_macro; #[macro_use] extern crate quote; extern crate ruma_api; -extern crate syn; -#[macro_use] extern crate synom; +#[macro_use] extern crate syn; use proc_macro::TokenStream; +use std::convert::TryFrom; use quote::{ToTokens, Tokens}; -use api::Api; -use parse::parse_entries; +use api::{Api, Exprs}; mod api; -mod parse; +// mod parse; /// Generates a `ruma_api::Endpoint` from a concise definition. /// @@ -196,13 +196,15 @@ mod parse; /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); - - let api = Api::from(entries); + let exprs: Exprs = syn::parse(input).expect("ruma_api! failed to parse input"); + let api = match Api::try_from(exprs.inner) { + Ok(api) => api, + Err(error) => panic!("{}", error), + }; let mut tokens = Tokens::new(); api.to_tokens(&mut tokens); - tokens.parse().expect("ruma_api! failed to parse output tokens as a TokenStream") + tokens.into() } diff --git a/src/parse.rs b/src/parse.rs index 4b4a0e80..483efb87 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -6,12 +6,12 @@ use syn::{ Expr, Field, Ident, - MetaItem, - NestedMetaItem, + Meta, + NestedMeta, Visibility, }; -use syn::parse::{expr, ident, lit, ty}; -use synom::space::{block_comment, whitespace}; +// use syn::parse::{expr, ident, lit, ty}; +// use synom::space::{block_comment, whitespace}; #[derive(Debug)] pub enum Entry { @@ -27,27 +27,24 @@ named!(pub parse_entries -> Vec, do_parse!( named!(entry -> Entry, alt!( do_parse!( - keyword!("metadata") >> - punct!("{") >> - fields: many0!(struct_init_field) >> - punct!("}") >> - (Entry::Metadata(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "metadata") >> + brace_and_fields: braces!(many0!(struct_init_field)) >> + (Entry::Metadata(brace_and_fields.1)) ) | do_parse!( - keyword!("request") >> - punct!("{") >> - fields: terminated_list!(punct!(","), struct_field) >> - punct!("}") >> - (Entry::Request(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "request") >> + brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> + (Entry::Request(brace_and_fields.1)) ) | do_parse!( - keyword!("response") >> - punct!("{") >> - fields: terminated_list!(punct!(","), struct_field) >> - punct!("}") >> - (Entry::Response(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "response") >> + brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> + (Entry::Response(brace_and_fields.1)) ) )); @@ -55,9 +52,9 @@ named!(entry -> Entry, alt!( named!(struct_init_field -> (Ident, Expr), do_parse!( ident: ident >> - punct!(":") >> + punct!(:) >> expr: expr >> - punct!(",") >> + punct!(,) >> (ident, expr) )); @@ -65,7 +62,7 @@ named!(struct_field -> Field, do_parse!( attrs: many0!(outer_attr) >> visibility >> id: ident >> - punct!(":") >> + punct!(:) >> ty: ty >> (Field { ident: Some(id), @@ -77,24 +74,24 @@ named!(struct_field -> Field, do_parse!( named!(outer_attr -> Attribute, alt!( do_parse!( - punct!("#") >> - punct!("[") >> - meta_item: meta_item >> - punct!("]") >> + punct!(#) >> + brackets_and_meta_item: brackets!(meta_item) >> (Attribute { style: AttrStyle::Outer, - value: meta_item, + value: brackets_and_meta_item.1, is_sugared_doc: false, }) ) | do_parse!( - punct!("///") >> + punct!(/) >> + punct!(/) >> + punct!(/) >> not!(tag!("/")) >> content: take_until!("\n") >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( + value: Meta::NameValue( "doc".into(), format!("///{}", content).into(), ), @@ -108,7 +105,7 @@ named!(outer_attr -> Attribute, alt!( com: block_comment >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( + value: Meta::NameValue( "doc".into(), com.into(), ), @@ -117,33 +114,31 @@ named!(outer_attr -> Attribute, alt!( ) )); -named!(meta_item -> MetaItem, alt!( +named!(meta_item -> Meta, alt!( do_parse!( id: ident >> - punct!("(") >> - inner: terminated_list!(punct!(","), nested_meta_item) >> - punct!(")") >> - (MetaItem::List(id, inner)) + parens_and_inner: parens!(terminated_list!(punct!(,), nested_meta_item)) >> + (Meta::List(id, parens_and_inner.1)) ) | do_parse!( name: ident >> - punct!("=") >> + punct!(=) >> value: lit >> - (MetaItem::NameValue(name, value)) + (Meta::NameValue(name, value)) ) | - map!(ident, MetaItem::Word) + map!(ident, Meta::Word) )); -named!(nested_meta_item -> NestedMetaItem, alt!( - meta_item => { NestedMetaItem::MetaItem } +named!(nested_meta_item -> NestedMeta, alt!( + meta_item => { NestedMeta::Meta } | - lit => { NestedMetaItem::Literal } + lit => { NestedMeta::Literal } )); named!(visibility -> Visibility, alt!( - keyword!("pub") => { |_| Visibility::Public } + keyword!(pub) => { |_| Visibility::Public } | epsilon!() => { |_| Visibility::Inherited } )); From 4db09dac8de6ed2ba0202a583eb4b87db5f779e2 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 03:52:55 -0700 Subject: [PATCH 042/107] ExprStruct --> Metadata --- src/api/metadata.rs | 144 ++++++++++++++++---------------------------- src/api/mod.rs | 1 - 2 files changed, 53 insertions(+), 92 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 5855314a..80c27ecc 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,68 +1,22 @@ +use std::convert::TryFrom; + use quote::{ToTokens, Tokens}; use syn::synom::Synom; -use syn::{Expr, Ident}; +use syn::{Expr, ExprStruct, Ident, Member}; -#[derive(Debug)] pub struct Metadata { - pub description: Tokens, - pub method: Tokens, - pub name: Tokens, - pub path: Tokens, - pub rate_limited: Tokens, - pub requires_authentication: Tokens, + pub description: Expr, + pub method: Expr, + pub name: Expr, + pub path: Expr, + pub rate_limited: Expr, + pub requires_authentication: Expr, } -impl Synom for Metadata { - named!(parse -> Self, do_parse!( - ident: syn!(Ident) >> - cond_reduce!(ident == "description") >> - punct!(:) >> - description: syn!(Expr) >> - punct!(,) >> +impl TryFrom for Metadata { + type Error = &'static str; - ident: syn!(Ident) >> - cond_reduce!(ident == "method") >> - punct!(:) >> - method: syn!(Expr) >> - punct!(,) >> - - ident: syn!(Ident) >> - cond_reduce!(ident == "name") >> - punct!(:) >> - name: syn!(Expr) >> - punct!(,) >> - - ident: syn!(Ident) >> - cond_reduce!(ident == "path") >> - punct!(:) >> - path: syn!(Expr) >> - punct!(,) >> - - ident: syn!(Ident) >> - cond_reduce!(ident == "rate_limited") >> - punct!(:) >> - rate_limited: syn!(Expr) >> - punct!(,) >> - - ident: syn!(Ident) >> - cond_reduce!(ident == "requires_authentication") >> - punct!(:) >> - requires_authentication: syn!(Expr) >> - punct!(,) >> - - (Metadata { - description, - method, - name, - path, - rate_limited, - requires_authentication, - }) - )); -} - -impl From> for Metadata { - fn from(fields: Vec<(Ident, Expr)>) -> Self { + fn try_from(expr: ExprStruct) -> Result { let mut description = None; let mut method = None; let mut name = None; @@ -70,43 +24,51 @@ impl From> for Metadata { let mut rate_limited = None; let mut requires_authentication = None; - for field in fields { - let (identifier, expression) = field; + for field in expr.fields { + let Member::Named(identifier) = field.member; - if identifier == Ident::new("description") { - description = Some(tokens_for(expression)); - } else if identifier == Ident::new("method") { - method = Some(tokens_for(expression)); - } else if identifier == Ident::new("name") { - name = Some(tokens_for(expression)); - } else if identifier == Ident::new("path") { - path = Some(tokens_for(expression)); - } else if identifier == Ident::new("rate_limited") { - rate_limited = Some(tokens_for(expression)); - } else if identifier == Ident::new("requires_authentication") { - requires_authentication = Some(tokens_for(expression)); - } else { - panic!("ruma_api! metadata included unexpected field: {}", identifier); + match identifier.as_ref() { + "description" => description = Some(field.expr), + "method" => method = Some(field.expr), + "name" => name = Some(field.expr), + "path" => path = Some(field.expr), + "rate_limited" => rate_limited = Some(field.expr), + "requires_authentication" => requires_authentication = Some(field.expr), + _ => return Err("ruma_api! metadata included unexpected field"), } } - Metadata { - description: description.expect("ruma_api! metadata is missing description"), - method: method.expect("ruma_api! metadata is missing method"), - name: name.expect("ruma_api! metadata is missing name"), - path: path.expect("ruma_api! metadata is missing path"), - rate_limited: rate_limited.expect("ruma_api! metadata is missing rate_limited"), - requires_authentication: requires_authentication - .expect("ruma_api! metadata is missing requires_authentication"), + if description.is_none() { + return Err("ruma_api! metadata is missing description"); } + + if method.is_none() { + return Err("ruma_api! metadata is missing method"); + } + + if name.is_none() { + return Err("ruma_api! metadata is missing name"); + } + + if path.is_none() { + return Err("ruma_api! metadata is missing path"); + } + + if rate_limited.is_none() { + return Err("ruma_api! metadata is missing rate_limited"); + } + + if requires_authentication.is_none() { + return Err("ruma_api! metadata is missing requires_authentication"); + } + + Ok(Metadata { + description: description.unwrap(), + method: method.unwrap(), + name: name.unwrap(), + path: path.unwrap(), + rate_limited: rate_limited.unwrap(), + requires_authentication: requires_authentication.unwrap(), + }) } } - -/// Helper method for turning a value into tokens. -fn tokens_for(value: T) -> Tokens where T: ToTokens { - let mut tokens = Tokens::new(); - - value.to_tokens(&mut tokens); - - tokens -} diff --git a/src/api/mod.rs b/src/api/mod.rs index c54aaa40..f3a871ae 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -35,7 +35,6 @@ use self::response::Response; // field // } -#[derive(Debug)] pub struct Api { metadata: Metadata, request: Request, From ba6eef9c760ae42061c39f47f6d317b815b367fa Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 17:07:35 -0700 Subject: [PATCH 043/107] Switch TryFrom back to From since proc macros must panic on errors. --- src/api/metadata.rs | 49 ++++++++++----------------------------------- src/api/mod.rs | 32 +++++++++++++---------------- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 80c27ecc..6c4ab254 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use quote::{ToTokens, Tokens}; use syn::synom::Synom; use syn::{Expr, ExprStruct, Ident, Member}; @@ -13,10 +11,8 @@ pub struct Metadata { pub requires_authentication: Expr, } -impl TryFrom for Metadata { - type Error = &'static str; - - fn try_from(expr: ExprStruct) -> Result { +impl From for Metadata { + fn from(expr: ExprStruct) -> Self { let mut description = None; let mut method = None; let mut name = None; @@ -34,41 +30,18 @@ impl TryFrom for Metadata { "path" => path = Some(field.expr), "rate_limited" => rate_limited = Some(field.expr), "requires_authentication" => requires_authentication = Some(field.expr), - _ => return Err("ruma_api! metadata included unexpected field"), + _ => panic!("ruma_api! metadata included unexpected field"), } } - if description.is_none() { - return Err("ruma_api! metadata is missing description"); + Metadata { + description: description.expect("ruma_api! `metadata` is missing `description`"), + method: method.expect("ruma_api! `metadata` is missing `method`"), + name: name.expect("ruma_api! `metadata` is missing `name`"), + path: path.expect("ruma_api! `metadata` is missing `path`"), + rate_limited: rate_limited.expect("ruma_api! `metadata` is missing `rate_limited`"), + requires_authentication: requires_authentication + .expect("ruma_api! `metadata` is missing `requires_authentication`"), } - - if method.is_none() { - return Err("ruma_api! metadata is missing method"); - } - - if name.is_none() { - return Err("ruma_api! metadata is missing name"); - } - - if path.is_none() { - return Err("ruma_api! metadata is missing path"); - } - - if rate_limited.is_none() { - return Err("ruma_api! metadata is missing rate_limited"); - } - - if requires_authentication.is_none() { - return Err("ruma_api! metadata is missing requires_authentication"); - } - - Ok(Metadata { - description: description.unwrap(), - method: method.unwrap(), - name: name.unwrap(), - path: path.unwrap(), - rate_limited: rate_limited.unwrap(), - requires_authentication: requires_authentication.unwrap(), - }) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index f3a871ae..35937030 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,3 @@ -use std::convert::{TryFrom, TryInto}; - use quote::{ToTokens, Tokens}; use syn::punctuated::Pair; use syn::synom::Synom; @@ -41,12 +39,10 @@ pub struct Api { response: Response, } -impl TryFrom> for Api { - type Error = &'static str; - - fn try_from(exprs: Vec) -> Result { +impl From> for Api { + fn from(exprs: Vec) -> Self { if exprs.len() != 3 { - return Err("ruma_api! expects 3 blocks: metadata, request, and response"); + panic!("ruma_api! expects 3 blocks: metadata, request, and response"); } let mut metadata = None; @@ -56,42 +52,42 @@ impl TryFrom> for Api { for expr in exprs { let expr = match expr { Expr::Struct(expr) => expr, - _ => return Err("ruma_api! blocks should use struct syntax"), + _ => panic!("ruma_api! blocks should use struct syntax"), }; let segments = expr.path.segments; if segments.len() != 1 { - return Err("ruma_api! blocks must be one of: metadata, request, or response"); + panic!("ruma_api! blocks must be one of: metadata, request, or response"); } let Pair::End(last_segment) = segments.last().unwrap(); match last_segment.ident.as_ref() { - "metadata" => metadata = Some(expr.try_into()?), - "request" => request = Some(expr.try_into()?), - "response" => response = Some(expr.try_into()?), - _ => return Err("ruma_api! blocks must be one of: metadata, request, or response"), + "metadata" => metadata = Some(expr.into()), + "request" => request = Some(expr.into()), + "response" => response = Some(expr.into()), + _ => panic!("ruma_api! blocks must be one of: metadata, request, or response"), } } if metadata.is_none() { - return Err("ruma_api! is missing metadata"); + panic!("ruma_api! is missing metadata"); } if request.is_none() { - return Err("ruma_api! is missing request"); + panic!("ruma_api! is missing request"); } if response.is_none() { - return Err("ruma_api! is missing response"); + panic!("ruma_api! is missing response"); } - Ok(Api { + Api { metadata: metadata.unwrap(), request: request.unwrap(), response: response.unwrap(), - }) + } } } From a1929e38cf7b9dd876c7e8b08d15fe7ab7f17120 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 17:30:20 -0700 Subject: [PATCH 044/107] ExprStruct --> Request --- src/api/request.rs | 87 ++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 50 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index 163e1d31..b2ce32ea 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,23 +1,13 @@ use quote::{ToTokens, Tokens}; use syn::synom::Synom; -use syn::{Field, FieldsNamed, Meta, NestedMeta}; +use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Meta, NestedMeta}; use api::strip_serde_attrs; -#[derive(Debug)] pub struct Request { fields: Vec, } -impl Synom for Request { - named!(parse -> Self, do_parse!( - fields: syn!(FieldsNamed) >> - (Request { - fields, - }) - )); -} - impl Request { pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) @@ -35,7 +25,7 @@ impl Request { self.fields.iter().filter(|field| field.is_path()).count() } - pub fn newtype_body_field(&self) -> Option<&Field> { + pub fn newtype_body_field(&self) -> Option<&FieldValue> { for request_field in self.fields.iter() { match *request_field { RequestField::NewtypeBody(ref field) => { @@ -75,41 +65,39 @@ impl Request { } } -impl From> for Request { - fn from(fields: Vec) -> Self { +impl From for Request { + fn from(expr: ExprStruct) -> Self { let mut has_newtype_body = false; - let request_fields = fields.into_iter().map(|mut field| { - let mut request_field_kind = RequestFieldKind::Body; + let fields = expr.fields.into_iter().map(|mut field_value| { + let mut field_kind = RequestFieldKind::Body; - field.attrs = field.attrs.into_iter().filter(|attr| { - let (attr_ident, nested_meta_items) = match attr.value { - Meta::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), - _ => return true, - }; + field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + let meta = attr.interpret_meta() + .expect("ruma_api! could not parse request field attributes"); - if attr_ident != "ruma_api" { + let Meta::List(meta_list) = meta; + + if meta_list.ident.as_ref() != "ruma_api" { return true; } - for nested_meta_item in nested_meta_items { - match *nested_meta_item { - NestedMeta::Meta(ref meta_item) => { - match *meta_item { - Meta::Word(ref ident) => { - if ident == "body" { + for nested_meta_item in meta_list.nested { + match nested_meta_item { + NestedMeta::Meta(meta_item) => { + match meta_item { + Meta::Word(ident) => { + match ident.as_ref() { + "body" => { has_newtype_body = true; - request_field_kind = RequestFieldKind::NewtypeBody; - } else if ident == "header" { - request_field_kind = RequestFieldKind::Header; - } else if ident == "path" { - request_field_kind = RequestFieldKind::Path; - } else if ident == "query" { - request_field_kind = RequestFieldKind::Query; - } else { - panic!( + field_kind = RequestFieldKind::NewtypeBody; + } + "header" => field_kind = RequestFieldKind::Header, + "path" => field_kind = RequestFieldKind::Path, + "query" => field_kind = RequestFieldKind::Query, + _ => panic!( "ruma_api! attribute meta item on requests must be: body, header, path, or query" - ); + ), } } _ => panic!( @@ -126,18 +114,18 @@ impl From> for Request { false }).collect(); - if request_field_kind == RequestFieldKind::Body { + if field_kind == RequestFieldKind::Body { assert!( !has_newtype_body, "ruma_api! requests cannot have both normal body fields and a newtype body field" ); } - RequestField::new(request_field_kind, field) + RequestField::new(field_kind, field_value) }).collect(); Request { - fields: request_fields, + fields, } } } @@ -251,17 +239,16 @@ impl ToTokens for Request { } } -#[derive(Debug)] pub enum RequestField { - Body(Field), - Header(Field), - NewtypeBody(Field), - Path(Field), - Query(Field), + Body(FieldValue), + Header(FieldValue), + NewtypeBody(FieldValue), + Path(FieldValue), + Query(FieldValue), } impl RequestField { - fn new(kind: RequestFieldKind, field: Field) -> RequestField { + fn new(kind: RequestFieldKind, field: FieldValue) -> RequestField { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => RequestField::Header(field), @@ -293,7 +280,7 @@ impl RequestField { self.kind() == RequestFieldKind::Query } - fn field(&self) -> &Field { + fn field(&self) -> &FieldValue { match *self { RequestField::Body(ref field) => field, RequestField::Header(ref field) => field, @@ -303,7 +290,7 @@ impl RequestField { } } - fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { + fn field_(&self, kind: RequestFieldKind) -> Option<&FieldValue> { if self.kind() == kind { Some(self.field()) } else { From ab106f75ac068bbbffbf32a7c0acb02e209a8784 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 18:46:48 -0700 Subject: [PATCH 045/107] ExprStruct --> Response --- src/api/response.rs | 70 +++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/api/response.rs b/src/api/response.rs index 2fd1782a..79ddfcb4 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,9 +1,8 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, Meta, NestedMeta}; +use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Meta, NestedMeta}; use api::strip_serde_attrs; -#[derive(Debug)] pub struct Response { fields: Vec, } @@ -74,43 +73,41 @@ impl Response { } -impl From> for Response { - fn from(fields: Vec) -> Self { +impl From for Response { + fn from(expr: ExprStruct) -> Self { let mut has_newtype_body = false; - let response_fields = fields.into_iter().map(|mut field| { - let mut response_field_kind = ResponseFieldKind::Body; + let fields = expr.fields.into_iter().map(|mut field_value| { + let mut field_kind = ResponseFieldKind::Body; - field.attrs = field.attrs.into_iter().filter(|attr| { - let (attr_ident, nested_meta_items) = match attr.value { - Meta::List(ref attr_ident, ref nested_meta_items) => { - (attr_ident, nested_meta_items) - } - _ => return true, - }; + field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + let meta = attr.interpret_meta() + .expect("ruma_api! could not parse response field attributes"); - if attr_ident != "ruma_api" { + let Meta::List(meta_list) = meta; + + if meta_list.ident.as_ref() != "ruma_api" { return true; } - for nested_meta_item in nested_meta_items { - match *nested_meta_item { - NestedMeta::Meta(ref meta_item) => { - match *meta_item { - Meta::Word(ref ident) => { - if ident == "body" { + for nested_meta_item in meta_list.nested { + match nested_meta_item { + NestedMeta::Meta(meta_item) => { + match meta_item { + Meta::Word(ident) => { + match ident.as_ref() { + "body" => { has_newtype_body = true; - response_field_kind = ResponseFieldKind::NewtypeBody; - } else if ident == "header" { - response_field_kind = ResponseFieldKind::Header; - } else { - panic!( + field_kind = ResponseFieldKind::NewtypeBody; + } + "header" => field_kind = ResponseFieldKind::Header, + _ => panic!( "ruma_api! attribute meta item on responses must be: header" - ); + ), } } _ => panic!( - "ruma_api! attribute meta item on requests cannot be a list or name/value pair" + "ruma_api! attribute meta item on responses cannot be a list or name/value pair" ), } } @@ -123,21 +120,21 @@ impl From> for Response { false }).collect(); - match response_field_kind { + match field_kind { ResponseFieldKind::Body => { if has_newtype_body { panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); } else { - return ResponseField::Body(field); + return ResponseField::Body(field_value); } } - ResponseFieldKind::Header => ResponseField::Header(field), - ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), + ResponseFieldKind::Header => ResponseField::Header(field_value), + ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field_value), } }).collect(); Response { - fields: response_fields, + fields, } } } @@ -205,15 +202,14 @@ impl ToTokens for Response { } } -#[derive(Debug)] pub enum ResponseField { - Body(Field), - Header(Field), - NewtypeBody(Field), + Body(FieldValue), + Header(FieldValue), + NewtypeBody(FieldValue), } impl ResponseField { - fn field(&self) -> &Field { + fn field(&self) -> &FieldValue { match *self { ResponseField::Body(ref field) => field, ResponseField::Header(ref field) => field, From 4c46df9a5929d02fe7148e824db87e0d9569a94a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 18:50:22 -0700 Subject: [PATCH 046/107] Update strip_serde_attrs, uncomment code. --- src/api/mod.rs | 446 ++++++++++++++++++++++++------------------------- 1 file changed, 222 insertions(+), 224 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 35937030..9bb6f125 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use quote::{ToTokens, Tokens}; use syn::punctuated::Pair; use syn::synom::Synom; -use syn::{Expr, Field, Ident, Meta}; +use syn::{Expr, FieldValue, Ident, Meta}; mod metadata; mod request; @@ -12,26 +12,24 @@ use self::metadata::Metadata; use self::request::Request; use self::response::Response; -// pub fn strip_serde_attrs(field: &Field) -> Field { -// let mut field = field.clone(); +pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { + let mut field_value = field_value.clone(); -// field.attrs = field.attrs.into_iter().filter(|attr| { -// let (attr_ident, _) = match attr.value { -// Meta::List(ref attr_ident, _) => { -// (attr_ident, ()) -// } -// _ => return true, -// }; + field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + let meta = attr.interpret_meta() + .expect("ruma_api! could not parse field attributes"); -// if attr_ident != "serde" { -// return true; -// } + let Meta::List(meta_list) = meta; -// false -// }).collect(); + if meta_list.ident.as_ref() != "serde" { + return true; + } -// field -// } + false + }).collect(); + + field_value +} pub struct Api { metadata: Metadata, @@ -104,266 +102,266 @@ impl Synom for Exprs { }) )); } -// impl ToTokens for Api { -// fn to_tokens(&self, tokens: &mut Tokens) { -// let description = &self.metadata.description; -// let method = &self.metadata.method; -// let name = &self.metadata.name; -// let path = &self.metadata.path; -// let rate_limited = &self.metadata.rate_limited; -// let requires_authentication = &self.metadata.requires_authentication; -// let request_types = { -// let mut tokens = Tokens::new(); -// self.request.to_tokens(&mut tokens); -// tokens -// }; -// let response_types = { -// let mut tokens = Tokens::new(); -// self.response.to_tokens(&mut tokens); -// tokens -// }; +impl ToTokens for Api { + fn to_tokens(&self, tokens: &mut Tokens) { + let description = &self.metadata.description; + let method = &self.metadata.method; + let name = &self.metadata.name; + let path = &self.metadata.path; + let rate_limited = &self.metadata.rate_limited; + let requires_authentication = &self.metadata.requires_authentication; -// let set_request_path = if self.request.has_path_fields() { -// let path_str_quoted = path.as_str(); -// assert!( -// path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), -// "path needs to be a string literal" -// ); + let request_types = { + let mut tokens = Tokens::new(); + self.request.to_tokens(&mut tokens); + tokens + }; + let response_types = { + let mut tokens = Tokens::new(); + self.response.to_tokens(&mut tokens); + tokens + }; -// let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + let set_request_path = if self.request.has_path_fields() { + let path_str_quoted = path.as_str(); + assert!( + path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), + "path needs to be a string literal" + ); -// assert!(path_str.starts_with('/'), "path needs to start with '/'"); -// assert!( -// path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), -// "number of declared path parameters needs to match amount of placeholders in path" -// ); + let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; -// let request_path_init_fields = self.request.request_path_init_fields(); + assert!(path_str.starts_with('/'), "path needs to start with '/'"); + assert!( + path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), + "number of declared path parameters needs to match amount of placeholders in path" + ); -// let mut tokens = quote! { -// let request_path = RequestPath { -// #request_path_init_fields -// }; + let request_path_init_fields = self.request.request_path_init_fields(); -// // This `unwrap()` can only fail when the url is a -// // cannot-be-base url like `mailto:` or `data:`, which is not -// // the case for our placeholder url. -// let mut path_segments = url.path_segments_mut().unwrap(); -// }; + let mut tokens = quote! { + let request_path = RequestPath { + #request_path_init_fields + }; -// for segment in path_str[1..].split('/') { -// tokens.append(quote! { -// path_segments.push -// }); + // This `unwrap()` can only fail when the url is a + // cannot-be-base url like `mailto:` or `data:`, which is not + // the case for our placeholder url. + let mut path_segments = url.path_segments_mut().unwrap(); + }; -// tokens.append("("); + for segment in path_str[1..].split('/') { + tokens.append(quote! { + path_segments.push + }); -// if segment.starts_with(':') { -// tokens.append("&request_path."); -// tokens.append(&segment[1..]); -// tokens.append(".to_string()"); -// } else { -// tokens.append("\""); -// tokens.append(segment); -// tokens.append("\""); -// } + tokens.append("("); -// tokens.append(");"); -// } + if segment.starts_with(':') { + tokens.append("&request_path."); + tokens.append(&segment[1..]); + tokens.append(".to_string()"); + } else { + tokens.append("\""); + tokens.append(segment); + tokens.append("\""); + } -// tokens -// } else { -// quote! { -// url.set_path(metadata.path); -// } -// }; + tokens.append(");"); + } -// let set_request_query = if self.request.has_query_fields() { -// let request_query_init_fields = self.request.request_query_init_fields(); + tokens + } else { + quote! { + url.set_path(metadata.path); + } + }; -// quote! { -// let request_query = RequestQuery { -// #request_query_init_fields -// }; + let set_request_query = if self.request.has_query_fields() { + let request_query_init_fields = self.request.request_query_init_fields(); -// url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); -// } -// } else { -// Tokens::new() -// }; + quote! { + let request_query = RequestQuery { + #request_query_init_fields + }; -// let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { -// let field_name = field.ident.as_ref().expect("expected body field to have a name"); + url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); + } + } else { + Tokens::new() + }; -// quote! { -// let request_body = RequestBody(request.#field_name); + let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { + let field_name = field.ident.as_ref().expect("expected body field to have a name"); -// hyper_request.set_body(::serde_json::to_vec(&request_body)?); -// } -// } else if self.request.has_body_fields() { -// let request_body_init_fields = self.request.request_body_init_fields(); + quote! { + let request_body = RequestBody(request.#field_name); -// quote! { -// let request_body = RequestBody { -// #request_body_init_fields -// }; + hyper_request.set_body(::serde_json::to_vec(&request_body)?); + } + } else if self.request.has_body_fields() { + let request_body_init_fields = self.request.request_body_init_fields(); -// hyper_request.set_body(::serde_json::to_vec(&request_body)?); -// } -// } else { -// Tokens::new() -// }; + quote! { + let request_body = RequestBody { + #request_body_init_fields + }; -// let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { -// let field_type = &field.ty; -// let mut tokens = Tokens::new(); + hyper_request.set_body(::serde_json::to_vec(&request_body)?); + } + } else { + Tokens::new() + }; -// tokens.append(quote! { -// let future_response = hyper_response.body() -// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { -// bytes.write_all(&chunk)?; + let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { + let field_type = &field.ty; + let mut tokens = Tokens::new(); -// Ok(bytes) -// }) -// .map_err(::ruma_api::Error::from) -// .and_then(|bytes| { -// ::serde_json::from_slice::<#field_type>(bytes.as_slice()) -// .map_err(::ruma_api::Error::from) -// }) -// }); + tokens.append(quote! { + let future_response = hyper_response.body() + .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { + bytes.write_all(&chunk)?; -// tokens.append(".and_then(move |response_body| {"); + Ok(bytes) + }) + .map_err(::ruma_api::Error::from) + .and_then(|bytes| { + ::serde_json::from_slice::<#field_type>(bytes.as_slice()) + .map_err(::ruma_api::Error::from) + }) + }); -// tokens -// } else if self.response.has_body_fields() { -// let mut tokens = Tokens::new(); + tokens.append(".and_then(move |response_body| {"); -// tokens.append(quote! { -// let future_response = hyper_response.body() -// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { -// bytes.write_all(&chunk)?; + tokens + } else if self.response.has_body_fields() { + let mut tokens = Tokens::new(); -// Ok(bytes) -// }) -// .map_err(::ruma_api::Error::from) -// .and_then(|bytes| { -// ::serde_json::from_slice::(bytes.as_slice()) -// .map_err(::ruma_api::Error::from) -// }) -// }); + tokens.append(quote! { + let future_response = hyper_response.body() + .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { + bytes.write_all(&chunk)?; -// tokens.append(".and_then(move |response_body| {"); + Ok(bytes) + }) + .map_err(::ruma_api::Error::from) + .and_then(|bytes| { + ::serde_json::from_slice::(bytes.as_slice()) + .map_err(::ruma_api::Error::from) + }) + }); -// tokens -// } else { -// let mut tokens = Tokens::new(); + tokens.append(".and_then(move |response_body| {"); -// tokens.append(quote! { -// let future_response = ::futures::future::ok(()) -// }); + tokens + } else { + let mut tokens = Tokens::new(); -// tokens.append(".and_then(move |_| {"); + tokens.append(quote! { + let future_response = ::futures::future::ok(()) + }); -// tokens -// }; + tokens.append(".and_then(move |_| {"); -// let mut closure_end = Tokens::new(); -// closure_end.append("});"); + tokens + }; -// let extract_headers = if self.response.has_header_fields() { -// quote! { -// let mut headers = hyper_response.headers().clone(); -// } -// } else { -// Tokens::new() -// }; + let mut closure_end = Tokens::new(); + closure_end.append("});"); -// let response_init_fields = if self.response.has_fields() { -// self.response.init_fields() -// } else { -// Tokens::new() -// }; + let extract_headers = if self.response.has_header_fields() { + quote! { + let mut headers = hyper_response.headers().clone(); + } + } else { + Tokens::new() + }; -// tokens.append(quote! { -// #[allow(unused_imports)] -// use std::io::Write as _Write; + let response_init_fields = if self.response.has_fields() { + self.response.init_fields() + } else { + Tokens::new() + }; -// #[allow(unused_imports)] -// use ::futures::{Future as _Future, Stream as _Stream}; -// use ::ruma_api::Endpoint as _RumaApiEndpoint; + tokens.append(quote! { + #[allow(unused_imports)] + use std::io::Write as _Write; -// /// The API endpoint. -// #[derive(Debug)] -// pub struct Endpoint; + #[allow(unused_imports)] + use ::futures::{Future as _Future, Stream as _Stream}; + use ::ruma_api::Endpoint as _RumaApiEndpoint; -// #request_types + /// The API endpoint. + #[derive(Debug)] + pub struct Endpoint; -// impl ::std::convert::TryFrom for ::hyper::Request { -// type Error = ::ruma_api::Error; + #request_types -// #[allow(unused_mut, unused_variables)] -// fn try_from(request: Request) -> Result { -// let metadata = Endpoint::METADATA; + impl ::std::convert::TryFrom for ::hyper::Request { + type Error = ::ruma_api::Error; -// // Use dummy homeserver url which has to be overwritten in -// // the calling code. Previously (with hyper::Uri) this was -// // not required, but Url::parse only accepts absolute urls. -// let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); + #[allow(unused_mut, unused_variables)] + fn try_from(request: Request) -> Result { + let metadata = Endpoint::METADATA; -// { #set_request_path } -// { #set_request_query } + // Use dummy homeserver url which has to be overwritten in + // the calling code. Previously (with hyper::Uri) this was + // not required, but Url::parse only accepts absolute urls. + let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); -// let mut hyper_request = ::hyper::Request::new( -// metadata.method, -// // Every valid URL is a valid URI -// url.into_string().parse().unwrap(), -// ); + { #set_request_path } + { #set_request_query } -// { #add_body_to_request } + let mut hyper_request = ::hyper::Request::new( + metadata.method, + // Every valid URL is a valid URI + url.into_string().parse().unwrap(), + ); -// Ok(hyper_request) -// } -// } + { #add_body_to_request } -// #response_types + Ok(hyper_request) + } + } -// impl ::futures::future::FutureFrom<::hyper::Response> for Response { -// type Future = Box<_Future>; -// type Error = ::ruma_api::Error; + #response_types -// #[allow(unused_variables)] -// fn future_from(hyper_response: ::hyper::Response) -// -> Box<_Future> { -// #extract_headers + impl ::futures::future::FutureFrom<::hyper::Response> for Response { + type Future = Box<_Future>; + type Error = ::ruma_api::Error; -// #deserialize_response_body + #[allow(unused_variables)] + fn future_from(hyper_response: ::hyper::Response) + -> Box<_Future> { + #extract_headers -// let response = Response { -// #response_init_fields -// }; + #deserialize_response_body -// Ok(response) -// #closure_end + let response = Response { + #response_init_fields + }; -// Box::new(future_response) -// } -// } + Ok(response) + #closure_end -// impl ::ruma_api::Endpoint for Endpoint { -// type Request = Request; -// type Response = Response; + Box::new(future_response) + } + } -// const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { -// description: #description, -// method: ::hyper::#method, -// name: #name, -// path: #path, -// rate_limited: #rate_limited, -// requires_authentication: #requires_authentication, -// }; -// } -// }); -// } -// } + impl ::ruma_api::Endpoint for Endpoint { + type Request = Request; + type Response = Response; + const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { + description: #description, + method: ::hyper::#method, + name: #name, + path: #path, + rate_limited: #rate_limited, + requires_authentication: #requires_authentication, + }; + } + }); + } +} From 2a84e038c428169e827e0c2fbbd685749ece74a8 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 18:52:09 -0700 Subject: [PATCH 047/107] Remove parse module. --- src/api/mod.rs | 1 - src/lib.rs | 7 +-- src/parse.rs | 144 ------------------------------------------------- 3 files changed, 2 insertions(+), 150 deletions(-) delete mode 100644 src/parse.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 9bb6f125..30372e7d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -7,7 +7,6 @@ mod metadata; mod request; mod response; -// use parse::Entry; use self::metadata::Metadata; use self::request::Request; use self::response::Response; diff --git a/src/lib.rs b/src/lib.rs index 2c4341f7..56c37691 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,6 @@ use quote::{ToTokens, Tokens}; use api::{Api, Exprs}; mod api; -// mod parse; /// Generates a `ruma_api::Endpoint` from a concise definition. /// @@ -197,10 +196,8 @@ mod api; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { let exprs: Exprs = syn::parse(input).expect("ruma_api! failed to parse input"); - let api = match Api::try_from(exprs.inner) { - Ok(api) => api, - Err(error) => panic!("{}", error), - }; + + let api = Api::from(exprs.inner); let mut tokens = Tokens::new(); diff --git a/src/parse.rs b/src/parse.rs deleted file mode 100644 index 483efb87..00000000 --- a/src/parse.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Implementation details of parsing proc macro input. - -use syn::{ - Attribute, - AttrStyle, - Expr, - Field, - Ident, - Meta, - NestedMeta, - Visibility, -}; -// use syn::parse::{expr, ident, lit, ty}; -// use synom::space::{block_comment, whitespace}; - -#[derive(Debug)] -pub enum Entry { - Metadata(Vec<(Ident, Expr)>), - Request(Vec), - Response(Vec), -} - -named!(pub parse_entries -> Vec, do_parse!( - entries: many0!(entry) >> - (entries) -)); - -named!(entry -> Entry, alt!( - do_parse!( - block_type: syn!(Ident) >> - cond_reduce!(block_type == "metadata") >> - brace_and_fields: braces!(many0!(struct_init_field)) >> - (Entry::Metadata(brace_and_fields.1)) - ) - | - do_parse!( - block_type: syn!(Ident) >> - cond_reduce!(block_type == "request") >> - brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> - (Entry::Request(brace_and_fields.1)) - ) - | - do_parse!( - block_type: syn!(Ident) >> - cond_reduce!(block_type == "response") >> - brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> - (Entry::Response(brace_and_fields.1)) - ) -)); - -// Everything below copy/pasted from syn 0.11.11. - -named!(struct_init_field -> (Ident, Expr), do_parse!( - ident: ident >> - punct!(:) >> - expr: expr >> - punct!(,) >> - (ident, expr) -)); - -named!(struct_field -> Field, do_parse!( - attrs: many0!(outer_attr) >> - visibility >> - id: ident >> - punct!(:) >> - ty: ty >> - (Field { - ident: Some(id), - vis: Visibility::Public, // Ignore declared visibility, always make fields public - attrs: attrs, - ty: ty, - }) -)); - -named!(outer_attr -> Attribute, alt!( - do_parse!( - punct!(#) >> - brackets_and_meta_item: brackets!(meta_item) >> - (Attribute { - style: AttrStyle::Outer, - value: brackets_and_meta_item.1, - is_sugared_doc: false, - }) - ) - | - do_parse!( - punct!(/) >> - punct!(/) >> - punct!(/) >> - not!(tag!("/")) >> - content: take_until!("\n") >> - (Attribute { - style: AttrStyle::Outer, - value: Meta::NameValue( - "doc".into(), - format!("///{}", content).into(), - ), - is_sugared_doc: true, - }) - ) - | - do_parse!( - option!(whitespace) >> - peek!(tuple!(tag!("/**"), not!(tag!("*")))) >> - com: block_comment >> - (Attribute { - style: AttrStyle::Outer, - value: Meta::NameValue( - "doc".into(), - com.into(), - ), - is_sugared_doc: true, - }) - ) -)); - -named!(meta_item -> Meta, alt!( - do_parse!( - id: ident >> - parens_and_inner: parens!(terminated_list!(punct!(,), nested_meta_item)) >> - (Meta::List(id, parens_and_inner.1)) - ) - | - do_parse!( - name: ident >> - punct!(=) >> - value: lit >> - (Meta::NameValue(name, value)) - ) - | - map!(ident, Meta::Word) -)); - -named!(nested_meta_item -> NestedMeta, alt!( - meta_item => { NestedMeta::Meta } - | - lit => { NestedMeta::Literal } -)); - -named!(visibility -> Visibility, alt!( - keyword!(pub) => { |_| Visibility::Public } - | - epsilon!() => { |_| Visibility::Inherited } -)); From 5e9a3be5d44af00f1eebe2cabbf49f3c667150f3 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 19:31:42 -0700 Subject: [PATCH 048/107] Simplify code with ToTokens::into_tokens. --- src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 56c37691..b6e440e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,9 +199,5 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { let api = Api::from(exprs.inner); - let mut tokens = Tokens::new(); - - api.to_tokens(&mut tokens); - - tokens.into() + api.into_tokens().into() } From dfaf1c7da1e9f9c6133c28275a7b6d3aa7f7d2b3 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 19:39:48 -0700 Subject: [PATCH 049/107] Rearrange some code. --- src/api/mod.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 30372e7d..8a4967e6 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -89,19 +89,6 @@ impl From> for Api { } } -pub struct Exprs { - pub inner: Vec, -} - -impl Synom for Exprs { - named!(parse -> Self, do_parse!( - exprs: many0!(syn!(Expr)) >> - (Exprs { - inner: exprs, - }) - )); -} - impl ToTokens for Api { fn to_tokens(&self, tokens: &mut Tokens) { let description = &self.metadata.description; @@ -364,3 +351,16 @@ impl ToTokens for Api { }); } } + +pub struct Exprs { + pub inner: Vec, +} + +impl Synom for Exprs { + named!(parse -> Self, do_parse!( + exprs: many0!(syn!(Expr)) >> + (Exprs { + inner: exprs, + }) + )); +} From 09e377d68e80068405d17d2b63bf71473d9869ca Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 20:13:34 -0700 Subject: [PATCH 050/107] Extract relevant types out of the metadata's fields. --- src/api/metadata.rs | 57 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 6c4ab254..68290d0c 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,14 +1,15 @@ use quote::{ToTokens, Tokens}; +use syn::punctuated::Pair; use syn::synom::Synom; -use syn::{Expr, ExprStruct, Ident, Member}; +use syn::{Expr, ExprStruct, Ident, Lit, Member}; pub struct Metadata { - pub description: Expr, - pub method: Expr, - pub name: Expr, - pub path: Expr, - pub rate_limited: Expr, - pub requires_authentication: Expr, + pub description: String, + pub method: String, + pub name: String, + pub path: String, + pub rate_limited: bool, + pub requires_authentication: bool, } impl From for Metadata { @@ -24,12 +25,42 @@ impl From for Metadata { let Member::Named(identifier) = field.member; match identifier.as_ref() { - "description" => description = Some(field.expr), - "method" => method = Some(field.expr), - "name" => name = Some(field.expr), - "path" => path = Some(field.expr), - "rate_limited" => rate_limited = Some(field.expr), - "requires_authentication" => requires_authentication = Some(field.expr), + "description" => { + let Expr::Lit(expr_lit) = field.expr; + let Lit::Str(lit_str) = expr_lit.lit; + description = Some(lit_str.value()); + } + "method" => { + let Expr::Path(expr_path) = field.expr; + let path = expr_path.path; + let segments = path.segments; + if segments.len() != 1 { + panic!("ruma_api! expects a one component path for `metadata` `method`"); + } + let pair = segments.first().unwrap(); // safe because we just checked + let Pair::End(method_name) = pair; + method = Some(method_name.ident.to_string()); + } + "name" => { + let Expr::Lit(expr_lit) = field.expr; + let Lit::Str(lit_str) = expr_lit.lit; + name = Some(lit_str.value()); + } + "path" => { + let Expr::Lit(expr_lit) = field.expr; + let Lit::Str(lit_str) = expr_lit.lit; + path = Some(lit_str.value()); + } + "rate_limited" => { + let Expr::Lit(expr_lit) = field.expr; + let Lit::Bool(lit_bool) = expr_lit.lit; + rate_limited = Some(lit_bool.value) + } + "requires_authentication" => { + let Expr::Lit(expr_lit) = field.expr; + let Lit::Bool(lit_bool) = expr_lit.lit; + requires_authentication = Some(lit_bool.value) + } _ => panic!("ruma_api! metadata included unexpected field"), } } From 8f6bc5af77007251988bcadbf91e2bee5f5129bd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 11 May 2018 08:50:39 -0700 Subject: [PATCH 051/107] Fix remaining compiler errors. --- src/api/metadata.rs | 65 ++++++++++++++++++++++++++++++++--------- src/api/mod.rs | 62 ++++++++++++++++++++++----------------- src/api/request.rs | 61 +++++++++++++++++++-------------------- src/api/response.rs | 70 ++++++++++++++++++++++++--------------------- 4 files changed, 156 insertions(+), 102 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 68290d0c..a2847c54 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -22,43 +22,82 @@ impl From for Metadata { let mut requires_authentication = None; for field in expr.fields { - let Member::Named(identifier) = field.member; + let identifier = match field.member { + Member::Named(identifier) => identifier, + _ => panic!("expected Member::Named"), + }; match identifier.as_ref() { "description" => { - let Expr::Lit(expr_lit) = field.expr; - let Lit::Str(lit_str) = expr_lit.lit; + let expr_lit = match field.expr { + Expr::Lit(expr_lit) => expr_lit, + _ => panic!("expected Expr::Lit"), + }; + let lit_str = match expr_lit.lit { + Lit::Str(lit_str) => lit_str, + _ => panic!("expected Lit::Str"), + }; description = Some(lit_str.value()); } "method" => { - let Expr::Path(expr_path) = field.expr; + let expr_path = match field.expr { + Expr::Path(expr_path) => expr_path, + _ => panic!("expected Expr::Path"), + }; let path = expr_path.path; let segments = path.segments; if segments.len() != 1 { panic!("ruma_api! expects a one component path for `metadata` `method`"); } let pair = segments.first().unwrap(); // safe because we just checked - let Pair::End(method_name) = pair; + let method_name = match pair { + Pair::End(method_name) => method_name, + _ => panic!("expected Pair::End"), + }; method = Some(method_name.ident.to_string()); } "name" => { - let Expr::Lit(expr_lit) = field.expr; - let Lit::Str(lit_str) = expr_lit.lit; + let expr_lit = match field.expr { + Expr::Lit(expr_lit) => expr_lit, + _ => panic!("expected Expr::Lit"), + }; + let lit_str = match expr_lit.lit { + Lit::Str(lit_str) => lit_str, + _ => panic!("expected Lit::Str"), + }; name = Some(lit_str.value()); } "path" => { - let Expr::Lit(expr_lit) = field.expr; - let Lit::Str(lit_str) = expr_lit.lit; + let expr_lit = match field.expr { + Expr::Lit(expr_lit) => expr_lit, + _ => panic!("expected Expr::Lit"), + }; + let lit_str = match expr_lit.lit { + Lit::Str(lit_str) => lit_str, + _ => panic!("expected Lit::Str"), + }; path = Some(lit_str.value()); } "rate_limited" => { - let Expr::Lit(expr_lit) = field.expr; - let Lit::Bool(lit_bool) = expr_lit.lit; + let expr_lit = match field.expr { + Expr::Lit(expr_lit) => expr_lit, + _ => panic!("expected Expr::Lit"), + }; + let lit_bool = match expr_lit.lit { + Lit::Bool(lit_bool) => lit_bool, + _ => panic!("expected Lit::Bool"), + }; rate_limited = Some(lit_bool.value) } "requires_authentication" => { - let Expr::Lit(expr_lit) = field.expr; - let Lit::Bool(lit_bool) = expr_lit.lit; + let expr_lit = match field.expr { + Expr::Lit(expr_lit) => expr_lit, + _ => panic!("expected Expr::Lit"), + }; + let lit_bool = match expr_lit.lit { + Lit::Bool(lit_bool) => lit_bool, + _ => panic!("expected Lit::Bool"), + }; requires_authentication = Some(lit_bool.value) } _ => panic!("ruma_api! metadata included unexpected field"), diff --git a/src/api/mod.rs b/src/api/mod.rs index 8a4967e6..1f72ef0d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use quote::{ToTokens, Tokens}; use syn::punctuated::Pair; use syn::synom::Synom; -use syn::{Expr, FieldValue, Ident, Meta}; +use syn::{Expr, FieldValue, Ident, Member, Meta}; mod metadata; mod request; @@ -18,7 +18,10 @@ pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { let meta = attr.interpret_meta() .expect("ruma_api! could not parse field attributes"); - let Meta::List(meta_list) = meta; + let meta_list = match meta { + Meta::List(meta_list) => meta_list, + _ => panic!("expected Meta::List"), + }; if meta_list.ident.as_ref() != "serde" { return true; @@ -52,13 +55,16 @@ impl From> for Api { _ => panic!("ruma_api! blocks should use struct syntax"), }; - let segments = expr.path.segments; + let segments = expr.path.segments.clone(); if segments.len() != 1 { panic!("ruma_api! blocks must be one of: metadata, request, or response"); } - let Pair::End(last_segment) = segments.last().unwrap(); + let last_segment = match segments.last().unwrap() { + Pair::End(last_segment) => last_segment, + _ => panic!("expected Pair::End"), + }; match last_segment.ident.as_ref() { "metadata" => metadata = Some(expr.into()), @@ -138,23 +144,21 @@ impl ToTokens for Api { }; for segment in path_str[1..].split('/') { - tokens.append(quote! { + tokens.append_all(quote! { path_segments.push }); - tokens.append("("); - if segment.starts_with(':') { - tokens.append("&request_path."); - tokens.append(&segment[1..]); - tokens.append(".to_string()"); - } else { - tokens.append("\""); - tokens.append(segment); - tokens.append("\""); - } + let what_is_this = &segment[1..]; - tokens.append(");"); + tokens.append_all(quote! { + (&request_path.#what_is_this.to_string()); + }); + } else { + tokens.append_all(quote! { + ("#segment"); + }); + } } tokens @@ -179,7 +183,10 @@ impl ToTokens for Api { }; let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected body field to have a name"); + let field_name = match field.member { + Member::Named(field_name) => field_name, + _ => panic!("expected Member::Named"), + }; quote! { let request_body = RequestBody(request.#field_name); @@ -201,10 +208,13 @@ impl ToTokens for Api { }; let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { - let field_type = &field.ty; + let field_type = match field.expr { + Expr::Path(ref field_type) => field_type, + _ => panic!("expected Expr::Path"), + }; let mut tokens = Tokens::new(); - tokens.append(quote! { + tokens.append_all(quote! { let future_response = hyper_response.body() .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { bytes.write_all(&chunk)?; @@ -218,13 +228,13 @@ impl ToTokens for Api { }) }); - tokens.append(".and_then(move |response_body| {"); + tokens.append_all(".and_then(move |response_body| {".into_tokens()); tokens } else if self.response.has_body_fields() { let mut tokens = Tokens::new(); - tokens.append(quote! { + tokens.append_all(quote! { let future_response = hyper_response.body() .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { bytes.write_all(&chunk)?; @@ -238,23 +248,23 @@ impl ToTokens for Api { }) }); - tokens.append(".and_then(move |response_body| {"); + tokens.append_all(".and_then(move |response_body| {".into_tokens()); tokens } else { let mut tokens = Tokens::new(); - tokens.append(quote! { + tokens.append_all(quote! { let future_response = ::futures::future::ok(()) }); - tokens.append(".and_then(move |_| {"); + tokens.append_all(".and_then(move |_| {".into_tokens()); tokens }; let mut closure_end = Tokens::new(); - closure_end.append("});"); + closure_end.append_all("});".into_tokens()); let extract_headers = if self.response.has_header_fields() { quote! { @@ -270,7 +280,7 @@ impl ToTokens for Api { Tokens::new() }; - tokens.append(quote! { + tokens.append_all(quote! { #[allow(unused_imports)] use std::io::Write as _Write; diff --git a/src/api/request.rs b/src/api/request.rs index b2ce32ea..b6f5b0cd 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,6 @@ use quote::{ToTokens, Tokens}; use syn::synom::Synom; -use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Meta, NestedMeta}; +use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -54,9 +54,12 @@ impl Request { let mut tokens = Tokens::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = field.ident.as_ref().expect("expected body field to have a name"); + let field_name = match field.member { + Member::Named(field_name) => field_name, + _ => panic!("expected Member::Named"), + }; - tokens.append(quote! { + tokens.append_all(quote! { #field_name: request.#field_name, }); } @@ -76,7 +79,10 @@ impl From for Request { let meta = attr.interpret_meta() .expect("ruma_api! could not parse request field attributes"); - let Meta::List(meta_list) = meta; + let meta_list = match meta { + Meta::List(meta_list) => meta_list, + _ => panic!("expected Meta::List"), + }; if meta_list.ident.as_ref() != "ruma_api" { return true; @@ -132,109 +138,102 @@ impl From for Request { impl ToTokens for Request { fn to_tokens(&self, mut tokens: &mut Tokens) { - tokens.append(quote! { + tokens.append_all(quote! { /// Data for a request to this API endpoint. #[derive(Debug)] pub struct Request }); if self.fields.len() == 0 { - tokens.append(";"); + tokens.append_all(";".into_tokens()); } else { - tokens.append("{"); + tokens.append_all("{".into_tokens()); for request_field in self.fields.iter() { strip_serde_attrs(request_field.field()).to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); + let expr = field.expr; - field.ident = None; - - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the request body. #[derive(Debug, Serialize)] - struct RequestBody + struct RequestBody(#expr); }); - - tokens.append("("); - - field.to_tokens(&mut tokens); - - tokens.append(");"); } else if self.has_body_fields() { - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the request body. #[derive(Debug, Serialize)] struct RequestBody }); - tokens.append("{"); + tokens.append_all("{".into_tokens()); for request_field in self.fields.iter() { match *request_field { RequestField::Body(ref field) => { field.to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } _ => {} } } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } if self.has_path_fields() { - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the request path. #[derive(Debug, Serialize)] struct RequestPath }); - tokens.append("{"); + tokens.append_all("{".into_tokens()); for request_field in self.fields.iter() { match *request_field { RequestField::Path(ref field) => { field.to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } _ => {} } } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } if self.has_query_fields() { - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the request's query string. #[derive(Debug, Serialize)] struct RequestQuery }); - tokens.append("{"); + tokens.append_all("{".into_tokens()); for request_field in self.fields.iter() { match *request_field { RequestField::Query(ref field) => { field.to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } _ => {} } } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } } } diff --git a/src/api/response.rs b/src/api/response.rs index 79ddfcb4..b7b7c514 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Meta, NestedMeta}; +use syn::{Expr, ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -26,28 +26,38 @@ impl Response { for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - let field_name = field.ident.as_ref() - .expect("expected body field to have a name"); + let field_name = match field.member { + Member::Named(field_name) => field_name, + _ => panic!("expected Member::Named"), + }; - tokens.append(quote! { + tokens.append_all(quote! { #field_name: response_body.#field_name, }); } ResponseField::Header(ref field) => { - let field_name = field.ident.as_ref() - .expect("expected body field to have a name"); - let field_type = &field.ty; + let field_name = match field.member { + Member::Named(field_name) => field_name, + _ => panic!("expected Member::Named"), + }; - tokens.append(quote! { + let field_type = match field.expr { + Expr::Path(ref field_type) => field_type, + _ => panic!("expected Expr::Path"), + }; + + tokens.append_all(quote! { #field_name: headers.remove::<#field_type>() .expect("missing expected request header"), }); } ResponseField::NewtypeBody(ref field) => { - let field_name = field.ident.as_ref() - .expect("expected body field to have a name"); + let field_name = match field.member { + Member::Named(field_name) => field_name, + _ => panic!("expected Member::Named"), + }; - tokens.append(quote! { + tokens.append_all(quote! { #field_name: response_body, }); } @@ -57,7 +67,7 @@ impl Response { tokens } - pub fn newtype_body_field(&self) -> Option<&Field> { + pub fn newtype_body_field(&self) -> Option<&FieldValue> { for response_field in self.fields.iter() { match *response_field { ResponseField::NewtypeBody(ref field) => { @@ -84,7 +94,10 @@ impl From for Response { let meta = attr.interpret_meta() .expect("ruma_api! could not parse response field attributes"); - let Meta::List(meta_list) = meta; + let meta_list = match meta { + Meta::List(meta_list) => meta_list, + _ => panic!("expected Meta::List"), + }; if meta_list.ident.as_ref() != "ruma_api" { return true; @@ -141,63 +154,56 @@ impl From for Response { impl ToTokens for Response { fn to_tokens(&self, mut tokens: &mut Tokens) { - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the response from this API endpoint. #[derive(Debug)] pub struct Response }); if self.fields.len() == 0 { - tokens.append(";"); + tokens.append_all(";".into_tokens()); } else { - tokens.append("{"); + tokens.append_all("{".into_tokens()); for response_field in self.fields.iter() { strip_serde_attrs(response_field.field()).to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); + let expr = field.expr; - field.ident = None; - - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the response body. #[derive(Debug, Deserialize)] - struct ResponseBody + struct ResponseBody(#expr); }); - - tokens.append("("); - - field.to_tokens(&mut tokens); - - tokens.append(");"); } else if self.has_body_fields() { - tokens.append(quote! { + tokens.append_all(quote! { /// Data in the response body. #[derive(Debug, Deserialize)] struct ResponseBody }); - tokens.append("{"); + tokens.append_all("{".into_tokens()); for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { field.to_tokens(&mut tokens); - tokens.append(","); + tokens.append_all(",".into_tokens()); } _ => {} } } - tokens.append("}"); + tokens.append_all("}".into_tokens()); } } } From 38746660b61bdb3c61519ed7cf2de14caeb7fcdd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 12 May 2018 23:56:23 -0700 Subject: [PATCH 052/107] Use a custom parser for the raw input. --- src/api/metadata.rs | 24 +++++---- src/api/mod.rs | 103 ++++++++++++--------------------------- src/api/request.rs | 40 +++++++-------- src/api/response.rs | 51 ++++++++----------- src/lib.rs | 12 ++--- tests/ruma_api_macros.rs | 3 +- 6 files changed, 86 insertions(+), 147 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index a2847c54..c167829d 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,7 +1,5 @@ -use quote::{ToTokens, Tokens}; use syn::punctuated::Pair; -use syn::synom::Synom; -use syn::{Expr, ExprStruct, Ident, Lit, Member}; +use syn::{Expr, FieldValue, Lit, Member}; pub struct Metadata { pub description: String, @@ -12,8 +10,8 @@ pub struct Metadata { pub requires_authentication: bool, } -impl From for Metadata { - fn from(expr: ExprStruct) -> Self { +impl From> for Metadata { + fn from(field_values: Vec) -> Self { let mut description = None; let mut method = None; let mut name = None; @@ -21,15 +19,15 @@ impl From for Metadata { let mut rate_limited = None; let mut requires_authentication = None; - for field in expr.fields { - let identifier = match field.member { + for field_value in field_values { + let identifier = match field_value.member { Member::Named(identifier) => identifier, _ => panic!("expected Member::Named"), }; match identifier.as_ref() { "description" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -40,7 +38,7 @@ impl From for Metadata { description = Some(lit_str.value()); } "method" => { - let expr_path = match field.expr { + let expr_path = match field_value.expr { Expr::Path(expr_path) => expr_path, _ => panic!("expected Expr::Path"), }; @@ -57,7 +55,7 @@ impl From for Metadata { method = Some(method_name.ident.to_string()); } "name" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -68,7 +66,7 @@ impl From for Metadata { name = Some(lit_str.value()); } "path" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -79,7 +77,7 @@ impl From for Metadata { path = Some(lit_str.value()); } "rate_limited" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -90,7 +88,7 @@ impl From for Metadata { rate_limited = Some(lit_bool.value) } "requires_authentication" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; diff --git a/src/api/mod.rs b/src/api/mod.rs index 1f72ef0d..a5238f9c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use quote::{ToTokens, Tokens}; -use syn::punctuated::Pair; +use syn::punctuated::Punctuated; use syn::synom::Synom; -use syn::{Expr, FieldValue, Ident, Member, Meta}; +use syn::{Field, FieldValue, Meta}; mod metadata; mod request; @@ -11,10 +11,10 @@ use self::metadata::Metadata; use self::request::Request; use self::response::Response; -pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { - let mut field_value = field_value.clone(); +pub fn strip_serde_attrs(field: &Field) -> Field { + let mut field = field.clone(); - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse field attributes"); @@ -30,7 +30,7 @@ pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { false }).collect(); - field_value + field } pub struct Api { @@ -39,59 +39,13 @@ pub struct Api { response: Response, } -impl From> for Api { - fn from(exprs: Vec) -> Self { - if exprs.len() != 3 { - panic!("ruma_api! expects 3 blocks: metadata, request, and response"); - } - - let mut metadata = None; - let mut request = None; - let mut response = None; - - for expr in exprs { - let expr = match expr { - Expr::Struct(expr) => expr, - _ => panic!("ruma_api! blocks should use struct syntax"), - }; - - let segments = expr.path.segments.clone(); - - if segments.len() != 1 { - panic!("ruma_api! blocks must be one of: metadata, request, or response"); - } - - let last_segment = match segments.last().unwrap() { - Pair::End(last_segment) => last_segment, - _ => panic!("expected Pair::End"), - }; - - match last_segment.ident.as_ref() { - "metadata" => metadata = Some(expr.into()), - "request" => request = Some(expr.into()), - "response" => response = Some(expr.into()), - _ => panic!("ruma_api! blocks must be one of: metadata, request, or response"), - } - } - - if metadata.is_none() { - panic!("ruma_api! is missing metadata"); - } - - if request.is_none() { - panic!("ruma_api! is missing request"); - } - - if response.is_none() { - panic!("ruma_api! is missing response"); - } - +impl From for Api { + fn from(raw_api: RawApi) -> Self { Api { - metadata: metadata.unwrap(), - request: request.unwrap(), - response: response.unwrap(), + metadata: raw_api.metadata.into(), + request: raw_api.request.into(), + response: raw_api.response.into(), } - } } @@ -183,10 +137,7 @@ impl ToTokens for Api { }; let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); @@ -208,10 +159,8 @@ impl ToTokens for Api { }; let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { - let field_type = match field.expr { - Expr::Path(ref field_type) => field_type, - _ => panic!("expected Expr::Path"), - }; + let field_type = &field.ty; + let mut tokens = Tokens::new(); tokens.append_all(quote! { @@ -362,15 +311,27 @@ impl ToTokens for Api { } } -pub struct Exprs { - pub inner: Vec, +type ParseMetadata = Punctuated; +type ParseFields = Punctuated; + +pub struct RawApi { + pub metadata: Vec, + pub request: Vec, + pub response: Vec, } -impl Synom for Exprs { +impl Synom for RawApi { named!(parse -> Self, do_parse!( - exprs: many0!(syn!(Expr)) >> - (Exprs { - inner: exprs, + custom_keyword!(metadata) >> + metadata: braces!(ParseMetadata::parse_terminated) >> + custom_keyword!(request) >> + request: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> + custom_keyword!(response) >> + response: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> + (RawApi { + metadata: metadata.1.into_iter().collect(), + request: request.1.into_iter().collect(), + response: response.1.into_iter().collect(), }) )); } diff --git a/src/api/request.rs b/src/api/request.rs index b6f5b0cd..4abdd02f 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::synom::Synom; -use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -25,7 +24,7 @@ impl Request { self.fields.iter().filter(|field| field.is_path()).count() } - pub fn newtype_body_field(&self) -> Option<&FieldValue> { + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { RequestField::NewtypeBody(ref field) => { @@ -54,10 +53,7 @@ impl Request { let mut tokens = Tokens::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: request.#field_name, @@ -68,14 +64,14 @@ impl Request { } } -impl From for Request { - fn from(expr: ExprStruct) -> Self { +impl From> for Request { + fn from(fields: Vec) -> Self { let mut has_newtype_body = false; - let fields = expr.fields.into_iter().map(|mut field_value| { + let fields = fields.into_iter().map(|mut field| { let mut field_kind = RequestFieldKind::Body; - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse request field attributes"); @@ -127,7 +123,7 @@ impl From for Request { ); } - RequestField::new(field_kind, field_value) + RequestField::new(field_kind, field) }).collect(); Request { @@ -160,12 +156,12 @@ impl ToTokens for Request { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let expr = field.expr; + let ty = field.ty; tokens.append_all(quote! { /// Data in the request body. #[derive(Debug, Serialize)] - struct RequestBody(#expr); + struct RequestBody(#ty); }); } else if self.has_body_fields() { tokens.append_all(quote! { @@ -239,15 +235,15 @@ impl ToTokens for Request { } pub enum RequestField { - Body(FieldValue), - Header(FieldValue), - NewtypeBody(FieldValue), - Path(FieldValue), - Query(FieldValue), + Body(Field), + Header(Field), + NewtypeBody(Field), + Path(Field), + Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: FieldValue) -> RequestField { + fn new(kind: RequestFieldKind, field: Field) -> RequestField { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => RequestField::Header(field), @@ -279,7 +275,7 @@ impl RequestField { self.kind() == RequestFieldKind::Query } - fn field(&self) -> &FieldValue { + fn field(&self) -> &Field { match *self { RequestField::Body(ref field) => field, RequestField::Header(ref field) => field, @@ -289,7 +285,7 @@ impl RequestField { } } - fn field_(&self, kind: RequestFieldKind) -> Option<&FieldValue> { + fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { if self.kind() == kind { Some(self.field()) } else { diff --git a/src/api/response.rs b/src/api/response.rs index b7b7c514..7246e4e1 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Expr, ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -26,25 +26,15 @@ impl Response { for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: response_body.#field_name, }); } ResponseField::Header(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; - - let field_type = match field.expr { - Expr::Path(ref field_type) => field_type, - _ => panic!("expected Expr::Path"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); + let field_type = &field.ty; tokens.append_all(quote! { #field_name: headers.remove::<#field_type>() @@ -52,10 +42,7 @@ impl Response { }); } ResponseField::NewtypeBody(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: response_body, @@ -67,7 +54,7 @@ impl Response { tokens } - pub fn newtype_body_field(&self) -> Option<&FieldValue> { + pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { ResponseField::NewtypeBody(ref field) => { @@ -83,14 +70,14 @@ impl Response { } -impl From for Response { - fn from(expr: ExprStruct) -> Self { +impl From> for Response { + fn from(fields: Vec) -> Self { let mut has_newtype_body = false; - let fields = expr.fields.into_iter().map(|mut field_value| { + let fields = fields.into_iter().map(|mut field| { let mut field_kind = ResponseFieldKind::Body; - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse response field attributes"); @@ -138,11 +125,11 @@ impl From for Response { if has_newtype_body { panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); } else { - return ResponseField::Body(field_value); + return ResponseField::Body(field); } } - ResponseFieldKind::Header => ResponseField::Header(field_value), - ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field_value), + ResponseFieldKind::Header => ResponseField::Header(field), + ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } }).collect(); @@ -176,12 +163,12 @@ impl ToTokens for Response { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let expr = field.expr; + let ty = field.ty; tokens.append_all(quote! { /// Data in the response body. #[derive(Debug, Deserialize)] - struct ResponseBody(#expr); + struct ResponseBody(#ty); }); } else if self.has_body_fields() { tokens.append_all(quote! { @@ -209,13 +196,13 @@ impl ToTokens for Response { } pub enum ResponseField { - Body(FieldValue), - Header(FieldValue), - NewtypeBody(FieldValue), + Body(Field), + Header(Field), + NewtypeBody(Field), } impl ResponseField { - fn field(&self) -> &FieldValue { + fn field(&self) -> &Field { match *self { ResponseField::Body(ref field) => field, ResponseField::Header(ref field) => field, diff --git a/src/lib.rs b/src/lib.rs index b6e440e7..05ba19cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,8 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro, try_from)] +#![feature(proc_macro)] #![recursion_limit="256"] -#![allow(warnings)] extern crate proc_macro; #[macro_use] extern crate quote; @@ -14,11 +13,10 @@ extern crate ruma_api; #[macro_use] extern crate syn; use proc_macro::TokenStream; -use std::convert::TryFrom; -use quote::{ToTokens, Tokens}; +use quote::ToTokens; -use api::{Api, Exprs}; +use api::{Api, RawApi}; mod api; @@ -195,9 +193,9 @@ mod api; /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let exprs: Exprs = syn::parse(input).expect("ruma_api! failed to parse input"); + let raw_api: RawApi = syn::parse(input).expect("ruma_api! failed to parse input"); - let api = Api::from(exprs.inner); + let api = Api::from(raw_api); api.into_tokens().into() } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index cc93423e..5c066220 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,7 +1,6 @@ #![feature(associated_consts, proc_macro, try_from)] extern crate futures; -extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; @@ -17,7 +16,7 @@ pub mod some_endpoint { ruma_api! { metadata { description: "Does something.", - method: Method::Get, // A `hyper::Method` value. No need to import the name. + method: GET, // An `http::Method` constant. No imports required. name: "some_endpoint", path: "/_matrix/some/endpoint/:baz", rate_limited: false, From c86cdb29b3591e8a0b56864feef6a43fbfd46a5b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 13 May 2018 00:09:39 -0700 Subject: [PATCH 053/107] Fix another bug and use a more useful variable name for named path segments. --- src/api/mod.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index a5238f9c..1089c044 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -70,13 +70,7 @@ impl ToTokens for Api { }; let set_request_path = if self.request.has_path_fields() { - let path_str_quoted = path.as_str(); - assert!( - path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), - "path needs to be a string literal" - ); - - let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + let path_str = path.as_str(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); assert!( @@ -103,10 +97,10 @@ impl ToTokens for Api { }); if segment.starts_with(':') { - let what_is_this = &segment[1..]; + let path_var = &segment[1..]; tokens.append_all(quote! { - (&request_path.#what_is_this.to_string()); + (&request_path.#path_var.to_string()); }); } else { tokens.append_all(quote! { From f6b6c946759381f4addc413c5870c734f1a0b93b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 13 May 2018 00:19:37 -0700 Subject: [PATCH 054/107] Preserve span information for fields. --- src/api/request.rs | 27 ++++++++++++++++++++------- src/api/response.rs | 28 ++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index 4abdd02f..dd1093a6 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,4 +1,5 @@ use quote::{ToTokens, Tokens}; +use syn::spanned::Spanned; use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -54,8 +55,9 @@ impl Request { for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { let field_name = field.ident.expect("expected field to have an identifier"); + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> #field_name: request.#field_name, }); } @@ -146,7 +148,12 @@ impl ToTokens for Request { tokens.append_all("{".into_tokens()); for request_field in self.fields.iter() { - strip_serde_attrs(request_field.field()).to_tokens(&mut tokens); + let field = request_field.field(); + let span = field.span(); + + strip_serde_attrs(field).to_tokens(&mut tokens); + + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } @@ -156,9 +163,10 @@ impl ToTokens for Request { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let ty = field.ty; + let ty = &field.ty; + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> /// Data in the request body. #[derive(Debug, Serialize)] struct RequestBody(#ty); @@ -175,7 +183,8 @@ impl ToTokens for Request { for request_field in self.fields.iter() { match *request_field { RequestField::Body(ref field) => { - field.to_tokens(&mut tokens); + let span = field.span(); + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } @@ -198,7 +207,9 @@ impl ToTokens for Request { for request_field in self.fields.iter() { match *request_field { RequestField::Path(ref field) => { - field.to_tokens(&mut tokens); + let span = field.span(); + + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } @@ -221,7 +232,9 @@ impl ToTokens for Request { for request_field in self.fields.iter() { match *request_field { RequestField::Query(ref field) => { - field.to_tokens(&mut tokens); + let span = field.span(); + + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } diff --git a/src/api/response.rs b/src/api/response.rs index 7246e4e1..bd6048cc 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,4 +1,5 @@ use quote::{ToTokens, Tokens}; +use syn::spanned::Spanned; use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -27,24 +28,27 @@ impl Response { match *response_field { ResponseField::Body(ref field) => { let field_name = field.ident.expect("expected field to have an identifier"); + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> #field_name: response_body.#field_name, }); } ResponseField::Header(ref field) => { let field_name = field.ident.expect("expected field to have an identifier"); let field_type = &field.ty; + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> #field_name: headers.remove::<#field_type>() .expect("missing expected request header"), }); } ResponseField::NewtypeBody(ref field) => { let field_name = field.ident.expect("expected field to have an identifier"); + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> #field_name: response_body, }); } @@ -140,7 +144,7 @@ impl From> for Response { } impl ToTokens for Response { - fn to_tokens(&self, mut tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut Tokens) { tokens.append_all(quote! { /// Data in the response from this API endpoint. #[derive(Debug)] @@ -153,7 +157,12 @@ impl ToTokens for Response { tokens.append_all("{".into_tokens()); for response_field in self.fields.iter() { - strip_serde_attrs(response_field.field()).to_tokens(&mut tokens); + let field = response_field.field(); + let span = field.span(); + + strip_serde_attrs(field); + + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } @@ -163,9 +172,10 @@ impl ToTokens for Response { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let ty = field.ty; + let ty = &field.ty; + let span = field.span(); - tokens.append_all(quote! { + tokens.append_all(quote_spanned! {span=> /// Data in the response body. #[derive(Debug, Deserialize)] struct ResponseBody(#ty); @@ -182,7 +192,9 @@ impl ToTokens for Response { for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - field.to_tokens(&mut tokens); + let span = field.span(); + + tokens.append_all(quote_spanned!(span=> #field)); tokens.append_all(",".into_tokens()); } From 5bc253b3247501bcaa96b0e61803a25bc7a8306d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 13 May 2018 01:09:45 -0700 Subject: [PATCH 055/107] Rewrite request and response ToTokens to avoid calls to append_all with string literals. --- src/api/request.rs | 135 +++++++++++++++++++++++++------------------- src/api/response.rs | 64 ++++++++++++--------- 2 files changed, 113 insertions(+), 86 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index dd1093a6..bd90860b 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -135,115 +135,132 @@ impl From> for Request { } impl ToTokens for Request { - fn to_tokens(&self, mut tokens: &mut Tokens) { - tokens.append_all(quote! { + fn to_tokens(&self, tokens: &mut Tokens) { + let request_struct_header = quote! { /// Data for a request to this API endpoint. #[derive(Debug)] pub struct Request - }); + }; - if self.fields.len() == 0 { - tokens.append_all(";".into_tokens()); + let request_struct_body = if self.fields.len() == 0 { + quote!(;) } else { - tokens.append_all("{".into_tokens()); - - for request_field in self.fields.iter() { + let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { let field = request_field.field(); let span = field.span(); - strip_serde_attrs(field).to_tokens(&mut tokens); + strip_serde_attrs(field); - tokens.append_all(quote_spanned!(span=> #field)); + field_tokens.append_all(quote_spanned!(span=> #field,)); - tokens.append_all(",".into_tokens()); + field_tokens + }); + + quote! { + { + #fields + } } + }; - tokens.append_all("}".into_tokens()); - } + let request_body_struct; if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); - tokens.append_all(quote_spanned! {span=> + request_body_struct = quote_spanned! {span=> /// Data in the request body. #[derive(Debug, Serialize)] struct RequestBody(#ty); - }); + }; } else if self.has_body_fields() { - tokens.append_all(quote! { - /// Data in the request body. - #[derive(Debug, Serialize)] - struct RequestBody - }); - - tokens.append_all("{".into_tokens()); - - for request_field in self.fields.iter() { + let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Body(ref field) => { let span = field.span(); - tokens.append_all(quote_spanned!(span=> #field)); - tokens.append_all(",".into_tokens()); + field_tokens.append_all(quote_spanned!(span=> #field,)); + + field_tokens } - _ => {} + _ => field_tokens, } - } - - tokens.append_all("}".into_tokens()); - } - - if self.has_path_fields() { - tokens.append_all(quote! { - /// Data in the request path. - #[derive(Debug, Serialize)] - struct RequestPath }); - tokens.append_all("{".into_tokens()); + request_body_struct = quote! { + /// Data in the request body. + #[derive(Debug, Serialize)] + struct RequestBody { + #fields + } + }; + } else { + request_body_struct = Tokens::new(); + } - for request_field in self.fields.iter() { + let request_path_struct; + + if self.has_path_fields() { + let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Path(ref field) => { let span = field.span(); - tokens.append_all(quote_spanned!(span=> #field)); + field_tokens.append_all(quote_spanned!(span=> #field,)); - tokens.append_all(",".into_tokens()); + field_tokens } - _ => {} + _ => field_tokens, } - } - - tokens.append_all("}".into_tokens()); - } - - if self.has_query_fields() { - tokens.append_all(quote! { - /// Data in the request's query string. - #[derive(Debug, Serialize)] - struct RequestQuery }); - tokens.append_all("{".into_tokens()); + request_path_struct = quote! { + /// Data in the request path. + #[derive(Debug, Serialize)] + struct RequestPath { + #fields + } + }; + } else { + request_path_struct = Tokens::new(); + } - for request_field in self.fields.iter() { + let request_query_struct; + + if self.has_query_fields() { + let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Query(ref field) => { let span = field.span(); - tokens.append_all(quote_spanned!(span=> #field)); + field_tokens.append_all(quote_spanned!(span=> #field)); - tokens.append_all(",".into_tokens()); + field_tokens } - _ => {} + _ => field_tokens, } - } + }); - tokens.append_all("}".into_tokens()); + request_query_struct = quote! { + /// Data in the request's query string. + #[derive(Debug, Serialize)] + struct RequestQuery { + #fields + } + }; + } else { + request_query_struct = Tokens::new(); } + + tokens.append_all(quote! { + #request_struct_header + #request_struct_body + #request_body_struct + #request_path_struct + #request_query_struct + }); } } diff --git a/src/api/response.rs b/src/api/response.rs index bd6048cc..1b0105fd 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -145,65 +145,75 @@ impl From> for Response { impl ToTokens for Response { fn to_tokens(&self, tokens: &mut Tokens) { - tokens.append_all(quote! { + let response_struct_header = quote! { /// Data in the response from this API endpoint. #[derive(Debug)] pub struct Response - }); + }; - if self.fields.len() == 0 { - tokens.append_all(";".into_tokens()); + let response_struct_body = if self.fields.len() == 0 { + quote!(;) } else { - tokens.append_all("{".into_tokens()); - - for response_field in self.fields.iter() { + let fields = self.fields.iter().fold(Tokens::new(), |mut fields_tokens, response_field| { let field = response_field.field(); let span = field.span(); strip_serde_attrs(field); - tokens.append_all(quote_spanned!(span=> #field)); + fields_tokens.append_all(quote_spanned!(span=> #field,)); - tokens.append_all(",".into_tokens()); + fields_tokens + }); + + quote! { + { + #fields + } } + }; - tokens.append_all("}".into_tokens()); - } + let response_body_struct; if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); - tokens.append_all(quote_spanned! {span=> + response_body_struct = quote_spanned! {span=> /// Data in the response body. #[derive(Debug, Deserialize)] struct ResponseBody(#ty); - }); + }; } else if self.has_body_fields() { - tokens.append_all(quote! { - /// Data in the response body. - #[derive(Debug, Deserialize)] - struct ResponseBody - }); - - tokens.append_all("{".into_tokens()); - - for response_field in self.fields.iter() { + let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, response_field| { match *response_field { ResponseField::Body(ref field) => { let span = field.span(); - tokens.append_all(quote_spanned!(span=> #field)); + field_tokens.append_all(quote_spanned!(span=> #field,)); - tokens.append_all(",".into_tokens()); + field_tokens } - _ => {} + _ => field_tokens, } - } + }); - tokens.append_all("}".into_tokens()); + response_body_struct = quote! { + /// Data in the response body. + #[derive(Debug, Deserialize)] + struct ResponseBody { + #fields + } + }; + } else { + response_body_struct = Tokens::new(); } + + tokens.append_all(quote! { + #response_struct_header + #response_struct_body + #response_body_struct + }); } } From 7b1e22eea4df08a7fbdde823f22b9dbde36e487f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 15 May 2018 01:32:19 -0700 Subject: [PATCH 056/107] Rewrite Api's ToTokens impl to avoid calls to append_all with string literals. --- Cargo.toml | 1 + src/api/mod.rs | 92 ++++++++++++++-------------------------- tests/ruma_api_macros.rs | 10 ++--- 3 files changed, 39 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index debf9c7f..8df2dd1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ features = ["nightly"] [dev-dependencies] futures = "0.1.21" +http = "0.1.5" serde = "1.0.45" serde_derive = "1.0.45" serde_json = "1.0.17" diff --git a/src/api/mod.rs b/src/api/mod.rs index 1089c044..dc355895 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use quote::{ToTokens, Tokens}; use syn::punctuated::Punctuated; use syn::synom::Synom; -use syn::{Field, FieldValue, Meta}; +use syn::{Field, FieldValue, Ident, Meta}; mod metadata; mod request; @@ -52,22 +52,16 @@ impl From for Api { impl ToTokens for Api { fn to_tokens(&self, tokens: &mut Tokens) { let description = &self.metadata.description; - let method = &self.metadata.method; + let method = Ident::from(self.metadata.method.as_ref()); let name = &self.metadata.name; let path = &self.metadata.path; let rate_limited = &self.metadata.rate_limited; let requires_authentication = &self.metadata.requires_authentication; - let request_types = { - let mut tokens = Tokens::new(); - self.request.to_tokens(&mut tokens); - tokens - }; - let response_types = { - let mut tokens = Tokens::new(); - self.response.to_tokens(&mut tokens); - tokens - }; + let request = &self.request; + let request_types = quote! { #request }; + let response = &self.response; + let response_types = quote! { #response }; let set_request_path = if self.request.has_path_fields() { let path_str = path.as_str(); @@ -98,9 +92,10 @@ impl ToTokens for Api { if segment.starts_with(':') { let path_var = &segment[1..]; + let path_var_ident = Ident::from(path_var); tokens.append_all(quote! { - (&request_path.#path_var.to_string()); + (&request_path.#path_var_ident.to_string()); }); } else { tokens.append_all(quote! { @@ -136,7 +131,7 @@ impl ToTokens for Api { quote! { let request_body = RequestBody(request.#field_name); - hyper_request.set_body(::serde_json::to_vec(&request_body)?); + http_request.set_body(::serde_json::to_vec(&request_body)?); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -146,7 +141,7 @@ impl ToTokens for Api { #request_body_init_fields }; - hyper_request.set_body(::serde_json::to_vec(&request_body)?); + http_request.set_body(::serde_json::to_vec(&request_body)?); } } else { Tokens::new() @@ -155,10 +150,8 @@ impl ToTokens for Api { let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { let field_type = &field.ty; - let mut tokens = Tokens::new(); - - tokens.append_all(quote! { - let future_response = hyper_response.body() + quote! { + let future_response = http_response.body() .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { bytes.write_all(&chunk)?; @@ -169,16 +162,10 @@ impl ToTokens for Api { ::serde_json::from_slice::<#field_type>(bytes.as_slice()) .map_err(::ruma_api::Error::from) }) - }); - - tokens.append_all(".and_then(move |response_body| {".into_tokens()); - - tokens + } } else if self.response.has_body_fields() { - let mut tokens = Tokens::new(); - - tokens.append_all(quote! { - let future_response = hyper_response.body() + quote! { + let future_response = http_response.body() .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { bytes.write_all(&chunk)?; @@ -189,29 +176,16 @@ impl ToTokens for Api { ::serde_json::from_slice::(bytes.as_slice()) .map_err(::ruma_api::Error::from) }) - }); - - tokens.append_all(".and_then(move |response_body| {".into_tokens()); - - tokens + } } else { - let mut tokens = Tokens::new(); - - tokens.append_all(quote! { + quote! { let future_response = ::futures::future::ok(()) - }); - - tokens.append_all(".and_then(move |_| {".into_tokens()); - - tokens + } }; - let mut closure_end = Tokens::new(); - closure_end.append_all("});".into_tokens()); - let extract_headers = if self.response.has_header_fields() { quote! { - let mut headers = hyper_response.headers().clone(); + let mut headers = http_response.headers().clone(); } } else { Tokens::new() @@ -237,7 +211,7 @@ impl ToTokens for Api { #request_types - impl ::std::convert::TryFrom for ::hyper::Request { + impl ::std::convert::TryFrom for ::http::Request { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -245,44 +219,44 @@ impl ToTokens for Api { let metadata = Endpoint::METADATA; // Use dummy homeserver url which has to be overwritten in - // the calling code. Previously (with hyper::Uri) this was + // the calling code. Previously (with http::Uri) this was // not required, but Url::parse only accepts absolute urls. let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); { #set_request_path } { #set_request_query } - let mut hyper_request = ::hyper::Request::new( - metadata.method, + let mut http_request = ::http::Request::new( + ::http::Method::#method, // Every valid URL is a valid URI url.into_string().parse().unwrap(), ); { #add_body_to_request } - Ok(hyper_request) + Ok(http_request) } } #response_types - impl ::futures::future::FutureFrom<::hyper::Response> for Response { + impl ::futures::future::FutureFrom<::http::Response> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(hyper_response: ::hyper::Response) + fn future_from(http_response: ::http::Response) -> Box<_Future> { #extract_headers #deserialize_response_body + .and_then(move |response_body| { + let response = Response { + #response_init_fields + }; - let response = Response { - #response_init_fields - }; - - Ok(response) - #closure_end + Ok(response) + }); Box::new(future_response) } @@ -294,7 +268,7 @@ impl ToTokens for Api { const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { description: #description, - method: ::hyper::#method, + method: ::http::Method::#method, name: #name, path: #path, rate_limited: #rate_limited, diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 5c066220..9379ae5d 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,6 +1,7 @@ #![feature(associated_consts, proc_macro, try_from)] extern crate futures; +extern crate http; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; @@ -10,7 +11,6 @@ extern crate serde_urlencoded; extern crate url; pub mod some_endpoint { - use hyper::header::ContentType; use ruma_api_macros::ruma_api; ruma_api! { @@ -28,8 +28,8 @@ pub mod some_endpoint { pub foo: String, // This value will be put into the "Content-Type" HTTP header. - #[ruma_api(header)] - pub content_type: ContentType, + #[ruma_api(header = "CONTENT_TYPE")] + pub content_type: String, // This value will be put into the query string of the request's URL. #[ruma_api(query)] @@ -43,8 +43,8 @@ pub mod some_endpoint { response { // This value will be extracted from the "Content-Type" HTTP header. - #[ruma_api(header)] - pub content_type: ContentType, + #[ruma_api(header = "CONTENT_TYPE")] + pub content_type: String, // With no attribute on the field, it will be extracted from the body of the response. pub value: String, From a035c233bed21fa02ab13e8e59354ab35f914c31 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 15 May 2018 01:49:42 -0700 Subject: [PATCH 057/107] Remove remaining references to hyper and use new header style in docs. --- README.md | 14 +++++++------- src/lib.rs | 53 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5b16942d..20af7394 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Here is an example that shows most of the macro's functionality. #![feature(associated_consts, proc_macro, try_from)] extern crate futures; -extern crate hyper; +extern crate http; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; @@ -28,9 +28,9 @@ pub mod some_endpoint { ruma_api! { metadata { description: "Does something.", - method: Method::Get, // A `hyper::Method` value. No need to import the name. + method: GET, // An `http::Method` constant. No imports required. name: "some_endpoint", - path: "/_matrix/some/endpoint/:baz", + path: "/_matrix/some/endpoint/:baz", // Variable path components start with a colon. rate_limited: false, requires_authentication: false, } @@ -40,8 +40,8 @@ pub mod some_endpoint { pub foo: String, // This value will be put into the "Content-Type" HTTP header. - #[ruma_api(header)] - pub content_type: ContentType, + #[ruma_api(header = "CONTENT_TYPE")] + pub content_type: String // This value will be put into the query string of the request's URL. #[ruma_api(query)] @@ -55,8 +55,8 @@ pub mod some_endpoint { response { // This value will be extracted from the "Content-Type" HTTP header. - #[ruma_api(header)] - pub content_type: ContentType, + #[ruma_api(header = "CONTENT_TYPE")] + pub content_type: String // With no attribute on the field, it will be extracted from the body of the response. pub value: String, diff --git a/src/lib.rs b/src/lib.rs index 05ba19cc..bff2b4aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ mod api; /// ruma_api! { /// metadata { /// description: &'static str -/// method: hyper::Method, +/// method: http::Method, /// name: &'static str, /// path: &'static str, /// rate_limited: bool, @@ -50,8 +50,8 @@ mod api; /// /// This will generate a `ruma_api::Metadata` value to be used for the `ruma_api::Endpoint`'s /// associated constant, single `Request` and `Response` structs, and the necessary trait -/// implementations to convert the request into a `hyper::Request` and to create a response from a -/// `hyper::response`. +/// implementations to convert the request into a `http::Request` and to create a response from a +/// `http::Response`. /// /// The details of each of the three sections of the macros are documented below. /// @@ -59,8 +59,8 @@ mod api; /// /// * `description`: A short description of what the endpoint does. /// * `method`: The HTTP method used for requests to the endpoint. -/// It's not necessary to import `hyper::Method`, you just write the value as if it was -/// imported, e.g. `Method::Get`. +/// It's not necessary to import `http::Method`'s associated constants. Just write +/// the value as if it was imported, e.g. `GET`. /// * `name`: A unique name for the endpoint. /// Generally this will be the same as the containing module. /// * `path`: The path component of the URL for the endpoint, e.g. "/foo/bar". @@ -76,11 +76,14 @@ mod api; /// The request block contains normal struct field definitions. /// Doc comments and attributes are allowed as normal. /// There are also a few special attributes available to control how the struct is converted into a -/// `hyper::Request`: +/// `http::Request`: /// -/// * `#[ruma_api(header)]`: Fields with this attribute will be treated as HTTP headers on the -/// request. -/// The value must implement `hyper::header::Header`. +/// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP +/// headers on the request. +/// The value must implement `http::HttpTryFrom for http::header::HeaderValue`. +/// Generally this is a string. +/// The attribute value shown above as `HEADER_NAME` must be a header name constant from +/// `http::header`, e.g. `CONTENT_TYPE`. /// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path /// component of the request URL. /// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query @@ -94,11 +97,14 @@ mod api; /// Like the request block, the response block consists of normal struct field definitions. /// Doc comments and attributes are allowed as normal. /// There is also a special attribute available to control how the struct is created from a -/// `hyper::Request`: +/// `http::Request`: /// -/// * `#[ruma_api(header)]`: Fields with this attribute will be treated as HTTP headers on the -/// response. -/// The value must implement `hyper::header::Header`. +/// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP +/// headers on the response. +/// The value must implement `http::HttpTryFrom for http::header::HeaderValue`. +/// Generally this is a string. +/// The attribute value shown above as `HEADER_NAME` must be a header name constant from +/// `http::header`, e.g. `CONTENT_TYPE`. /// /// Any field that does not include the above attribute will be expected in the response's JSON /// body. @@ -115,10 +121,10 @@ mod api; /// # Examples /// /// ```rust,no_run -/// #![feature(associated_consts, proc_macro, try_from)] +/// #![feature(proc_macro, try_from)] /// /// extern crate futures; -/// extern crate hyper; +/// extern crate http; /// extern crate ruma_api; /// extern crate ruma_api_macros; /// extern crate serde; @@ -129,13 +135,12 @@ mod api; /// /// # fn main() { /// pub mod some_endpoint { -/// use hyper::header::ContentType; /// use ruma_api_macros::ruma_api; /// /// ruma_api! { /// metadata { /// description: "Does something.", -/// method: Method::Get, +/// method: GET, /// name: "some_endpoint", /// path: "/_matrix/some/endpoint/:baz", /// rate_limited: false, @@ -144,17 +149,21 @@ mod api; /// /// request { /// pub foo: String, -/// #[ruma_api(header)] -/// pub content_type: ContentType, +/// +/// #[ruma_api(header = "CONTENT_TYPE")] +/// pub content_type: String, +/// /// #[ruma_api(query)] /// pub bar: String, +/// /// #[ruma_api(path)] /// pub baz: String, /// } /// /// response { -/// #[ruma_api(header)] -/// pub content_type: ContentType, +/// #[ruma_api(header = "CONTENT_TYPE")] +/// pub content_type: String, +/// /// pub value: String, /// } /// } @@ -171,7 +180,7 @@ mod api; /// ruma_api! { /// metadata { /// description: "Does something.", -/// method: Method::Get, +/// method: GET, /// name: "newtype_body_endpoint", /// path: "/_matrix/some/newtype/body/endpoint", /// rate_limited: false, From f0f4f9bd17439f2ae4910b3f8b8c8b40edd93d7e Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 15 May 2018 22:57:06 -0700 Subject: [PATCH 058/107] Detect header attributes as name/value pairs. --- src/api/request.rs | 29 +++++++++++++++-------------- src/api/response.rs | 21 +++++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index bd90860b..2c9084fb 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -92,25 +92,26 @@ impl From> for Request { match meta_item { Meta::Word(ident) => { match ident.as_ref() { - "body" => { - has_newtype_body = true; - field_kind = RequestFieldKind::NewtypeBody; - } - "header" => field_kind = RequestFieldKind::Header, - "path" => field_kind = RequestFieldKind::Path, - "query" => field_kind = RequestFieldKind::Query, - _ => panic!( - "ruma_api! attribute meta item on requests must be: body, header, path, or query" - ), + "body" => { + has_newtype_body = true; + field_kind = RequestFieldKind::NewtypeBody; + } + "path" => field_kind = RequestFieldKind::Path, + "query" => field_kind = RequestFieldKind::Query, + _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), } } - _ => panic!( - "ruma_api! attribute meta item on requests cannot be a list or name/value pair" - ), + Meta::NameValue(name_value) => { + match name_value.ident.as_ref() { + "header" => field_kind = RequestFieldKind::Header, + _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), + } + } + _ => panic!("ruma_api! attributes on requests must be a single word or a name/value pair"), } } NestedMeta::Literal(_) => panic!( - "ruma_api! attribute meta item on requests must be: body, header, path, or query" + "ruma_api! attributes on requests must be: body, header, path, or query" ), } } diff --git a/src/api/response.rs b/src/api/response.rs index 1b0105fd..d4388851 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -101,18 +101,19 @@ impl From> for Response { Meta::Word(ident) => { match ident.as_ref() { "body" => { - has_newtype_body = true; - field_kind = ResponseFieldKind::NewtypeBody; - } - "header" => field_kind = ResponseFieldKind::Header, - _ => panic!( - "ruma_api! attribute meta item on responses must be: header" - ), + has_newtype_body = true; + field_kind = ResponseFieldKind::NewtypeBody; + } + _ => panic!("ruma_api! single-word attribute on responses must be: body"), } } - _ => panic!( - "ruma_api! attribute meta item on responses cannot be a list or name/value pair" - ), + Meta::NameValue(name_value) => { + match name_value.ident.as_ref() { + "header" => field_kind = ResponseFieldKind::Header, + _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), + } + } + _ => panic!("ruma_api! attributes on responses must be a single word or a name/value pair"), } } NestedMeta::Literal(_) => panic!( From c9454caff1ce86bf9ebab67f4a78887b1ae7d152 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 May 2018 00:40:51 -0700 Subject: [PATCH 059/107] Update request/response header logic for new style. --- README.md | 2 +- src/api/mod.rs | 22 ++++++++++++--- src/api/request.rs | 61 ++++++++++++++++++++++++++++++++-------- src/api/response.rs | 28 +++++++++++------- tests/ruma_api_macros.rs | 2 +- 5 files changed, 87 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 20af7394..494cdd9c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You define the endpoint's metadata, request fields, and response fields, and the Here is an example that shows most of the macro's functionality. ``` rust -#![feature(associated_consts, proc_macro, try_from)] +#![feature(proc_macro, try_from)] extern crate futures; extern crate http; diff --git a/src/api/mod.rs b/src/api/mod.rs index dc355895..e755ec54 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -99,7 +99,7 @@ impl ToTokens for Api { }); } else { tokens.append_all(quote! { - ("#segment"); + (#segment); }); } } @@ -125,6 +125,18 @@ impl ToTokens for Api { Tokens::new() }; + let add_headers_to_request = if self.request.has_header_fields() { + let mut header_tokens = quote! { + let headers = http_request.headers_mut(); + }; + + header_tokens.append_all(self.request.add_headers_to_request()); + + header_tokens + } else { + Tokens::new() + }; + let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { let field_name = field.ident.expect("expected field to have an identifier"); @@ -211,7 +223,7 @@ impl ToTokens for Api { #request_types - impl ::std::convert::TryFrom for ::http::Request { + impl ::std::convert::TryFrom for ::http::Request { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -232,6 +244,8 @@ impl ToTokens for Api { url.into_string().parse().unwrap(), ); + { #add_headers_to_request } + { #add_body_to_request } Ok(http_request) @@ -240,12 +254,12 @@ impl ToTokens for Api { #response_types - impl ::futures::future::FutureFrom<::http::Response> for Response { + impl ::futures::future::FutureFrom<::http::Response> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response) + fn future_from(http_response: ::http::Response) -> Box<_Future> { #extract_headers diff --git a/src/api/request.rs b/src/api/request.rs index 2c9084fb..505c6a14 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,6 @@ use quote::{ToTokens, Tokens}; use syn::spanned::Spanned; -use syn::{Field, Meta, NestedMeta}; +use syn::{Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -9,10 +9,31 @@ pub struct Request { } impl Request { + pub fn add_headers_to_request(&self) -> Tokens { + self.header_fields().fold(Tokens::new(), |mut header_tokens, request_field| { + let (field, header_name_string) = match request_field { + RequestField::Header(field, header_name_string) => (field, header_name_string), + _ => panic!("expected request field to be header variant"), + }; + + let field_name = &field.ident; + let header_name = Ident::from(header_name_string.as_ref()); + + header_tokens.append_all(quote! { + headers.append(::http::header::#header_name, request.#field_name); + }); + + header_tokens + }) + } + pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + pub fn has_header_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_header()) + } pub fn has_path_fields(&self) -> bool { self.fields.iter().any(|field| field.is_path()) } @@ -21,6 +42,10 @@ impl Request { self.fields.iter().any(|field| field.is_query()) } + pub fn header_fields(&self) -> impl Iterator { + self.fields.iter().filter(|field| field.is_header()) + } + pub fn path_field_count(&self) -> usize { self.fields.iter().filter(|field| field.is_path()).count() } @@ -72,6 +97,7 @@ impl From> for Request { let fields = fields.into_iter().map(|mut field| { let mut field_kind = RequestFieldKind::Body; + let mut header = None; field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() @@ -103,7 +129,14 @@ impl From> for Request { } Meta::NameValue(name_value) => { match name_value.ident.as_ref() { - "header" => field_kind = RequestFieldKind::Header, + "header" => { + match name_value.lit { + Lit::Str(lit_str) => header = Some(lit_str.value()), + _ => panic!("ruma_api! header attribute's value must be a string literal"), + } + + field_kind = RequestFieldKind::Header; + } _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), } } @@ -126,7 +159,7 @@ impl From> for Request { ); } - RequestField::new(field_kind, field) + RequestField::new(field_kind, field, header) }).collect(); Request { @@ -267,17 +300,17 @@ impl ToTokens for Request { pub enum RequestField { Body(Field), - Header(Field), + Header(Field, String), NewtypeBody(Field), Path(Field), Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: Field) -> RequestField { + fn new(kind: RequestFieldKind, field: Field, header: Option) -> RequestField { match kind { RequestFieldKind::Body => RequestField::Body(field), - RequestFieldKind::Header => RequestField::Header(field), + RequestFieldKind::Header => RequestField::Header(field, header.expect("missing header name")), RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), @@ -286,11 +319,11 @@ impl RequestField { fn kind(&self) -> RequestFieldKind { match *self { - RequestField::Body(_) => RequestFieldKind::Body, - RequestField::Header(_) => RequestFieldKind::Header, - RequestField::NewtypeBody(_) => RequestFieldKind::NewtypeBody, - RequestField::Path(_) => RequestFieldKind::Path, - RequestField::Query(_) => RequestFieldKind::Query, + RequestField::Body(..) => RequestFieldKind::Body, + RequestField::Header(..) => RequestFieldKind::Header, + RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, + RequestField::Path(..) => RequestFieldKind::Path, + RequestField::Query(..) => RequestFieldKind::Query, } } @@ -298,6 +331,10 @@ impl RequestField { self.kind() == RequestFieldKind::Body } + fn is_header(&self) -> bool { + self.kind() == RequestFieldKind::Header + } + fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path } @@ -309,7 +346,7 @@ impl RequestField { fn field(&self) -> &Field { match *self { RequestField::Body(ref field) => field, - RequestField::Header(ref field) => field, + RequestField::Header(ref field, _) => field, RequestField::NewtypeBody(ref field) => field, RequestField::Path(ref field) => field, RequestField::Query(ref field) => field, diff --git a/src/api/response.rs b/src/api/response.rs index d4388851..3f08327e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,6 +1,6 @@ use quote::{ToTokens, Tokens}; use syn::spanned::Spanned; -use syn::{Field, Meta, NestedMeta}; +use syn::{Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -34,13 +34,13 @@ impl Response { #field_name: response_body.#field_name, }); } - ResponseField::Header(ref field) => { + ResponseField::Header(ref field, ref header) => { let field_name = field.ident.expect("expected field to have an identifier"); - let field_type = &field.ty; + let header_name = Ident::from(header.as_ref()); let span = field.span(); tokens.append_all(quote_spanned! {span=> - #field_name: headers.remove::<#field_type>() + #field_name: headers.remove(::http::header::#header_name) .expect("missing expected request header"), }); } @@ -80,6 +80,7 @@ impl From> for Response { let fields = fields.into_iter().map(|mut field| { let mut field_kind = ResponseFieldKind::Body; + let mut header = None; field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() @@ -109,7 +110,14 @@ impl From> for Response { } Meta::NameValue(name_value) => { match name_value.ident.as_ref() { - "header" => field_kind = ResponseFieldKind::Header, + "header" => { + match name_value.lit { + Lit::Str(lit_str) => header = Some(lit_str.value()), + _ => panic!("ruma_api! header attribute's value must be a string literal"), + } + + field_kind = ResponseFieldKind::Header; + } _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), } } @@ -133,7 +141,7 @@ impl From> for Response { return ResponseField::Body(field); } } - ResponseFieldKind::Header => ResponseField::Header(field), + ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")), ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } }).collect(); @@ -220,7 +228,7 @@ impl ToTokens for Response { pub enum ResponseField { Body(Field), - Header(Field), + Header(Field, String), NewtypeBody(Field), } @@ -228,21 +236,21 @@ impl ResponseField { fn field(&self) -> &Field { match *self { ResponseField::Body(ref field) => field, - ResponseField::Header(ref field) => field, + ResponseField::Header(ref field, _) => field, ResponseField::NewtypeBody(ref field) => field, } } fn is_body(&self) -> bool { match *self { - ResponseField::Body(_) => true, + ResponseField::Body(..) => true, _ => false, } } fn is_header(&self) -> bool { match *self { - ResponseField::Header(_) => true, + ResponseField::Header(..) => true, _ => false, } } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 9379ae5d..43cd5472 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,4 +1,4 @@ -#![feature(associated_consts, proc_macro, try_from)] +#![feature(proc_macro, try_from)] extern crate futures; extern crate http; From a27adc2f7322be36a00e19cf82c68438506a2528 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 May 2018 01:11:37 -0700 Subject: [PATCH 060/107] Use Vec for request and response bodies. Use http's API for creating requests and responses. --- src/api/mod.rs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index e755ec54..808c6181 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -137,13 +137,13 @@ impl ToTokens for Api { Tokens::new() }; - let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { + let create_http_request = if let Some(field) = self.request.newtype_body_field() { let field_name = field.ident.expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); - http_request.set_body(::serde_json::to_vec(&request_body)?); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -153,10 +153,12 @@ impl ToTokens for Api { #request_body_init_fields }; - http_request.set_body(::serde_json::to_vec(&request_body)?); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); } } else { - Tokens::new() + quote! { + let mut http_request = ::http::Request::new(()); + } }; let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { @@ -223,7 +225,7 @@ impl ToTokens for Api { #request_types - impl ::std::convert::TryFrom for ::http::Request { + impl ::std::convert::TryFrom for ::http::Request> { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -238,28 +240,25 @@ impl ToTokens for Api { { #set_request_path } { #set_request_query } - let mut http_request = ::http::Request::new( - ::http::Method::#method, - // Every valid URL is a valid URI - url.into_string().parse().unwrap(), - ); + #create_http_request + + *http_request.method_mut() = ::http::Method::#method; + *http_request.uri_mut() = url.into_string().parse().unwrap(); { #add_headers_to_request } - { #add_body_to_request } - Ok(http_request) } } #response_types - impl ::futures::future::FutureFrom<::http::Response> for Response { + impl ::futures::future::FutureFrom<::http::Response>> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response) + fn future_from(http_response: ::http::Response>) -> Box<_Future> { #extract_headers @@ -276,7 +275,7 @@ impl ToTokens for Api { } } - impl ::ruma_api::Endpoint for Endpoint { + impl ::ruma_api::Endpoint, Vec> for Endpoint { type Request = Request; type Response = Response; From e3cf7a38a1eb35f4429da277b95d586877e96c2b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 May 2018 01:21:59 -0700 Subject: [PATCH 061/107] Remove code for building full bodies from streams. --- src/api/mod.rs | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 808c6181..d1d9b63f 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -165,31 +165,17 @@ impl ToTokens for Api { let field_type = &field.ty; quote! { - let future_response = http_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::<#field_type>(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) + let future_response = + ::serde_json::from_slice::<#field_type>(http_response.body().as_slice()) + .into_future() + .map_err(::ruma_api::Error::from) } } else if self.response.has_body_fields() { quote! { - let future_response = http_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) + let future_response = + ::serde_json::from_slice::(http_response.body().as_slice()) + .into_future() + .map_err(::ruma_api::Error::from) } } else { quote! { @@ -213,10 +199,7 @@ impl ToTokens for Api { tokens.append_all(quote! { #[allow(unused_imports)] - use std::io::Write as _Write; - - #[allow(unused_imports)] - use ::futures::{Future as _Future, Stream as _Stream}; + use ::futures::{Future as _Future, IntoFuture as _IntoFuture}; use ::ruma_api::Endpoint as _RumaApiEndpoint; /// The API endpoint. From ef32a2f9c1d815c40af8dea159518624476135e8 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 May 2018 01:42:53 -0700 Subject: [PATCH 062/107] Convert between HeaderValue and the declared type. --- src/api/request.rs | 6 +++++- src/api/response.rs | 5 ++++- src/lib.rs | 8 ++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index 505c6a14..3f90b093 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -20,7 +20,11 @@ impl Request { let header_name = Ident::from(header_name_string.as_ref()); header_tokens.append_all(quote! { - headers.append(::http::header::#header_name, request.#field_name); + headers.append( + ::http::header::#header_name, + ::http::header::HeaderValue::from_str(request.#field_name.as_ref()) + .expect("failed to convert value into HeaderValue"), + ); }); header_tokens diff --git a/src/api/response.rs b/src/api/response.rs index 3f08327e..12ac6c72 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -41,7 +41,10 @@ impl Response { tokens.append_all(quote_spanned! {span=> #field_name: headers.remove(::http::header::#header_name) - .expect("missing expected request header"), + .expect("response missing expected header") + .to_str() + .expect("failed to convert HeaderValue to str") + .to_owned(), }); } ResponseField::NewtypeBody(ref field) => { diff --git a/src/lib.rs b/src/lib.rs index bff2b4aa..6306380d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,8 +80,8 @@ mod api; /// /// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP /// headers on the request. -/// The value must implement `http::HttpTryFrom for http::header::HeaderValue`. -/// Generally this is a string. +/// The value must implement `AsRef`. +/// Generally this is a `String`. /// The attribute value shown above as `HEADER_NAME` must be a header name constant from /// `http::header`, e.g. `CONTENT_TYPE`. /// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path @@ -101,8 +101,8 @@ mod api; /// /// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP /// headers on the response. -/// The value must implement `http::HttpTryFrom for http::header::HeaderValue`. -/// Generally this is a string. +/// The value must implement `AsRef`. +/// Generally this is a `String`. /// The attribute value shown above as `HEADER_NAME` must be a header name constant from /// `http::header`, e.g. `CONTENT_TYPE`. /// From 29f2d2fd7f4e9eb7c753b99ed410c8b9fdc32dbc Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 16 May 2018 01:48:15 -0700 Subject: [PATCH 063/107] Bump version to 0.2.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8df2dd1b..55d7f5e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.1.0" +version = "0.2.0" [dependencies] quote = "0.5.2" From c59b43d0276b92027d97967ce59e58bdc7559902 Mon Sep 17 00:00:00 2001 From: Florian Jacob Date: Thu, 17 May 2018 14:58:23 +0200 Subject: [PATCH 064/107] Throw StatusCode error if http response is non-success to prevent a misleading deserialization error on error responses. --- src/api/mod.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index d1d9b63f..d67508fb 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -243,18 +243,22 @@ impl ToTokens for Api { #[allow(unused_variables)] fn future_from(http_response: ::http::Response>) -> Box<_Future> { - #extract_headers + if http_response.status().is_success() { + #extract_headers - #deserialize_response_body - .and_then(move |response_body| { - let response = Response { - #response_init_fields - }; + #deserialize_response_body + .and_then(move |response_body| { + let response = Response { + #response_init_fields + }; - Ok(response) - }); + Ok(response) + }); - Box::new(future_response) + Box::new(future_response) + } else { + Box::new(::futures::future::err(::ruma_api::Error::StatusCode(http_response.status().clone()))) + } } } From dcd259c1ca8f132bf6986d02da532d2f727c97a3 Mon Sep 17 00:00:00 2001 From: Florian Jacob Date: Fri, 18 May 2018 13:01:16 +0200 Subject: [PATCH 065/107] update proc_macro2 to fix build with current nightly --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 55d7f5e4..0668fcfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ version = "0.13.4" features = ["full"] [dependencies.proc-macro2] -version = "0.3.8" +version = "0.4.1" features = ["nightly"] [dev-dependencies] From 1678ee2cead1e66903d009b9724f945c5af331fe Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 01:13:07 -0700 Subject: [PATCH 066/107] Ignore attributes that aren't `Meta::List`s. --- src/api/mod.rs | 2 +- src/api/request.rs | 2 +- src/api/response.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index d67508fb..33d56e07 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -20,7 +20,7 @@ pub fn strip_serde_attrs(field: &Field) -> Field { let meta_list = match meta { Meta::List(meta_list) => meta_list, - _ => panic!("expected Meta::List"), + _ => return true, }; if meta_list.ident.as_ref() != "serde" { diff --git a/src/api/request.rs b/src/api/request.rs index 3f90b093..39ce8804 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -109,7 +109,7 @@ impl From> for Request { let meta_list = match meta { Meta::List(meta_list) => meta_list, - _ => panic!("expected Meta::List"), + _ => return true, }; if meta_list.ident.as_ref() != "ruma_api" { diff --git a/src/api/response.rs b/src/api/response.rs index 12ac6c72..9bd8bb9e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -91,7 +91,7 @@ impl From> for Response { let meta_list = match meta { Meta::List(meta_list) => meta_list, - _ => panic!("expected Meta::List"), + _ => return true, }; if meta_list.ident.as_ref() != "ruma_api" { From 621b73bd6fd36f122e75717f59f949adfba51812 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 01:20:47 -0700 Subject: [PATCH 067/107] Add missing commas after each query field. --- src/api/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/request.rs b/src/api/request.rs index 39ce8804..83b422be 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -273,7 +273,7 @@ impl ToTokens for Request { RequestField::Query(ref field) => { let span = field.span(); - field_tokens.append_all(quote_spanned!(span=> #field)); + field_tokens.append_all(quote_spanned!(span=> #field,)); field_tokens } From ff30a4381a965ed99d1aa93a503a459656e68bb6 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 01:52:13 -0700 Subject: [PATCH 068/107] Use fields stripped of serde attributes. --- src/api/mod.rs | 6 +++--- src/api/request.rs | 4 ++-- src/api/response.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 33d56e07..d07fd5bc 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -23,11 +23,11 @@ pub fn strip_serde_attrs(field: &Field) -> Field { _ => return true, }; - if meta_list.ident.as_ref() != "serde" { - return true; + if meta_list.ident.as_ref() == "serde" { + return false; } - false + true }).collect(); field diff --git a/src/api/request.rs b/src/api/request.rs index 83b422be..be33d5be 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -187,9 +187,9 @@ impl ToTokens for Request { let field = request_field.field(); let span = field.span(); - strip_serde_attrs(field); + let stripped_field = strip_serde_attrs(field); - field_tokens.append_all(quote_spanned!(span=> #field,)); + field_tokens.append_all(quote_spanned!(span=> #stripped_field,)); field_tokens }); diff --git a/src/api/response.rs b/src/api/response.rs index 9bd8bb9e..39658f1f 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -170,9 +170,9 @@ impl ToTokens for Response { let field = response_field.field(); let span = field.span(); - strip_serde_attrs(field); + let stripped_field = strip_serde_attrs(field); - fields_tokens.append_all(quote_spanned!(span=> #field,)); + fields_tokens.append_all(quote_spanned!(span=> #stripped_field,)); fields_tokens }); From 1bedd5af4eff8bfdb86e7946b68a98d341f07405 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 01:56:15 -0700 Subject: [PATCH 069/107] Request body must always be a Vec. --- src/api/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index d07fd5bc..c199c680 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -157,7 +157,7 @@ impl ToTokens for Api { } } else { quote! { - let mut http_request = ::http::Request::new(()); + let mut http_request = ::http::Request::new(Vec::with_capacity(0)); } }; From 527562c760c82bc77e218e6820a4dbef15bef817 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 02:06:16 -0700 Subject: [PATCH 070/107] Bump version to 0.2.1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0668fcfd..91ceeb44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.2.0" +version = "0.2.1" [dependencies] quote = "0.5.2" From f744e0813d6cdf0daf7ea9a25aff02f240aaa9e4 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 02:15:51 -0700 Subject: [PATCH 071/107] Upgrade dependencies. --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91ceeb44..741baa91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,20 +15,20 @@ quote = "0.5.2" ruma-api = "0.5.0" [dependencies.syn] -version = "0.13.4" +version = "0.13.10" features = ["full"] [dependencies.proc-macro2] -version = "0.4.1" +version = "0.4.2" features = ["nightly"] [dev-dependencies] futures = "0.1.21" http = "0.1.5" -serde = "1.0.45" -serde_derive = "1.0.45" +serde = "1.0.57" +serde_derive = "1.0.57" serde_json = "1.0.17" -serde_urlencoded = "0.5.1" +serde_urlencoded = "0.5.2" url = "1.7.0" [lib] From adf785ffc9764048e38b607f57f87bbd31c4129a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 19 May 2018 02:16:10 -0700 Subject: [PATCH 072/107] Bump version to 0.2.2. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 741baa91..de2b539d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.2.1" +version = "0.2.2" [dependencies] quote = "0.5.2" From 8703e515a960362872db00c3f97af9c35a7d86d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Sommer?= Date: Sat, 25 Aug 2018 16:06:28 +0200 Subject: [PATCH 073/107] Replace Vec by hyper::Body The `hyper::Request` and `Response` used in *ruma-client* require a type parameter that implements `hyper::body::Payload`, but no implementation for `Vec` is provided by a crate. Therefore, the best is to use `hyper::Body` in the macros. --- Cargo.toml | 1 + src/api/mod.rs | 46 ++++++++++++++++++++++++++-------------- src/lib.rs | 1 + tests/ruma_api_macros.rs | 1 + 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de2b539d..31ac6a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ features = ["nightly"] [dev-dependencies] futures = "0.1.21" http = "0.1.5" +hyper = "0.12" serde = "1.0.57" serde_derive = "1.0.57" serde_json = "1.0.17" diff --git a/src/api/mod.rs b/src/api/mod.rs index c199c680..e7c84d61 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -143,7 +143,7 @@ impl ToTokens for Api { quote! { let request_body = RequestBody(request.#field_name); - let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?.into()); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -153,11 +153,11 @@ impl ToTokens for Api { #request_body_init_fields }; - let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?.into()); } } else { quote! { - let mut http_request = ::http::Request::new(Vec::with_capacity(0)); + let mut http_request = ::http::Request::new(::hyper::Body::empty()); } }; @@ -165,17 +165,31 @@ impl ToTokens for Api { let field_type = &field.ty; quote! { - let future_response = - ::serde_json::from_slice::<#field_type>(http_response.body().as_slice()) - .into_future() - .map_err(::ruma_api::Error::from) + let future_response = http_response.into_body() + .fold(Vec::new(), |mut vec, chunk| { + vec.extend(chunk.iter()); + ::futures::future::ok::<_, ::hyper::Error>(vec) + }) + .map_err(::ruma_api::Error::from) + .and_then(|data| + ::serde_json::from_slice::<#field_type>(data.as_slice()) + .map_err(::ruma_api::Error::from) + .into_future() + ) } } else if self.response.has_body_fields() { quote! { - let future_response = - ::serde_json::from_slice::(http_response.body().as_slice()) - .into_future() - .map_err(::ruma_api::Error::from) + let future_response = http_response.into_body() + .fold(Vec::new(), |mut vec, chunk| { + vec.extend(chunk.iter()); + ::futures::future::ok::<_, ::hyper::Error>(vec) + }) + .map_err(::ruma_api::Error::from) + .and_then(|data| + ::serde_json::from_slice::(data.as_slice()) + .map_err(::ruma_api::Error::from) + .into_future() + ) } } else { quote! { @@ -199,7 +213,7 @@ impl ToTokens for Api { tokens.append_all(quote! { #[allow(unused_imports)] - use ::futures::{Future as _Future, IntoFuture as _IntoFuture}; + use ::futures::{Future as _Future, IntoFuture as _IntoFuture, Stream as _Stream}; use ::ruma_api::Endpoint as _RumaApiEndpoint; /// The API endpoint. @@ -208,7 +222,7 @@ impl ToTokens for Api { #request_types - impl ::std::convert::TryFrom for ::http::Request> { + impl ::std::convert::TryFrom for ::http::Request<::hyper::Body> { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -236,12 +250,12 @@ impl ToTokens for Api { #response_types - impl ::futures::future::FutureFrom<::http::Response>> for Response { + impl ::futures::future::FutureFrom<::http::Response<::hyper::Body>> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response>) + fn future_from(http_response: ::http::Response<::hyper::Body>) -> Box<_Future> { if http_response.status().is_success() { #extract_headers @@ -262,7 +276,7 @@ impl ToTokens for Api { } } - impl ::ruma_api::Endpoint, Vec> for Endpoint { + impl ::ruma_api::Endpoint for Endpoint { type Request = Request; type Response = Response; diff --git a/src/lib.rs b/src/lib.rs index 6306380d..4a90c685 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,7 @@ mod api; /// /// extern crate futures; /// extern crate http; +/// extern crate hyper; /// extern crate ruma_api; /// extern crate ruma_api_macros; /// extern crate serde; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 43cd5472..37295dbb 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -2,6 +2,7 @@ extern crate futures; extern crate http; +extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; From c91b9137fbaa84b81e90e5aa1844193e70b24c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Sommer?= Date: Sun, 26 Aug 2018 23:54:03 +0200 Subject: [PATCH 074/107] Update dependent crates quote, syn and others Cargo treats updates in the third position of the version number as compatible and updates them silently. Therefore, we can drop this number in the config. `Tokens` was moved from *quote* to *proc_macro2* and got renamed to `TokenStream`. --- Cargo.toml | 22 +++++++++++----------- src/api/metadata.rs | 2 +- src/api/mod.rs | 21 +++++++++++---------- src/api/request.rs | 43 ++++++++++++++++++++++--------------------- src/api/response.rs | 29 +++++++++++++++-------------- src/lib.rs | 3 ++- 6 files changed, 62 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31ac6a77..56cdf43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,26 +11,26 @@ repository = "https://github.com/ruma/ruma-api-macros" version = "0.2.2" [dependencies] -quote = "0.5.2" -ruma-api = "0.5.0" +quote = "0.6" +ruma-api = "0.5" [dependencies.syn] -version = "0.13.10" +version = "0.14" features = ["full"] [dependencies.proc-macro2] -version = "0.4.2" +version = "0.4" features = ["nightly"] [dev-dependencies] -futures = "0.1.21" -http = "0.1.5" +futures = "0.1" +http = "0.1" hyper = "0.12" -serde = "1.0.57" -serde_derive = "1.0.57" -serde_json = "1.0.17" -serde_urlencoded = "0.5.2" -url = "1.7.0" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +serde_urlencoded = "0.5" +url = "1.7" [lib] proc-macro = true diff --git a/src/api/metadata.rs b/src/api/metadata.rs index c167829d..2684aab3 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -25,7 +25,7 @@ impl From> for Metadata { _ => panic!("expected Member::Named"), }; - match identifier.as_ref() { + match identifier.to_string().as_ref() { "description" => { let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, diff --git a/src/api/mod.rs b/src/api/mod.rs index e7c84d61..a7737f6d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,5 @@ -use quote::{ToTokens, Tokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; use syn::punctuated::Punctuated; use syn::synom::Synom; use syn::{Field, FieldValue, Ident, Meta}; @@ -23,7 +24,7 @@ pub fn strip_serde_attrs(field: &Field) -> Field { _ => return true, }; - if meta_list.ident.as_ref() == "serde" { + if meta_list.ident == "serde" { return false; } @@ -50,9 +51,9 @@ impl From for Api { } impl ToTokens for Api { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let description = &self.metadata.description; - let method = Ident::from(self.metadata.method.as_ref()); + let method = Ident::new(self.metadata.method.as_ref(), Span::call_site()); let name = &self.metadata.name; let path = &self.metadata.path; let rate_limited = &self.metadata.rate_limited; @@ -92,7 +93,7 @@ impl ToTokens for Api { if segment.starts_with(':') { let path_var = &segment[1..]; - let path_var_ident = Ident::from(path_var); + let path_var_ident = Ident::new(path_var, Span::call_site()); tokens.append_all(quote! { (&request_path.#path_var_ident.to_string()); @@ -122,7 +123,7 @@ impl ToTokens for Api { url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); } } else { - Tokens::new() + TokenStream::new() }; let add_headers_to_request = if self.request.has_header_fields() { @@ -134,11 +135,11 @@ impl ToTokens for Api { header_tokens } else { - Tokens::new() + TokenStream::new() }; let create_http_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); @@ -202,13 +203,13 @@ impl ToTokens for Api { let mut headers = http_response.headers().clone(); } } else { - Tokens::new() + TokenStream::new() }; let response_init_fields = if self.response.has_fields() { self.response.init_fields() } else { - Tokens::new() + TokenStream::new() }; tokens.append_all(quote! { diff --git a/src/api/request.rs b/src/api/request.rs index be33d5be..d8272053 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,4 +1,5 @@ -use quote::{ToTokens, Tokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{TokenStreamExt, ToTokens}; use syn::spanned::Spanned; use syn::{Field, Ident, Lit, Meta, NestedMeta}; @@ -9,15 +10,15 @@ pub struct Request { } impl Request { - pub fn add_headers_to_request(&self) -> Tokens { - self.header_fields().fold(Tokens::new(), |mut header_tokens, request_field| { + pub fn add_headers_to_request(&self) -> TokenStream { + self.header_fields().fold(TokenStream::new(), |mut header_tokens, request_field| { let (field, header_name_string) = match request_field { RequestField::Header(field, header_name_string) => (field, header_name_string), _ => panic!("expected request field to be header variant"), }; let field_name = &field.ident; - let header_name = Ident::from(header_name_string.as_ref()); + let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); header_tokens.append_all(quote! { headers.append( @@ -67,23 +68,23 @@ impl Request { None } - pub fn request_body_init_fields(&self) -> Tokens { + pub fn request_body_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body) } - pub fn request_path_init_fields(&self) -> Tokens { + pub fn request_path_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Path) } - pub fn request_query_init_fields(&self) -> Tokens { + pub fn request_query_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query) } - fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> Tokens { - let mut tokens = Tokens::new(); + fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> TokenStream { + let mut tokens = TokenStream::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -112,7 +113,7 @@ impl From> for Request { _ => return true, }; - if meta_list.ident.as_ref() != "ruma_api" { + if meta_list.ident != "ruma_api" { return true; } @@ -121,7 +122,7 @@ impl From> for Request { NestedMeta::Meta(meta_item) => { match meta_item { Meta::Word(ident) => { - match ident.as_ref() { + match ident.to_string().as_ref() { "body" => { has_newtype_body = true; field_kind = RequestFieldKind::NewtypeBody; @@ -132,7 +133,7 @@ impl From> for Request { } } Meta::NameValue(name_value) => { - match name_value.ident.as_ref() { + match name_value.ident.to_string().as_ref() { "header" => { match name_value.lit { Lit::Str(lit_str) => header = Some(lit_str.value()), @@ -173,7 +174,7 @@ impl From> for Request { } impl ToTokens for Request { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let request_struct_header = quote! { /// Data for a request to this API endpoint. #[derive(Debug)] @@ -183,7 +184,7 @@ impl ToTokens for Request { let request_struct_body = if self.fields.len() == 0 { quote!(;) } else { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { let field = request_field.field(); let span = field.span(); @@ -214,7 +215,7 @@ impl ToTokens for Request { struct RequestBody(#ty); }; } else if self.has_body_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Body(ref field) => { let span = field.span(); @@ -235,13 +236,13 @@ impl ToTokens for Request { } }; } else { - request_body_struct = Tokens::new(); + request_body_struct = TokenStream::new(); } let request_path_struct; if self.has_path_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Path(ref field) => { let span = field.span(); @@ -262,13 +263,13 @@ impl ToTokens for Request { } }; } else { - request_path_struct = Tokens::new(); + request_path_struct = TokenStream::new(); } let request_query_struct; if self.has_query_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Query(ref field) => { let span = field.span(); @@ -289,7 +290,7 @@ impl ToTokens for Request { } }; } else { - request_query_struct = Tokens::new(); + request_query_struct = TokenStream::new(); } tokens.append_all(quote! { diff --git a/src/api/response.rs b/src/api/response.rs index 39658f1f..3d879f6f 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,4 +1,5 @@ -use quote::{ToTokens, Tokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; use syn::spanned::Spanned; use syn::{Field, Ident, Lit, Meta, NestedMeta}; @@ -21,13 +22,13 @@ impl Response { self.fields.iter().any(|field| field.is_header()) } - pub fn init_fields(&self) -> Tokens { - let mut tokens = Tokens::new(); + pub fn init_fields(&self) -> TokenStream { + let mut tokens = TokenStream::new(); for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -35,8 +36,8 @@ impl Response { }); } ResponseField::Header(ref field, ref header) => { - let field_name = field.ident.expect("expected field to have an identifier"); - let header_name = Ident::from(header.as_ref()); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -48,7 +49,7 @@ impl Response { }); } ResponseField::NewtypeBody(ref field) => { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -94,7 +95,7 @@ impl From> for Response { _ => return true, }; - if meta_list.ident.as_ref() != "ruma_api" { + if meta_list.ident != "ruma_api" { return true; } @@ -103,7 +104,7 @@ impl From> for Response { NestedMeta::Meta(meta_item) => { match meta_item { Meta::Word(ident) => { - match ident.as_ref() { + match ident.to_string().as_ref() { "body" => { has_newtype_body = true; field_kind = ResponseFieldKind::NewtypeBody; @@ -112,7 +113,7 @@ impl From> for Response { } } Meta::NameValue(name_value) => { - match name_value.ident.as_ref() { + match name_value.ident.to_string().as_ref() { "header" => { match name_value.lit { Lit::Str(lit_str) => header = Some(lit_str.value()), @@ -156,7 +157,7 @@ impl From> for Response { } impl ToTokens for Response { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let response_struct_header = quote! { /// Data in the response from this API endpoint. #[derive(Debug)] @@ -166,7 +167,7 @@ impl ToTokens for Response { let response_struct_body = if self.fields.len() == 0 { quote!(;) } else { - let fields = self.fields.iter().fold(Tokens::new(), |mut fields_tokens, response_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut fields_tokens, response_field| { let field = response_field.field(); let span = field.span(); @@ -197,7 +198,7 @@ impl ToTokens for Response { struct ResponseBody(#ty); }; } else if self.has_body_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, response_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, response_field| { match *response_field { ResponseField::Body(ref field) => { let span = field.span(); @@ -218,7 +219,7 @@ impl ToTokens for Response { } }; } else { - response_body_struct = Tokens::new(); + response_body_struct = TokenStream::new(); } tokens.append_all(quote! { diff --git a/src/lib.rs b/src/lib.rs index 4a90c685..126a60ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![recursion_limit="256"] extern crate proc_macro; +extern crate proc_macro2; #[macro_use] extern crate quote; extern crate ruma_api; #[macro_use] extern crate syn; @@ -207,5 +208,5 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { let api = Api::from(raw_api); - api.into_tokens().into() + api.into_token_stream().into() } From 116a6f44bca2236b7f85da55b8e2eb63cb733191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Sommer?= Date: Fri, 31 Aug 2018 13:01:22 +0200 Subject: [PATCH 075/107] Fix some hints from Rust and clippy * the feature `proc_macro` has been stable since 1.29.0 and no longer requires an attribute to enable * https://rust-lang-nursery.github.io/rust-clippy/v0.0.212/index.html#needless_return * https://rust-lang-nursery.github.io/rust-clippy/v0.0.212/index.html#len_zero --- src/api/request.rs | 2 +- src/api/response.rs | 6 +++--- src/lib.rs | 1 - tests/ruma_api_macros.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index d8272053..d86a2129 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -181,7 +181,7 @@ impl ToTokens for Request { pub struct Request }; - let request_struct_body = if self.fields.len() == 0 { + let request_struct_body = if self.fields.is_empty() { quote!(;) } else { let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { diff --git a/src/api/response.rs b/src/api/response.rs index 3d879f6f..1974d6a9 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -15,7 +15,7 @@ impl Response { } pub fn has_fields(&self) -> bool { - self.fields.len() != 0 + !self.fields.is_empty() } pub fn has_header_fields(&self) -> bool { @@ -142,7 +142,7 @@ impl From> for Response { if has_newtype_body { panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); } else { - return ResponseField::Body(field); + ResponseField::Body(field) } } ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")), @@ -164,7 +164,7 @@ impl ToTokens for Response { pub struct Response }; - let response_struct_body = if self.fields.len() == 0 { + let response_struct_body = if self.fields.is_empty() { quote!(;) } else { let fields = self.fields.iter().fold(TokenStream::new(), |mut fields_tokens, response_field| { diff --git a/src/lib.rs b/src/lib.rs index 126a60ba..fa110b54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro)] #![recursion_limit="256"] extern crate proc_macro; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 37295dbb..f6735ea1 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro, try_from)] +#![feature(try_from)] extern crate futures; extern crate http; From e23eff151bc063387ddf0a36a6cd801b85f50021 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 8 Sep 2018 11:06:20 +0200 Subject: [PATCH 076/107] Add convertion to/from Request/Response from/to http::Request/Response --- src/api/mod.rs | 175 +++++++++++++++++++++++++++++++++++++++++--- src/api/request.rs | 58 ++++++++++++--- src/api/response.rs | 49 ++++++++++++- src/lib.rs | 4 +- 4 files changed, 262 insertions(+), 24 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index a7737f6d..09ff964c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -64,7 +64,15 @@ impl ToTokens for Api { let response = &self.response; let response_types = quote! { #response }; - let set_request_path = if self.request.has_path_fields() { + let extract_request_path = if self.request.has_path_fields() { + quote! { + let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); + } + } else { + TokenStream::new() + }; + + let (set_request_path, parse_request_path) = if self.request.has_path_fields() { let path_str = path.as_str(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); @@ -75,7 +83,7 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); - let mut tokens = quote! { + let mut set_tokens = quote! { let request_path = RequestPath { #request_path_init_fields }; @@ -86,8 +94,10 @@ impl ToTokens for Api { let mut path_segments = url.path_segments_mut().unwrap(); }; - for segment in path_str[1..].split('/') { - tokens.append_all(quote! { + let mut parse_tokens = TokenStream::new(); + + for (i, segment) in path_str[1..].split('/').into_iter().enumerate() { + set_tokens.append_all(quote! { path_segments.push }); @@ -95,21 +105,38 @@ impl ToTokens for Api { let path_var = &segment[1..]; let path_var_ident = Ident::new(path_var, Span::call_site()); - tokens.append_all(quote! { + set_tokens.append_all(quote! { (&request_path.#path_var_ident.to_string()); }); + + let path_field = self.request.path_field(path_var) + .expect("expected request to have path field"); + let ty = &path_field.ty; + + parse_tokens.append_all(quote! { + #path_var_ident: { + let segment = path_segments.get(#i).unwrap().as_bytes(); + let decoded = + ::url::percent_encoding::percent_decode(segment) + .decode_utf8_lossy(); + #ty::deserialize(decoded.into_deserializer()) + .map_err(|e: ::serde_json::error::Error| e)? + }, + }); } else { - tokens.append_all(quote! { + set_tokens.append_all(quote! { (#segment); }); } } - tokens + (set_tokens, parse_tokens) } else { - quote! { + let set_tokens = quote! { url.set_path(metadata.path); - } + }; + let parse_tokens = TokenStream::new(); + (set_tokens, parse_tokens) }; let set_request_query = if self.request.has_query_fields() { @@ -126,6 +153,21 @@ impl ToTokens for Api { TokenStream::new() }; + let extract_request_query = if self.request.has_query_fields() { + quote! { + let request_query: RequestQuery = + ::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; + } + } else { + TokenStream::new() + }; + + let parse_request_query = if self.request.has_query_fields() { + self.request.request_init_query_fields() + } else { + TokenStream::new() + }; + let add_headers_to_request = if self.request.has_header_fields() { let mut header_tokens = quote! { let headers = http_request.headers_mut(); @@ -138,6 +180,20 @@ impl ToTokens for Api { TokenStream::new() }; + let extract_request_headers = if self.request.has_header_fields() { + quote! { + let headers = request.headers(); + } + } else { + TokenStream::new() + }; + + let parse_request_headers = if self.request.has_header_fields() { + self.request.parse_headers_from_request() + } else { + TokenStream::new() + }; + let create_http_request = if let Some(field) = self.request.newtype_body_field() { let field_name = field.ident.as_ref().expect("expected field to have an identifier"); @@ -162,6 +218,33 @@ impl ToTokens for Api { } }; + let extract_request_body = if let Some(field) = self.request.newtype_body_field() { + let ty = &field.ty; + quote! { + let request_body: #ty = + ::serde_json::from_slice(request.body().as_slice())?; + } + } else if self.request.has_body_fields() { + quote! { + let request_body: RequestBody = + ::serde_json::from_slice(request.body().as_slice())?; + } + } else { + TokenStream::new() + }; + + let parse_request_body = if let Some(field) = self.request.newtype_body_field() { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + + quote! { + #field_name: request_body, + } + } else if self.request.has_body_fields() { + self.request.request_init_body_fields() + } else { + TokenStream::new() + }; + let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { let field_type = &field.ty; @@ -198,7 +281,7 @@ impl ToTokens for Api { } }; - let extract_headers = if self.response.has_header_fields() { + let extract_response_headers = if self.response.has_header_fields() { quote! { let mut headers = http_response.headers().clone(); } @@ -212,10 +295,27 @@ impl ToTokens for Api { TokenStream::new() }; + let serialize_response_headers = self.response.apply_header_fields(); + + let serialize_response_body = if self.response.has_body() { + let body = self.response.to_body(); + quote! { + .body(::hyper::Body::from(::serde_json::to_vec(&#body)?)) + } + } else { + quote! { + .body(::hyper::Body::from("{}".as_bytes().to_vec())) + } + }; + tokens.append_all(quote! { #[allow(unused_imports)] use ::futures::{Future as _Future, IntoFuture as _IntoFuture, Stream as _Stream}; use ::ruma_api::Endpoint as _RumaApiEndpoint; + use ::serde::Deserialize; + use ::serde::de::{Error as _SerdeError, IntoDeserializer}; + + use ::std::convert::{TryInto as _TryInto}; /// The API endpoint. #[derive(Debug)] @@ -223,6 +323,45 @@ impl ToTokens for Api { #request_types + impl ::std::convert::TryFrom<::http::Request>> for Request { + type Error = ::ruma_api::Error; + + #[allow(unused_variables)] + fn try_from(request: ::http::Request>) -> Result { + #extract_request_path + #extract_request_query + #extract_request_headers + #extract_request_body + + Ok(Request { + #parse_request_path + #parse_request_query + #parse_request_headers + #parse_request_body + }) + } + } + + impl ::futures::future::FutureFrom<::http::Request<::hyper::Body>> for Request { + type Future = Box<_Future>; + type Error = ::ruma_api::Error; + + #[allow(unused_variables)] + fn future_from(request: ::http::Request<::hyper::Body>) -> Self::Future { + let (parts, body) = request.into_parts(); + let future = body.from_err().fold(Vec::new(), |mut vec, chunk| { + vec.extend(chunk.iter()); + ::futures::future::ok::<_, Self::Error>(vec) + }).and_then(|body| { + ::http::Request::from_parts(parts, body) + .try_into() + .into_future() + .from_err() + }); + Box::new(future) + } + } + impl ::std::convert::TryFrom for ::http::Request<::hyper::Body> { type Error = ::ruma_api::Error; @@ -251,6 +390,20 @@ impl ToTokens for Api { #response_types + impl ::std::convert::TryFrom for ::http::Response<::hyper::Body> { + type Error = ::ruma_api::Error; + + #[allow(unused_variables)] + fn try_from(response: Response) -> Result { + let response = ::http::Response::builder() + .header(::http::header::CONTENT_TYPE, "application/json") + #serialize_response_headers + #serialize_response_body + .unwrap(); + Ok(response) + } + } + impl ::futures::future::FutureFrom<::http::Response<::hyper::Body>> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; @@ -259,7 +412,7 @@ impl ToTokens for Api { fn future_from(http_response: ::http::Response<::hyper::Body>) -> Box<_Future> { if http_response.status().is_success() { - #extract_headers + #extract_response_headers #deserialize_response_body .and_then(move |response_body| { diff --git a/src/api/request.rs b/src/api/request.rs index d86a2129..55cbacbe 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -32,6 +32,27 @@ impl Request { }) } + pub fn parse_headers_from_request(&self) -> TokenStream { + self.header_fields().fold(TokenStream::new(), |mut header_tokens, request_field| { + let (field, header_name_string) = match request_field { + RequestField::Header(field, header_name_string) => (field, header_name_string), + _ => panic!("expected request field to be header variant"), + }; + + let field_name = &field.ident; + let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); + + header_tokens.append_all(quote! { + #field_name: headers.get(::http::header::#header_name) + .and_then(|v| v.to_str().ok()) + .ok_or(::serde_json::Error::missing_field(#header_name_string))? + .to_owned(), + }); + + header_tokens + }) + } + pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } @@ -55,6 +76,17 @@ impl Request { self.fields.iter().filter(|field| field.is_path()).count() } + pub fn path_field(&self, name: &str) -> Option<&Field> { + self.fields.iter() + .flat_map(|f| f.field_(RequestFieldKind::Path)) + .find(|field| { + field.ident.as_ref() + .expect("expected field to have an identifier") + .to_string() + == name + }) + } + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -69,18 +101,26 @@ impl Request { } pub fn request_body_init_fields(&self) -> TokenStream { - self.struct_init_fields(RequestFieldKind::Body) + self.struct_init_fields(RequestFieldKind::Body, quote!(request)) } pub fn request_path_init_fields(&self) -> TokenStream { - self.struct_init_fields(RequestFieldKind::Path) + self.struct_init_fields(RequestFieldKind::Path, quote!(request)) } pub fn request_query_init_fields(&self) -> TokenStream { - self.struct_init_fields(RequestFieldKind::Query) + self.struct_init_fields(RequestFieldKind::Query, quote!(request)) } - fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> TokenStream { + pub fn request_init_body_fields(&self) -> TokenStream { + self.struct_init_fields(RequestFieldKind::Body, quote!(request_body)) + } + + pub fn request_init_query_fields(&self) -> TokenStream { + self.struct_init_fields(RequestFieldKind::Query, quote!(request_query)) + } + + fn struct_init_fields(&self, request_field_kind: RequestFieldKind, src: TokenStream) -> TokenStream { let mut tokens = TokenStream::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { @@ -88,7 +128,7 @@ impl Request { let span = field.span(); tokens.append_all(quote_spanned! {span=> - #field_name: request.#field_name, + #field_name: #src.#field_name, }); } @@ -211,7 +251,7 @@ impl ToTokens for Request { request_body_struct = quote_spanned! {span=> /// Data in the request body. - #[derive(Debug, Serialize)] + #[derive(Debug, Deserialize, Serialize)] struct RequestBody(#ty); }; } else if self.has_body_fields() { @@ -230,7 +270,7 @@ impl ToTokens for Request { request_body_struct = quote! { /// Data in the request body. - #[derive(Debug, Serialize)] + #[derive(Debug, Deserialize, Serialize)] struct RequestBody { #fields } @@ -257,7 +297,7 @@ impl ToTokens for Request { request_path_struct = quote! { /// Data in the request path. - #[derive(Debug, Serialize)] + #[derive(Debug, Deserialize, Serialize)] struct RequestPath { #fields } @@ -284,7 +324,7 @@ impl ToTokens for Request { request_query_struct = quote! { /// Data in the request's query string. - #[derive(Debug, Serialize)] + #[derive(Debug, Deserialize, Serialize)] struct RequestQuery { #fields } diff --git a/src/api/response.rs b/src/api/response.rs index 1974d6a9..3fdf380a 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -22,6 +22,10 @@ impl Response { self.fields.iter().any(|field| field.is_header()) } + pub fn has_body(&self) -> bool { + self.fields.iter().any(|field| !field.is_header()) + } + pub fn init_fields(&self) -> TokenStream { let mut tokens = TokenStream::new(); @@ -62,6 +66,47 @@ impl Response { tokens } + pub fn apply_header_fields(&self) -> TokenStream { + let mut tokens = TokenStream::new(); + + for response_field in self.fields.iter() { + if let ResponseField::Header(ref field, ref header) = *response_field { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let header_name = Ident::new(header.as_ref(), Span::call_site()); + let span = field.span(); + + tokens.append_all(quote_spanned! {span=> + .header(::http::header::#header_name, response.#field_name) + }); + } + } + + tokens + } + + pub fn to_body(&self) -> TokenStream { + if let Some(ref field) = self.newtype_body_field() { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); + quote_spanned!(span=> response.#field_name) + } else { + let fields = self.fields.iter().filter_map(|response_field| { + if let ResponseField::Body(ref field) = *response_field { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); + + Some(quote_spanned! {span=> + #field_name: response.#field_name + }) + } else { + None + } + }); + + quote!(ResponseBody{#(#fields),*}) + } + } + pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { @@ -194,7 +239,7 @@ impl ToTokens for Response { response_body_struct = quote_spanned! {span=> /// Data in the response body. - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Serialize)] struct ResponseBody(#ty); }; } else if self.has_body_fields() { @@ -213,7 +258,7 @@ impl ToTokens for Response { response_body_struct = quote! { /// Data in the response body. - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, Serialize)] struct ResponseBody { #fields } diff --git a/src/lib.rs b/src/lib.rs index fa110b54..741e56fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ mod api; /// This will generate a `ruma_api::Metadata` value to be used for the `ruma_api::Endpoint`'s /// associated constant, single `Request` and `Response` structs, and the necessary trait /// implementations to convert the request into a `http::Request` and to create a response from a -/// `http::Response`. +/// `http::Response` and vice versa. /// /// The details of each of the three sections of the macros are documented below. /// @@ -173,7 +173,7 @@ mod api; /// pub mod newtype_body_endpoint { /// use ruma_api_macros::ruma_api; /// -/// #[derive(Debug, Deserialize)] +/// #[derive(Debug, Deserialize, Serialize)] /// pub struct MyCustomType { /// pub foo: String, /// } From e4ec9442d8fec9955c7d3d57708d84be7ba05f3e Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 12 Sep 2018 13:23:33 +0200 Subject: [PATCH 077/107] Make the Future returned by generated `future_from`s be Send --- src/api/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 09ff964c..4ed3ddec 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -343,7 +343,7 @@ impl ToTokens for Api { } impl ::futures::future::FutureFrom<::http::Request<::hyper::Body>> for Request { - type Future = Box<_Future>; + type Future = Box<_Future + Send>; type Error = ::ruma_api::Error; #[allow(unused_variables)] @@ -405,12 +405,11 @@ impl ToTokens for Api { } impl ::futures::future::FutureFrom<::http::Response<::hyper::Body>> for Response { - type Future = Box<_Future>; + type Future = Box<_Future + Send>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response<::hyper::Body>) - -> Box<_Future> { + fn future_from(http_response: ::http::Response<::hyper::Body>) -> Self::Future { if http_response.status().is_success() { #extract_response_headers From 20cbadd95bcf1b618a613da6e243f877c997f0ba Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 8 Sep 2018 11:10:40 +0200 Subject: [PATCH 078/107] Make Request and Response cloneable --- src/api/request.rs | 2 +- src/api/response.rs | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/request.rs b/src/api/request.rs index be33d5be..c94c1f92 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -176,7 +176,7 @@ impl ToTokens for Request { fn to_tokens(&self, tokens: &mut Tokens) { let request_struct_header = quote! { /// Data for a request to this API endpoint. - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct Request }; diff --git a/src/api/response.rs b/src/api/response.rs index 39658f1f..58c4d710 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -159,7 +159,7 @@ impl ToTokens for Response { fn to_tokens(&self, tokens: &mut Tokens) { let response_struct_header = quote! { /// Data in the response from this API endpoint. - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct Response }; diff --git a/src/lib.rs b/src/lib.rs index 6306380d..4fdce2b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -172,7 +172,7 @@ mod api; /// pub mod newtype_body_endpoint { /// use ruma_api_macros::ruma_api; /// -/// #[derive(Debug, Deserialize)] +/// #[derive(Clone, Debug, Deserialize)] /// pub struct MyCustomType { /// pub foo: String, /// } From c9277ddc94ebd55e54cc9a72e135525638c68bbd Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 10 Nov 2018 14:04:46 +0100 Subject: [PATCH 079/107] Upgrade dependencies --- Cargo.toml | 20 +++++------ src/api/metadata.rs | 2 +- src/api/mod.rs | 76 +++++++++++++++++++++++++--------------- src/api/request.rs | 43 ++++++++++++----------- src/api/response.rs | 38 ++++++++++++-------- src/lib.rs | 8 ++--- tests/ruma_api_macros.rs | 2 +- 7 files changed, 110 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de2b539d..784ffc84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,25 +11,25 @@ repository = "https://github.com/ruma/ruma-api-macros" version = "0.2.2" [dependencies] -quote = "0.5.2" +quote = "0.6.10" ruma-api = "0.5.0" [dependencies.syn] -version = "0.13.10" +version = "0.15.18" features = ["full"] [dependencies.proc-macro2] -version = "0.4.2" +version = "0.4.21" features = ["nightly"] [dev-dependencies] -futures = "0.1.21" -http = "0.1.5" -serde = "1.0.57" -serde_derive = "1.0.57" -serde_json = "1.0.17" -serde_urlencoded = "0.5.2" -url = "1.7.0" +futures = "0.1.25" +http = "0.1.13" +serde = "1.0.80" +serde_derive = "1.0.80" +serde_json = "1.0.33" +serde_urlencoded = "0.5.3" +url = "1.7.2" [lib] proc-macro = true diff --git a/src/api/metadata.rs b/src/api/metadata.rs index c167829d..300eb44b 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -25,7 +25,7 @@ impl From> for Metadata { _ => panic!("expected Member::Named"), }; - match identifier.as_ref() { + match &identifier.to_string()[..] { "description" => { let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, diff --git a/src/api/mod.rs b/src/api/mod.rs index c199c680..4a4d5510 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ -use quote::{ToTokens, Tokens}; -use syn::punctuated::Punctuated; -use syn::synom::Synom; -use syn::{Field, FieldValue, Ident, Meta}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; +use syn::{braced, Field, FieldValue, Ident, Meta, Token}; +use syn::parse::{Parse, ParseStream, Result}; mod metadata; mod request; @@ -23,7 +23,7 @@ pub fn strip_serde_attrs(field: &Field) -> Field { _ => return true, }; - if meta_list.ident.as_ref() == "serde" { + if &meta_list.ident.to_string() == "serde" { return false; } @@ -50,9 +50,9 @@ impl From for Api { } impl ToTokens for Api { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let description = &self.metadata.description; - let method = Ident::from(self.metadata.method.as_ref()); + let method = Ident::new(self.metadata.method.as_ref(), Span::call_site()); let name = &self.metadata.name; let path = &self.metadata.path; let rate_limited = &self.metadata.rate_limited; @@ -92,7 +92,7 @@ impl ToTokens for Api { if segment.starts_with(':') { let path_var = &segment[1..]; - let path_var_ident = Ident::from(path_var); + let path_var_ident = Ident::new(path_var, Span::call_site()); tokens.append_all(quote! { (&request_path.#path_var_ident.to_string()); @@ -122,7 +122,7 @@ impl ToTokens for Api { url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); } } else { - Tokens::new() + TokenStream::new() }; let add_headers_to_request = if self.request.has_header_fields() { @@ -134,11 +134,11 @@ impl ToTokens for Api { header_tokens } else { - Tokens::new() + TokenStream::new() }; let create_http_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.clone().expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); @@ -188,13 +188,13 @@ impl ToTokens for Api { let mut headers = http_response.headers().clone(); } } else { - Tokens::new() + TokenStream::new() }; let response_init_fields = if self.response.has_fields() { self.response.init_fields() } else { - Tokens::new() + TokenStream::new() }; tokens.append_all(quote! { @@ -279,8 +279,13 @@ impl ToTokens for Api { } } -type ParseMetadata = Punctuated; -type ParseFields = Punctuated; +mod kw { + use syn::custom_keyword; + + custom_keyword!(metadata); + custom_keyword!(request); + custom_keyword!(response); +} pub struct RawApi { pub metadata: Vec, @@ -288,18 +293,33 @@ pub struct RawApi { pub response: Vec, } -impl Synom for RawApi { - named!(parse -> Self, do_parse!( - custom_keyword!(metadata) >> - metadata: braces!(ParseMetadata::parse_terminated) >> - custom_keyword!(request) >> - request: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> - custom_keyword!(response) >> - response: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> - (RawApi { - metadata: metadata.1.into_iter().collect(), - request: request.1.into_iter().collect(), - response: response.1.into_iter().collect(), +impl Parse for RawApi { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + let metadata; + braced!(metadata in input); + + input.parse::()?; + let request; + braced!(request in input); + + input.parse::()?; + let response; + braced!(response in input); + + Ok(RawApi { + metadata: metadata + .parse_terminated::(FieldValue::parse)? + .into_iter() + .collect(), + request: request + .parse_terminated::(Field::parse_named)? + .into_iter() + .collect(), + response: response + .parse_terminated::(Field::parse_named)? + .into_iter() + .collect(), }) - )); + } } diff --git a/src/api/request.rs b/src/api/request.rs index be33d5be..1d3bff4d 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,4 +1,5 @@ -use quote::{ToTokens, Tokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; use syn::spanned::Spanned; use syn::{Field, Ident, Lit, Meta, NestedMeta}; @@ -9,15 +10,15 @@ pub struct Request { } impl Request { - pub fn add_headers_to_request(&self) -> Tokens { - self.header_fields().fold(Tokens::new(), |mut header_tokens, request_field| { + pub fn add_headers_to_request(&self) -> TokenStream { + self.header_fields().fold(TokenStream::new(), |mut header_tokens, request_field| { let (field, header_name_string) = match request_field { RequestField::Header(field, header_name_string) => (field, header_name_string), _ => panic!("expected request field to be header variant"), }; let field_name = &field.ident; - let header_name = Ident::from(header_name_string.as_ref()); + let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); header_tokens.append_all(quote! { headers.append( @@ -67,23 +68,23 @@ impl Request { None } - pub fn request_body_init_fields(&self) -> Tokens { + pub fn request_body_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body) } - pub fn request_path_init_fields(&self) -> Tokens { + pub fn request_path_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Path) } - pub fn request_query_init_fields(&self) -> Tokens { + pub fn request_query_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query) } - fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> Tokens { - let mut tokens = Tokens::new(); + fn struct_init_fields(&self, request_field_kind: RequestFieldKind) -> TokenStream { + let mut tokens = TokenStream::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field.ident.clone().expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -112,7 +113,7 @@ impl From> for Request { _ => return true, }; - if meta_list.ident.as_ref() != "ruma_api" { + if &meta_list.ident.to_string() != "ruma_api" { return true; } @@ -121,7 +122,7 @@ impl From> for Request { NestedMeta::Meta(meta_item) => { match meta_item { Meta::Word(ident) => { - match ident.as_ref() { + match &ident.to_string()[..] { "body" => { has_newtype_body = true; field_kind = RequestFieldKind::NewtypeBody; @@ -132,7 +133,7 @@ impl From> for Request { } } Meta::NameValue(name_value) => { - match name_value.ident.as_ref() { + match &name_value.ident.to_string()[..] { "header" => { match name_value.lit { Lit::Str(lit_str) => header = Some(lit_str.value()), @@ -173,7 +174,7 @@ impl From> for Request { } impl ToTokens for Request { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let request_struct_header = quote! { /// Data for a request to this API endpoint. #[derive(Debug)] @@ -183,7 +184,7 @@ impl ToTokens for Request { let request_struct_body = if self.fields.len() == 0 { quote!(;) } else { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { let field = request_field.field(); let span = field.span(); @@ -214,7 +215,7 @@ impl ToTokens for Request { struct RequestBody(#ty); }; } else if self.has_body_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Body(ref field) => { let span = field.span(); @@ -235,13 +236,13 @@ impl ToTokens for Request { } }; } else { - request_body_struct = Tokens::new(); + request_body_struct = TokenStream::new(); } let request_path_struct; if self.has_path_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Path(ref field) => { let span = field.span(); @@ -262,13 +263,13 @@ impl ToTokens for Request { } }; } else { - request_path_struct = Tokens::new(); + request_path_struct = TokenStream::new(); } let request_query_struct; if self.has_query_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { match *request_field { RequestField::Query(ref field) => { let span = field.span(); @@ -289,7 +290,7 @@ impl ToTokens for Request { } }; } else { - request_query_struct = Tokens::new(); + request_query_struct = TokenStream::new(); } tokens.append_all(quote! { diff --git a/src/api/response.rs b/src/api/response.rs index 39658f1f..7b4e6499 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,4 +1,5 @@ -use quote::{ToTokens, Tokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; use syn::spanned::Spanned; use syn::{Field, Ident, Lit, Meta, NestedMeta}; @@ -21,13 +22,16 @@ impl Response { self.fields.iter().any(|field| field.is_header()) } - pub fn init_fields(&self) -> Tokens { - let mut tokens = Tokens::new(); + pub fn init_fields(&self) -> TokenStream { + let mut tokens = TokenStream::new(); for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field + .ident + .clone() + .expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -35,8 +39,11 @@ impl Response { }); } ResponseField::Header(ref field, ref header) => { - let field_name = field.ident.expect("expected field to have an identifier"); - let header_name = Ident::from(header.as_ref()); + let field_name = field + .ident + .clone() + .expect("expected field to have an identifier"); + let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -48,7 +55,10 @@ impl Response { }); } ResponseField::NewtypeBody(ref field) => { - let field_name = field.ident.expect("expected field to have an identifier"); + let field_name = field + .ident + .clone() + .expect("expected field to have an identifier"); let span = field.span(); tokens.append_all(quote_spanned! {span=> @@ -94,7 +104,7 @@ impl From> for Response { _ => return true, }; - if meta_list.ident.as_ref() != "ruma_api" { + if &meta_list.ident.to_string() != "ruma_api" { return true; } @@ -103,7 +113,7 @@ impl From> for Response { NestedMeta::Meta(meta_item) => { match meta_item { Meta::Word(ident) => { - match ident.as_ref() { + match &ident.to_string()[..] { "body" => { has_newtype_body = true; field_kind = ResponseFieldKind::NewtypeBody; @@ -112,7 +122,7 @@ impl From> for Response { } } Meta::NameValue(name_value) => { - match name_value.ident.as_ref() { + match &name_value.ident.to_string()[..] { "header" => { match name_value.lit { Lit::Str(lit_str) => header = Some(lit_str.value()), @@ -156,7 +166,7 @@ impl From> for Response { } impl ToTokens for Response { - fn to_tokens(&self, tokens: &mut Tokens) { + fn to_tokens(&self, tokens: &mut TokenStream) { let response_struct_header = quote! { /// Data in the response from this API endpoint. #[derive(Debug)] @@ -166,7 +176,7 @@ impl ToTokens for Response { let response_struct_body = if self.fields.len() == 0 { quote!(;) } else { - let fields = self.fields.iter().fold(Tokens::new(), |mut fields_tokens, response_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut fields_tokens, response_field| { let field = response_field.field(); let span = field.span(); @@ -197,7 +207,7 @@ impl ToTokens for Response { struct ResponseBody(#ty); }; } else if self.has_body_fields() { - let fields = self.fields.iter().fold(Tokens::new(), |mut field_tokens, response_field| { + let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, response_field| { match *response_field { ResponseField::Body(ref field) => { let span = field.span(); @@ -218,7 +228,7 @@ impl ToTokens for Response { } }; } else { - response_body_struct = Tokens::new(); + response_body_struct = TokenStream::new(); } tokens.append_all(quote! { diff --git a/src/lib.rs b/src/lib.rs index 6306380d..c2134f43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,13 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro)] #![recursion_limit="256"] extern crate proc_macro; +extern crate proc_macro2; #[macro_use] extern crate quote; extern crate ruma_api; -#[macro_use] extern crate syn; +extern crate syn; use proc_macro::TokenStream; @@ -202,9 +202,9 @@ mod api; /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let raw_api: RawApi = syn::parse(input).expect("ruma_api! failed to parse input"); + let raw_api = syn::parse_macro_input!(input as RawApi); let api = Api::from(raw_api); - api.into_tokens().into() + api.into_token_stream().into() } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 43cd5472..b37f20f6 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro, try_from)] +#![feature(try_from)] extern crate futures; extern crate http; From 2f07b803a84ee5183c51d3c0832a58ec073c04dd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 18:06:14 -0800 Subject: [PATCH 080/107] Update to ruma-api 0.6.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 56cdf43a..88e438e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ version = "0.2.2" [dependencies] quote = "0.6" -ruma-api = "0.5" +ruma-api = "0.6" [dependencies.syn] version = "0.14" From b72afdefaed1194ed88625f8b73fe3586ac3a071 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 18:08:03 -0800 Subject: [PATCH 081/107] Update dependencies. --- Cargo.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88e438e0..baebad17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,26 +11,26 @@ repository = "https://github.com/ruma/ruma-api-macros" version = "0.2.2" [dependencies] -quote = "0.6" -ruma-api = "0.6" +quote = "0.6.10" +ruma-api = "0.6.0" [dependencies.syn] version = "0.14" features = ["full"] [dependencies.proc-macro2] -version = "0.4" +version = "0.4.24" features = ["nightly"] [dev-dependencies] -futures = "0.1" -http = "0.1" -hyper = "0.12" -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -serde_urlencoded = "0.5" -url = "1.7" +futures = "0.1.25" +http = "0.1.14" +hyper = "0.12.16" +serde = "1.0.80" +serde_derive = "1.0.80" +serde_json = "1.0.33" +serde_urlencoded = "0.5.4" +url = "1.7.2" [lib] proc-macro = true From 0e494ade6655626362350b91dfc9f39d4aeb0147 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 18:28:16 -0800 Subject: [PATCH 082/107] Bump version to 0.3.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4c2c4c6e..98361860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.2.2" +version = "0.3.0" [dependencies] quote = "0.6.10" From 0a4239b678de08792b59f8997de4ee8080d3d62f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 15 Dec 2018 21:22:07 +0100 Subject: [PATCH 083/107] Get rid of almost all calls to append_all --- src/api/mod.rs | 66 ++++++++++++---------- src/api/request.rs | 135 ++++++++++++++++++++++---------------------- src/api/response.rs | 85 ++++++++++++++-------------- 3 files changed, 144 insertions(+), 142 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 7287d397..1b807406 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -82,7 +82,22 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); - let mut set_tokens = quote! { + let path_segments = path_str[1..].split('/').into_iter(); + let path_segment_push = path_segments.clone().map(|segment| { + let arg = if segment.starts_with(':') { + let path_var = &segment[1..]; + let path_var_ident = Ident::new(path_var, Span::call_site()); + quote!(&request_path.#path_var_ident.to_string()) + } else { + quote!(#segment) + }; + + quote! { + path_segments.push(#arg); + } + }); + + let set_tokens = quote! { let request_path = RequestPath { #request_path_init_fields }; @@ -91,28 +106,22 @@ impl ToTokens for Api { // cannot-be-base url like `mailto:` or `data:`, which is not // the case for our placeholder url. let mut path_segments = url.path_segments_mut().unwrap(); + #(#path_segment_push)* }; - let mut parse_tokens = TokenStream::new(); - - for (i, segment) in path_str[1..].split('/').into_iter().enumerate() { - set_tokens.append_all(quote! { - path_segments.push - }); - - if segment.starts_with(':') { + let path_fields = path_segments + .enumerate() + .filter(|(_, s)| s.starts_with(':')) + .map(|(i, segment)| { let path_var = &segment[1..]; let path_var_ident = Ident::new(path_var, Span::call_site()); - - set_tokens.append_all(quote! { - (&request_path.#path_var_ident.to_string()); - }); - - let path_field = self.request.path_field(path_var) + let path_field = self + .request + .path_field(path_var) .expect("expected request to have path field"); let ty = &path_field.ty; - parse_tokens.append_all(quote! { + quote! { #path_var_ident: { let segment = path_segments.get(#i).unwrap().as_bytes(); let decoded = @@ -120,14 +129,13 @@ impl ToTokens for Api { .decode_utf8_lossy(); #ty::deserialize(decoded.into_deserializer()) .map_err(|e: ::serde_json::error::Error| e)? - }, - }); - } else { - set_tokens.append_all(quote! { - (#segment); - }); - } - } + } + } + }); + + let parse_tokens = quote! { + #(#path_fields,)* + }; (set_tokens, parse_tokens) } else { @@ -168,13 +176,11 @@ impl ToTokens for Api { }; let add_headers_to_request = if self.request.has_header_fields() { - let mut header_tokens = quote! { + let add_headers = self.request.add_headers_to_request(); + quote! { let headers = http_request.headers_mut(); - }; - - header_tokens.append_all(self.request.add_headers_to_request()); - - header_tokens + #add_headers + } } else { TokenStream::new() }; diff --git a/src/api/request.rs b/src/api/request.rs index 01fea4d7..54c4453c 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -11,7 +11,7 @@ pub struct Request { impl Request { pub fn add_headers_to_request(&self) -> TokenStream { - self.header_fields().fold(TokenStream::new(), |mut header_tokens, request_field| { + let append_stmts = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { RequestField::Header(field, header_name_string) => (field, header_name_string), _ => panic!("expected request field to be header variant"), @@ -20,20 +20,22 @@ impl Request { let field_name = &field.ident; let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); - header_tokens.append_all(quote! { + quote! { headers.append( ::http::header::#header_name, ::http::header::HeaderValue::from_str(request.#field_name.as_ref()) .expect("failed to convert value into HeaderValue"), ); - }); + } + }); - header_tokens - }) + quote! { + #(#append_stmts)* + } } pub fn parse_headers_from_request(&self) -> TokenStream { - self.header_fields().fold(TokenStream::new(), |mut header_tokens, request_field| { + let fields = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { RequestField::Header(field, header_name_string) => (field, header_name_string), _ => panic!("expected request field to be header variant"), @@ -42,15 +44,17 @@ impl Request { let field_name = &field.ident; let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); - header_tokens.append_all(quote! { + quote! { #field_name: headers.get(::http::header::#header_name) .and_then(|v| v.to_str().ok()) .ok_or(::serde_json::Error::missing_field(#header_name_string))? - .to_owned(), - }); + .to_owned() + } + }); - header_tokens - }) + quote! { + #(#fields,)* + } } pub fn has_body_fields(&self) -> bool { @@ -120,19 +124,28 @@ impl Request { self.struct_init_fields(RequestFieldKind::Query, quote!(request_query)) } - fn struct_init_fields(&self, request_field_kind: RequestFieldKind, src: TokenStream) -> TokenStream { - let mut tokens = TokenStream::new(); + fn struct_init_fields( + &self, + request_field_kind: RequestFieldKind, + src: TokenStream, + ) -> TokenStream { + let fields = self.fields.iter().filter_map(|f| { + f.field_(request_field_kind).map(|field| { + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); + let span = field.span(); - for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); - let span = field.span(); + quote_spanned! {span=> + #field_name: #src.#field_name + } + }) + }); - tokens.append_all(quote_spanned! {span=> - #field_name: #src.#field_name, - }); + quote! { + #(#fields,)* } - - tokens } } @@ -224,114 +237,98 @@ impl ToTokens for Request { let request_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().map(|request_field| { let field = request_field.field(); let span = field.span(); let stripped_field = strip_serde_attrs(field); - field_tokens.append_all(quote_spanned!(span=> #stripped_field,)); - - field_tokens + quote_spanned!(span=> #stripped_field) }); quote! { { - #fields + #(#fields),* } } }; - let request_body_struct; - - if let Some(newtype_body_field) = self.newtype_body_field() { + let request_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); - request_body_struct = quote_spanned! {span=> + quote_spanned! {span=> /// Data in the request body. #[derive(Debug, Deserialize, Serialize)] struct RequestBody(#ty); - }; + } } else if self.has_body_fields() { - let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { + let fields = self.fields.iter().filter_map(|request_field| { match *request_field { RequestField::Body(ref field) => { let span = field.span(); - - field_tokens.append_all(quote_spanned!(span=> #field,)); - - field_tokens + Some(quote_spanned!(span=> #field)) } - _ => field_tokens, + _ => None, } }); - request_body_struct = quote! { + quote! { /// Data in the request body. #[derive(Debug, Deserialize, Serialize)] struct RequestBody { - #fields + #(#fields),* } - }; + } } else { - request_body_struct = TokenStream::new(); - } + TokenStream::new() + }; - let request_path_struct; - - if self.has_path_fields() { - let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { + let request_path_struct = if self.has_path_fields() { + let fields = self.fields.iter().filter_map(|request_field| { match *request_field { RequestField::Path(ref field) => { let span = field.span(); - field_tokens.append_all(quote_spanned!(span=> #field,)); - - field_tokens + Some(quote_spanned!(span=> #field)) } - _ => field_tokens, + _ => None, } }); - request_path_struct = quote! { + quote! { /// Data in the request path. #[derive(Debug, Deserialize, Serialize)] struct RequestPath { - #fields + #(#fields),* } - }; + } } else { - request_path_struct = TokenStream::new(); - } + TokenStream::new() + }; - let request_query_struct; - - if self.has_query_fields() { - let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, request_field| { + let request_query_struct = if self.has_query_fields() { + let fields = self.fields.iter().filter_map(|request_field| { match *request_field { RequestField::Query(ref field) => { let span = field.span(); - - field_tokens.append_all(quote_spanned!(span=> #field,)); - - field_tokens + Some(quote_spanned!(span=> #field)) } - _ => field_tokens, + _ => None, } }); - request_query_struct = quote! { + quote! { /// Data in the request's query string. #[derive(Debug, Deserialize, Serialize)] struct RequestQuery { - #fields + #(#fields),* } - }; + } } else { - request_query_struct = TokenStream::new(); - } + TokenStream::new() + }; tokens.append_all(quote! { #request_struct_header diff --git a/src/api/response.rs b/src/api/response.rs index a926913d..98f0e8ac 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -27,9 +27,7 @@ impl Response { } pub fn init_fields(&self) -> TokenStream { - let mut tokens = TokenStream::new(); - - for response_field in self.fields.iter() { + let fields = self.fields.iter().map(|response_field| { match *response_field { ResponseField::Body(ref field) => { let field_name = field @@ -38,9 +36,9 @@ impl Response { .expect("expected field to have an identifier"); let span = field.span(); - tokens.append_all(quote_spanned! {span=> - #field_name: response_body.#field_name, - }); + quote_spanned! {span=> + #field_name: response_body.#field_name + } } ResponseField::Header(ref field, ref header) => { let field_name = field @@ -50,13 +48,13 @@ impl Response { let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); - tokens.append_all(quote_spanned! {span=> + quote_spanned! {span=> #field_name: headers.remove(::http::header::#header_name) .expect("response missing expected header") .to_str() .expect("failed to convert HeaderValue to str") - .to_owned(), - }); + .to_owned() + } } ResponseField::NewtypeBody(ref field) => { let field_name = field @@ -65,32 +63,36 @@ impl Response { .expect("expected field to have an identifier"); let span = field.span(); - tokens.append_all(quote_spanned! {span=> - #field_name: response_body, - }); + quote_spanned! {span=> + #field_name: response_body + } } } - } + }); - tokens + quote! { + #(#fields,)* + } } pub fn apply_header_fields(&self) -> TokenStream { - let mut tokens = TokenStream::new(); - - for response_field in self.fields.iter() { + let header_calls = self.fields.iter().filter_map(|response_field| { if let ResponseField::Header(ref field, ref header) = *response_field { let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); - tokens.append_all(quote_spanned! {span=> + Some(quote_spanned! {span=> .header(::http::header::#header_name, response.#field_name) - }); + }) + } else { + None } - } + }); - tokens + quote! { + #(#header_calls)* + } } pub fn to_body(&self) -> TokenStream { @@ -112,7 +114,11 @@ impl Response { } }); - quote!(ResponseBody{#(#fields),*}) + quote! { + ResponseBody { + #(#fields),* + } + } } } @@ -221,60 +227,53 @@ impl ToTokens for Response { let response_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self.fields.iter().fold(TokenStream::new(), |mut fields_tokens, response_field| { + let fields = self.fields.iter().map(|response_field| { let field = response_field.field(); let span = field.span(); let stripped_field = strip_serde_attrs(field); - fields_tokens.append_all(quote_spanned!(span=> #stripped_field,)); - - fields_tokens + quote_spanned!(span=> #stripped_field) }); quote! { { - #fields + #(#fields),* } } }; - let response_body_struct; - - if let Some(newtype_body_field) = self.newtype_body_field() { + let response_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); - response_body_struct = quote_spanned! {span=> + quote_spanned! {span=> /// Data in the response body. #[derive(Debug, Deserialize, Serialize)] struct ResponseBody(#ty); - }; + } } else if self.has_body_fields() { - let fields = self.fields.iter().fold(TokenStream::new(), |mut field_tokens, response_field| { + let fields = self.fields.iter().filter_map(|response_field| { match *response_field { ResponseField::Body(ref field) => { let span = field.span(); - - field_tokens.append_all(quote_spanned!(span=> #field,)); - - field_tokens + Some(quote_spanned!(span=> #field)) } - _ => field_tokens, + _ => None, } }); - response_body_struct = quote! { + quote! { /// Data in the response body. #[derive(Debug, Deserialize, Serialize)] struct ResponseBody { - #fields + #(#fields),* } - }; + } } else { - response_body_struct = TokenStream::new(); - } + TokenStream::new() + }; tokens.append_all(quote! { #response_struct_header From 0b3dd48c3e894cbd8f086e1927707c901865198a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 8 Jan 2019 20:04:12 +0100 Subject: [PATCH 084/107] Configure rustfmt for nested imports, re-run 'cargo fmt' --- .rustfmt.toml | 1 + src/api/metadata.rs | 3 +-- src/api/mod.rs | 50 +++++++++++++++++++++++++--------------- src/api/request.rs | 45 ++++++++++++++++++++---------------- src/api/response.rs | 42 ++++++++++++++++++--------------- src/lib.rs | 5 ++-- tests/ruma_api_macros.rs | 3 ++- 7 files changed, 87 insertions(+), 62 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..7d2cf549 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 300eb44b..724ea00f 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,5 +1,4 @@ -use syn::punctuated::Pair; -use syn::{Expr, FieldValue, Lit, Member}; +use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member}; pub struct Metadata { pub description: String, diff --git a/src/api/mod.rs b/src/api/mod.rs index 1b807406..2d597432 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,34 +1,40 @@ use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::{braced, Field, FieldValue, Ident, Meta, Token}; -use syn::parse::{Parse, ParseStream, Result}; +use syn::{ + braced, + parse::{Parse, ParseStream, Result}, + Field, FieldValue, Ident, Meta, Token, +}; mod metadata; mod request; mod response; -use self::metadata::Metadata; -use self::request::Request; -use self::response::Response; +use self::{metadata::Metadata, request::Request, response::Response}; pub fn strip_serde_attrs(field: &Field) -> Field { let mut field = field.clone(); - field.attrs = field.attrs.into_iter().filter(|attr| { - let meta = attr.interpret_meta() - .expect("ruma_api! could not parse field attributes"); + field.attrs = field + .attrs + .into_iter() + .filter(|attr| { + let meta = attr + .interpret_meta() + .expect("ruma_api! could not parse field attributes"); - let meta_list = match meta { - Meta::List(meta_list) => meta_list, - _ => return true, - }; + let meta_list = match meta { + Meta::List(meta_list) => meta_list, + _ => return true, + }; - if &meta_list.ident.to_string() == "serde" { - return false; - } + if &meta_list.ident.to_string() == "serde" { + return false; + } - true - }).collect(); + true + }) + .collect(); field } @@ -200,7 +206,10 @@ impl ToTokens for Api { }; let create_http_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); @@ -239,7 +248,10 @@ impl ToTokens for Api { }; let parse_request_body = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); quote! { #field_name: request_body, diff --git a/src/api/request.rs b/src/api/request.rs index 54c4453c..11b5baae 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,7 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::spanned::Spanned; -use syn::{Field, Ident, Lit, Meta, NestedMeta}; +use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -81,10 +80,13 @@ impl Request { } pub fn path_field(&self, name: &str) -> Option<&Field> { - self.fields.iter() + self.fields + .iter() .flat_map(|f| f.field_(RequestFieldKind::Path)) .find(|field| { - field.ident.as_ref() + field + .ident + .as_ref() .expect("expected field to have an identifier") .to_string() == name @@ -220,9 +222,7 @@ impl From> for Request { RequestField::new(field_kind, field, header) }).collect(); - Request { - fields, - } + Request { fields } } } @@ -264,15 +264,16 @@ impl ToTokens for Request { struct RequestBody(#ty); } } else if self.has_body_fields() { - let fields = self.fields.iter().filter_map(|request_field| { - match *request_field { + let fields = self + .fields + .iter() + .filter_map(|request_field| match *request_field { RequestField::Body(ref field) => { let span = field.span(); Some(quote_spanned!(span=> #field)) } _ => None, - } - }); + }); quote! { /// Data in the request body. @@ -286,16 +287,17 @@ impl ToTokens for Request { }; let request_path_struct = if self.has_path_fields() { - let fields = self.fields.iter().filter_map(|request_field| { - match *request_field { + let fields = self + .fields + .iter() + .filter_map(|request_field| match *request_field { RequestField::Path(ref field) => { let span = field.span(); Some(quote_spanned!(span=> #field)) } _ => None, - } - }); + }); quote! { /// Data in the request path. @@ -309,15 +311,16 @@ impl ToTokens for Request { }; let request_query_struct = if self.has_query_fields() { - let fields = self.fields.iter().filter_map(|request_field| { - match *request_field { + let fields = self + .fields + .iter() + .filter_map(|request_field| match *request_field { RequestField::Query(ref field) => { let span = field.span(); Some(quote_spanned!(span=> #field)) } _ => None, - } - }); + }); quote! { /// Data in the request's query string. @@ -352,7 +355,9 @@ impl RequestField { fn new(kind: RequestFieldKind, field: Field, header: Option) -> RequestField { match kind { RequestFieldKind::Body => RequestField::Body(field), - RequestFieldKind::Header => RequestField::Header(field, header.expect("missing header name")), + RequestFieldKind::Header => { + RequestField::Header(field, header.expect("missing header name")) + } RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), diff --git a/src/api/response.rs b/src/api/response.rs index 98f0e8ac..80755b24 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,7 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::spanned::Spanned; -use syn::{Field, Ident, Lit, Meta, NestedMeta}; +use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -27,8 +26,10 @@ impl Response { } pub fn init_fields(&self) -> TokenStream { - let fields = self.fields.iter().map(|response_field| { - match *response_field { + let fields = self + .fields + .iter() + .map(|response_field| match *response_field { ResponseField::Body(ref field) => { let field_name = field .ident @@ -67,8 +68,7 @@ impl Response { #field_name: response_body } } - } - }); + }); quote! { #(#fields,)* @@ -78,7 +78,10 @@ impl Response { pub fn apply_header_fields(&self) -> TokenStream { let header_calls = self.fields.iter().filter_map(|response_field| { if let ResponseField::Header(ref field, ref header) = *response_field { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); @@ -97,13 +100,19 @@ impl Response { pub fn to_body(&self) -> TokenStream { if let Some(ref field) = self.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); let span = field.span(); quote_spanned!(span=> response.#field_name) } else { let fields = self.fields.iter().filter_map(|response_field| { if let ResponseField::Body(ref field) = *response_field { - let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); let span = field.span(); Some(quote_spanned! {span=> @@ -126,7 +135,6 @@ impl Response { for response_field in self.fields.iter() { match *response_field { ResponseField::NewtypeBody(ref field) => { - return Some(field); } _ => continue, @@ -135,7 +143,6 @@ impl Response { None } - } impl From> for Response { @@ -210,9 +217,7 @@ impl From> for Response { } }).collect(); - Response { - fields, - } + Response { fields } } } @@ -254,15 +259,16 @@ impl ToTokens for Response { struct ResponseBody(#ty); } } else if self.has_body_fields() { - let fields = self.fields.iter().filter_map(|response_field| { - match *response_field { + let fields = self + .fields + .iter() + .filter_map(|response_field| match *response_field { ResponseField::Body(ref field) => { let span = field.span(); Some(quote_spanned!(span=> #field)) } _ => None, - } - }); + }); quote! { /// Data in the response body. diff --git a/src/lib.rs b/src/lib.rs index 1bd72a3c..e5d5894e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,12 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![recursion_limit="256"] +#![recursion_limit = "256"] extern crate proc_macro; extern crate proc_macro2; -#[macro_use] extern crate quote; +#[macro_use] +extern crate quote; extern crate ruma_api; extern crate syn; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index f6735ea1..f292e43b 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -6,7 +6,8 @@ extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate serde_derive; extern crate serde_json; extern crate serde_urlencoded; extern crate url; From d3322bec113e25741375fddb0615f6bab8040806 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 13 Jan 2019 21:39:27 +0100 Subject: [PATCH 085/107] Update to Rust 2018 --- Cargo.toml | 1 + src/api/mod.rs | 4 ++-- src/api/request.rs | 6 +++--- src/api/response.rs | 6 +++--- src/lib.rs | 23 ++++------------------- tests/ruma_api_macros.rs | 13 +------------ 6 files changed, 14 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98361860..07359338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" version = "0.3.0" +edition = "2018" [dependencies] quote = "0.6.10" diff --git a/src/api/mod.rs b/src/api/mod.rs index 2d597432..3e19888f 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,5 @@ use proc_macro2::{Span, TokenStream}; -use quote::{ToTokens, TokenStreamExt}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ braced, parse::{Parse, ParseStream, Result}, @@ -478,7 +478,7 @@ pub struct RawApi { } impl Parse for RawApi { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream<'_>) -> Result { input.parse::()?; let metadata; braced!(metadata in input); diff --git a/src/api/request.rs b/src/api/request.rs index 11b5baae..00238776 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,8 +1,8 @@ use proc_macro2::{Span, TokenStream}; -use quote::{ToTokens, TokenStreamExt}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; -use api::strip_serde_attrs; +use crate::api::strip_serde_attrs; pub struct Request { fields: Vec, @@ -254,7 +254,7 @@ impl ToTokens for Request { }; let request_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { - let mut field = newtype_body_field.clone(); + let field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); diff --git a/src/api/response.rs b/src/api/response.rs index 80755b24..a0eb0688 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,8 +1,8 @@ use proc_macro2::{Span, TokenStream}; -use quote::{ToTokens, TokenStreamExt}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; -use api::strip_serde_attrs; +use crate::api::strip_serde_attrs; pub struct Response { fields: Vec, @@ -249,7 +249,7 @@ impl ToTokens for Response { }; let response_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { - let mut field = newtype_body_field.clone(); + let field = newtype_body_field.clone(); let ty = &field.ty; let span = field.span(); diff --git a/src/lib.rs b/src/lib.rs index e5d5894e..54149958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,17 +7,11 @@ #![recursion_limit = "256"] extern crate proc_macro; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -extern crate ruma_api; -extern crate syn; use proc_macro::TokenStream; - use quote::ToTokens; -use api::{Api, RawApi}; +use crate::api::{Api, RawApi}; mod api; @@ -122,22 +116,12 @@ mod api; /// # Examples /// /// ```rust,no_run -/// #![feature(proc_macro, try_from)] -/// -/// extern crate futures; -/// extern crate http; -/// extern crate hyper; -/// extern crate ruma_api; -/// extern crate ruma_api_macros; -/// extern crate serde; -/// #[macro_use] extern crate serde_derive; -/// extern crate serde_json; -/// extern crate serde_urlencoded; -/// extern crate url; +/// #![feature(try_from)] /// /// # fn main() { /// pub mod some_endpoint { /// use ruma_api_macros::ruma_api; +/// use serde_derive::{Deserialize, Serialize}; /// /// ruma_api! { /// metadata { @@ -173,6 +157,7 @@ mod api; /// /// pub mod newtype_body_endpoint { /// use ruma_api_macros::ruma_api; +/// use serde_derive::{Deserialize, Serialize}; /// /// #[derive(Clone, Debug, Deserialize, Serialize)] /// pub struct MyCustomType { diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index f292e43b..7f007935 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,19 +1,8 @@ #![feature(try_from)] -extern crate futures; -extern crate http; -extern crate hyper; -extern crate ruma_api; -extern crate ruma_api_macros; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; -extern crate serde_urlencoded; -extern crate url; - pub mod some_endpoint { use ruma_api_macros::ruma_api; + use serde_derive::{Deserialize, Serialize}; ruma_api! { metadata { From f761a8f837f44915c0abe5ae42a44a09cb832790 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 30 Jan 2019 22:50:06 +0100 Subject: [PATCH 086/107] Fix trait imports in generated code without rename Previously, the generated code would fail to compile when the 'derive' feature on the serde crate was enabled --- src/api/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 3e19888f..e25316a4 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -329,8 +329,8 @@ impl ToTokens for Api { #[allow(unused_imports)] use ::futures::{Future as _Future, IntoFuture as _IntoFuture, Stream as _Stream}; use ::ruma_api::Endpoint as _RumaApiEndpoint; - use ::serde::Deserialize; - use ::serde::de::{Error as _SerdeError, IntoDeserializer}; + use ::serde::Deserialize as _Deserialize; + use ::serde::de::{Error as _SerdeError, IntoDeserializer as _IntoDeserializer}; use ::std::convert::{TryInto as _TryInto}; From ebead04bb90b1eb41360ee443b81be4f6bac7397 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 1 Feb 2019 13:57:39 -0800 Subject: [PATCH 087/107] Bump version to 0.3.1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 07359338..7faa1182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.3.0" +version = "0.3.1" edition = "2018" [dependencies] From 26842652fcbcee3dd73c824bf85e8561b5d021fc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 6 Feb 2019 20:17:38 +0100 Subject: [PATCH 088/107] Replace serde_derive by re-exports in serde --- Cargo.toml | 8 +++++--- src/lib.rs | 4 ++-- tests/ruma_api_macros.rs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7faa1182..131220e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,11 +27,13 @@ features = ["nightly"] futures = "0.1.25" http = "0.1.14" hyper = "0.12.16" -serde = "1.0.80" -serde_derive = "1.0.80" -serde_json = "1.0.33" +serde_json = "1.0.38" serde_urlencoded = "0.5.4" url = "1.7.2" +[dev-dependencies.serde] +version = "1.0.87" +features = ["derive"] + [lib] proc-macro = true diff --git a/src/lib.rs b/src/lib.rs index 54149958..f57bc85d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ mod api; /// # fn main() { /// pub mod some_endpoint { /// use ruma_api_macros::ruma_api; -/// use serde_derive::{Deserialize, Serialize}; +/// use serde::{Deserialize, Serialize}; /// /// ruma_api! { /// metadata { @@ -157,7 +157,7 @@ mod api; /// /// pub mod newtype_body_endpoint { /// use ruma_api_macros::ruma_api; -/// use serde_derive::{Deserialize, Serialize}; +/// use serde::{Deserialize, Serialize}; /// /// #[derive(Clone, Debug, Deserialize, Serialize)] /// pub struct MyCustomType { diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 7f007935..cd2e36f0 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -2,7 +2,7 @@ pub mod some_endpoint { use ruma_api_macros::ruma_api; - use serde_derive::{Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; ruma_api! { metadata { From 6659df84957720d77b039346c04774aef7bc94c4 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:15:26 -0700 Subject: [PATCH 089/107] Update dependencies. --- Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 131220e2..f6e0f894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,27 +12,27 @@ version = "0.3.1" edition = "2018" [dependencies] -quote = "0.6.10" -ruma-api = "0.6.0" +quote = "0.6.12" +ruma-api = "0.7.0" [dependencies.syn] -version = "0.15.22" +version = "0.15.30" features = ["full"] [dependencies.proc-macro2] -version = "0.4.24" +version = "0.4.27" features = ["nightly"] [dev-dependencies] -futures = "0.1.25" -http = "0.1.14" -hyper = "0.12.16" -serde_json = "1.0.38" +futures = "0.1.26" +http = "0.1.17" +hyper = "0.12.27" +serde_json = "1.0.39" serde_urlencoded = "0.5.4" url = "1.7.2" [dev-dependencies.serde] -version = "1.0.87" +version = "1.0.90" features = ["derive"] [lib] From 2e60cf882649e516cb926dd6015e93d62e7d8a7d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:15:32 -0700 Subject: [PATCH 090/107] Remove try_from feature. --- src/lib.rs | 2 -- tests/ruma_api_macros.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f57bc85d..8e703817 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,8 +116,6 @@ mod api; /// # Examples /// /// ```rust,no_run -/// #![feature(try_from)] -/// /// # fn main() { /// pub mod some_endpoint { /// use ruma_api_macros::ruma_api; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index cd2e36f0..55a34917 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,5 +1,3 @@ -#![feature(try_from)] - pub mod some_endpoint { use ruma_api_macros::ruma_api; use serde::{Deserialize, Serialize}; From 09a337f42afbf3e4b05d6f87a2d872a727a463c8 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:16:01 -0700 Subject: [PATCH 091/107] Bump version to 0.4.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f6e0f894..1f12410e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.3.1" +version = "0.4.0" edition = "2018" [dependencies] From 5f116e4e49a9d3ec7b0f930afe1b314883322007 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 20 Apr 2019 13:30:26 +0200 Subject: [PATCH 092/107] Remove remaining uses of quote::TokenStreamExt fixes #4 --- src/api/mod.rs | 8 +++++--- src/api/request.rs | 8 +++++--- src/api/response.rs | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index e25316a4..91b54c0b 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,5 @@ use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::{quote, ToTokens}; use syn::{ braced, parse::{Parse, ParseStream, Result}, @@ -325,7 +325,7 @@ impl ToTokens for Api { } }; - tokens.append_all(quote! { + let api = quote! { #[allow(unused_imports)] use ::futures::{Future as _Future, IntoFuture as _IntoFuture, Stream as _Stream}; use ::ruma_api::Endpoint as _RumaApiEndpoint; @@ -459,7 +459,9 @@ impl ToTokens for Api { requires_authentication: #requires_authentication, }; } - }); + }; + + api.to_tokens(tokens); } } diff --git a/src/api/request.rs b/src/api/request.rs index 00238776..07fee5dd 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,5 +1,5 @@ use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; +use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; @@ -333,13 +333,15 @@ impl ToTokens for Request { TokenStream::new() }; - tokens.append_all(quote! { + let request = quote! { #request_struct_header #request_struct_body #request_body_struct #request_path_struct #request_query_struct - }); + }; + + request.to_tokens(tokens); } } diff --git a/src/api/response.rs b/src/api/response.rs index a0eb0688..4b6a25e7 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; +use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; @@ -281,11 +281,13 @@ impl ToTokens for Response { TokenStream::new() }; - tokens.append_all(quote! { + let response = quote! { #response_struct_header #response_struct_body #response_body_struct - }); + }; + + response.to_tokens(tokens); } } From c3a5741f5ec6848fbe0b5bfa2ad6fe1e736ce270 Mon Sep 17 00:00:00 2001 From: GondwanaNuna <46540468+GondwanaNuna@users.noreply.github.com> Date: Fri, 3 May 2019 09:42:25 -0700 Subject: [PATCH 093/107] Make trait imports more readable (#18) --- src/api/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 91b54c0b..77a14b4f 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -327,12 +327,12 @@ impl ToTokens for Api { let api = quote! { #[allow(unused_imports)] - use ::futures::{Future as _Future, IntoFuture as _IntoFuture, Stream as _Stream}; - use ::ruma_api::Endpoint as _RumaApiEndpoint; - use ::serde::Deserialize as _Deserialize; - use ::serde::de::{Error as _SerdeError, IntoDeserializer as _IntoDeserializer}; + use ::futures::{Future as _, IntoFuture as _, Stream as _}; + use ::ruma_api::Endpoint as _; + use ::serde::Deserialize as _; + use ::serde::de::{Error as _, IntoDeserializer as _}; - use ::std::convert::{TryInto as _TryInto}; + use ::std::convert::{TryInto as _}; /// The API endpoint. #[derive(Debug)] @@ -360,7 +360,7 @@ impl ToTokens for Api { } impl ::futures::future::FutureFrom<::http::Request<::hyper::Body>> for Request { - type Future = Box<_Future + Send>; + type Future = Box<::futures::Future + Send>; type Error = ::ruma_api::Error; #[allow(unused_variables)] @@ -422,7 +422,7 @@ impl ToTokens for Api { } impl ::futures::future::FutureFrom<::http::Response<::hyper::Body>> for Response { - type Future = Box<_Future + Send>; + type Future = Box<::futures::Future + Send>; type Error = ::ruma_api::Error; #[allow(unused_variables)] From a92e71f873ef95eb18c7abe8d7bdbd499545bb40 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 09:12:50 -0700 Subject: [PATCH 094/107] Use stable Rust on Travis. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b55557f..d148d7b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,3 @@ notifications: channels: - secure: "FiHwNDkLqlzn+fZnn42uZ+GWm59S9OJreUIz9r7+nXrxUBeBcthQlqamJUiuYryVohzqLydBVv6xmT5wgS/LxRnj4f363eBpKejuSktglnf2rl8JjuSXZVgrPMDmrfgkBdC+aMCPzdw2fIHSWmvQMr/t9kGW9cHl0VlLxPAhnAsry+E1Kxrrz4IuOJmyb43VqPf/GO6VCDzTpHiKHKe5Rp7i2IkbGus2GiSD/UMrgUTWmMOFoejl7fWX7SH9kvSrN/SCYldVOYA4nazeZfaHv7mCX6G8U3GGXTHwjAVAluXyYgUCYpsYKC5KGkUJFcLhjaBu5qpmlI0EZd/rsgscOBzqfJ0D/WkahWiKtlQEKZ7UEAhA3SaAhcrSh2kSQFf2GW1T8kfzqlnBtjpqSvCFuOpY5XQcSYEEX7qxT1aiK2UBi9iAKgMnG1SDEfeFERekw0KJPKbwJDMV7NhCg9kYVBHG1hxvFeYqMmnFrjLlRDQQrbDHrP9Avdtg0FScolsFVmT+uatBuRXDcqunssolfnWguyrQ0Z9KGauv0iqkwFwO7jQSA9f87wgsuzqlzstHRxoGGlPtGt4J/+MhyA3lOEXwBa5eotjILI7iykK+ykJ33cOTGcqyXbkWoYRZ6+fS2guI+f2CxxsYWUOK2UgMyYKEwtraC3duVIGtQR+zuvc=" use_notice: true -rust: - - "nightly" From 6a09f1f754e36084dd3271bfa437f4ef746873b9 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 17:35:26 -0700 Subject: [PATCH 095/107] Add rustfmt and clippy to CI and address clippy warnings. --- .rustfmt.toml | 1 - .travis.yml | 8 ++++++ src/api/metadata.rs | 11 +++++++- src/api/mod.rs | 19 +++++++++++--- src/api/request.rs | 63 +++++++++++++++++++++++++++++++++++++-------- src/api/response.rs | 35 ++++++++++++++++++++----- src/lib.rs | 26 ++++++++++++++++++- 7 files changed, 140 insertions(+), 23 deletions(-) delete mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 7d2cf549..00000000 --- a/.rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -merge_imports = true diff --git a/.travis.yml b/.travis.yml index d148d7b4..e9812c08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,12 @@ language: "rust" +before_script: + - "rustup component add rustfmt" + - "rustup component add clippy" +script: + - "cargo fmt --all -- --check" + - "cargo clippy --all-targets --all-features -- -D warnings" + - "cargo build --verbose" + - "cargo test --verbose" notifications: email: false irc: diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 724ea00f..69fc7523 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,11 +1,20 @@ +//! Details of the `metadata` section of the procedural macro. + use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member}; +/// The result of processing the `metadata` section of the macro. pub struct Metadata { + /// The description field. pub description: String, + /// The method field. pub method: String, + /// The name field. pub name: String, + /// The path field. pub path: String, + /// The rate_limited field. pub rate_limited: bool, + /// The description field. pub requires_authentication: bool, } @@ -101,7 +110,7 @@ impl From> for Metadata { } } - Metadata { + Self { description: description.expect("ruma_api! `metadata` is missing `description`"), method: method.expect("ruma_api! `metadata` is missing `method`"), name: name.expect("ruma_api! `metadata` is missing `name`"), diff --git a/src/api/mod.rs b/src/api/mod.rs index 77a14b4f..d8b3eb03 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,5 @@ +//! Details of the `ruma-api` procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ @@ -12,6 +14,7 @@ mod response; use self::{metadata::Metadata, request::Request, response::Response}; +/// Removes `serde` attributes from struct fields. pub fn strip_serde_attrs(field: &Field) -> Field { let mut field = field.clone(); @@ -39,15 +42,19 @@ pub fn strip_serde_attrs(field: &Field) -> Field { field } +/// The result of processing the `ruma_api` macro, ready for output back to source code. pub struct Api { + /// The `metadata` section of the macro. metadata: Metadata, + /// The `request` section of the macro. request: Request, + /// The `response` section of the macro. response: Response, } impl From for Api { fn from(raw_api: RawApi) -> Self { - Api { + Self { metadata: raw_api.metadata.into(), request: raw_api.request.into(), response: raw_api.response.into(), @@ -88,7 +95,7 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); - let path_segments = path_str[1..].split('/').into_iter(); + let path_segments = path_str[1..].split('/'); let path_segment_push = path_segments.clone().map(|segment| { let arg = if segment.starts_with(':') { let path_var = &segment[1..]; @@ -450,6 +457,7 @@ impl ToTokens for Api { type Request = Request; type Response = Response; + /// Metadata for this endpoint. const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { description: #description, method: ::http::Method::#method, @@ -465,6 +473,7 @@ impl ToTokens for Api { } } +/// Custom keyword macros for syn. mod kw { use syn::custom_keyword; @@ -473,9 +482,13 @@ mod kw { custom_keyword!(response); } +/// The entire `ruma_api!` macro structure directly as it appears in the source code.. pub struct RawApi { + /// The `metadata` section of the macro. pub metadata: Vec, + /// The `request` section of the macro. pub request: Vec, + /// The `response` section of the macro. pub response: Vec, } @@ -493,7 +506,7 @@ impl Parse for RawApi { let response; braced!(response in input); - Ok(RawApi { + Ok(Self { metadata: metadata .parse_terminated::(FieldValue::parse)? .into_iter() diff --git a/src/api/request.rs b/src/api/request.rs index 07fee5dd..af2cb2d3 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,14 +1,19 @@ +//! Details of the `request` section of the procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; +/// The result of processing the `request` section of the macro. pub struct Request { + /// The fields of the request. fields: Vec, } impl Request { + /// Produces code to add necessary HTTP headers to an `http::Request`. pub fn add_headers_to_request(&self) -> TokenStream { let append_stmts = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { @@ -33,6 +38,7 @@ impl Request { } } + /// Produces code to extract fields from the HTTP headers in an `http::Request`. pub fn parse_headers_from_request(&self) -> TokenStream { let fields = self.header_fields().map(|request_field| { let (field, header_name_string) = match request_field { @@ -56,43 +62,50 @@ impl Request { } } + /// Whether or not this request has any data in the HTTP body. pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + /// Whether or not this request has any data in HTTP headers. pub fn has_header_fields(&self) -> bool { self.fields.iter().any(|field| field.is_header()) } + /// Whether or not this request has any data in the URL path. pub fn has_path_fields(&self) -> bool { self.fields.iter().any(|field| field.is_path()) } + /// Whether or not this request has any data in the query string. pub fn has_query_fields(&self) -> bool { self.fields.iter().any(|field| field.is_query()) } + /// Produces an iterator over all the header fields. pub fn header_fields(&self) -> impl Iterator { self.fields.iter().filter(|field| field.is_header()) } + /// Gets the number of path fields. pub fn path_field_count(&self) -> usize { self.fields.iter().filter(|field| field.is_path()).count() } + /// Gets the path field with the given name. pub fn path_field(&self, name: &str) -> Option<&Field> { self.fields .iter() - .flat_map(|f| f.field_(RequestFieldKind::Path)) + .flat_map(|f| f.field_of_kind(RequestFieldKind::Path)) .find(|field| { field .ident .as_ref() .expect("expected field to have an identifier") - .to_string() == name }) } + /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -106,33 +119,41 @@ impl Request { None } + /// Produces code for a struct initializer for body fields on a variable named `request`. pub fn request_body_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body, quote!(request)) } + /// Produces code for a struct initializer for path fields on a variable named `request`. pub fn request_path_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Path, quote!(request)) } + /// Produces code for a struct initializer for query string fields on a variable named `request`. pub fn request_query_init_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query, quote!(request)) } + /// Produces code for a struct initializer for body fields on a variable named `request_body`. pub fn request_init_body_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Body, quote!(request_body)) } + /// Produces code for a struct initializer for query string fields on a variable named + /// `request_query`. pub fn request_init_query_fields(&self) -> TokenStream { self.struct_init_fields(RequestFieldKind::Query, quote!(request_query)) } + /// Produces code for a struct initializer for the given field kind to be accessed through the + /// given variable name. fn struct_init_fields( &self, request_field_kind: RequestFieldKind, src: TokenStream, ) -> TokenStream { let fields = self.fields.iter().filter_map(|f| { - f.field_(request_field_kind).map(|field| { + f.field_of_kind(request_field_kind).map(|field| { let field_name = field .ident .as_ref() @@ -222,7 +243,7 @@ impl From> for Request { RequestField::new(field_kind, field, header) }).collect(); - Request { fields } + Self { fields } } } @@ -345,16 +366,23 @@ impl ToTokens for Request { } } +/// The types of fields that a request can have. pub enum RequestField { + /// JSON data in the body of the request. Body(Field), + /// Data in an HTTP header. Header(Field, String), + /// A specific data type in the body of the request. NewtypeBody(Field), + /// Data that appears in the URL path. Path(Field), + /// Data that appears in the query string. Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: Field, header: Option) -> RequestField { + /// Creates a new `RequestField`. + fn new(kind: RequestFieldKind, field: Field, header: Option) -> Self { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => { @@ -366,6 +394,7 @@ impl RequestField { } } + /// Gets the kind of the request field. fn kind(&self) -> RequestFieldKind { match *self { RequestField::Body(..) => RequestFieldKind::Body, @@ -376,33 +405,39 @@ impl RequestField { } } + /// Whether or not this request field is a body kind. fn is_body(&self) -> bool { self.kind() == RequestFieldKind::Body } + /// Whether or not this request field is a header kind. fn is_header(&self) -> bool { self.kind() == RequestFieldKind::Header } + /// Whether or not this request field is a path kind. fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path } + /// Whether or not this request field is a query string kind. fn is_query(&self) -> bool { self.kind() == RequestFieldKind::Query } + /// Gets the inner `Field` value. fn field(&self) -> &Field { match *self { - RequestField::Body(ref field) => field, - RequestField::Header(ref field, _) => field, - RequestField::NewtypeBody(ref field) => field, - RequestField::Path(ref field) => field, - RequestField::Query(ref field) => field, + RequestField::Body(ref field) + | RequestField::Header(ref field, _) + | RequestField::NewtypeBody(ref field) + | RequestField::Path(ref field) + | RequestField::Query(ref field) => field, } } - fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { + /// Gets the inner `Field` value if it's of the provided kind. + fn field_of_kind(&self, kind: RequestFieldKind) -> Option<&Field> { if self.kind() == kind { Some(self.field()) } else { @@ -411,11 +446,17 @@ impl RequestField { } } +/// The types of fields that a request can have, without their values. #[derive(Clone, Copy, PartialEq, Eq)] enum RequestFieldKind { + /// See the similarly named variant of `RequestField`. Body, + /// See the similarly named variant of `RequestField`. Header, + /// See the similarly named variant of `RequestField`. NewtypeBody, + /// See the similarly named variant of `RequestField`. Path, + /// See the similarly named variant of `RequestField`. Query, } diff --git a/src/api/response.rs b/src/api/response.rs index 4b6a25e7..d860ed39 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,30 +1,39 @@ +//! Details of the `response` section of the procedural macro. + use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; use crate::api::strip_serde_attrs; +/// The result of processing the `request` section of the macro. pub struct Response { + /// The fields of the response. fields: Vec, } impl Response { + /// Whether or not this response has any data in the HTTP body. pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) } + /// Whether or not this response has any fields. pub fn has_fields(&self) -> bool { !self.fields.is_empty() } + /// Whether or not this response has any data in HTTP headers. pub fn has_header_fields(&self) -> bool { self.fields.iter().any(|field| field.is_header()) } + /// Whether or not this response has any data in the HTTP body. pub fn has_body(&self) -> bool { self.fields.iter().any(|field| !field.is_header()) } + /// Produces code for a request struct initializer. pub fn init_fields(&self) -> TokenStream { let fields = self .fields @@ -75,6 +84,7 @@ impl Response { } } + /// Produces code to add necessary HTTP headers to an `http::Response`. pub fn apply_header_fields(&self) -> TokenStream { let header_calls = self.fields.iter().filter_map(|response_field| { if let ResponseField::Header(ref field, ref header) = *response_field { @@ -98,8 +108,9 @@ impl Response { } } + /// Produces code to initialize the struct that will be used to create the response body. pub fn to_body(&self) -> TokenStream { - if let Some(ref field) = self.newtype_body_field() { + if let Some(field) = self.newtype_body_field() { let field_name = field .ident .as_ref() @@ -131,6 +142,7 @@ impl Response { } } + /// Gets the newtype body field, if this request has one. pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { @@ -217,7 +229,7 @@ impl From> for Response { } }).collect(); - Response { fields } + Self { fields } } } @@ -291,28 +303,35 @@ impl ToTokens for Response { } } +/// The types of fields that a response can have. pub enum ResponseField { + /// JSON data in the body of the response. Body(Field), + /// Data in an HTTP header. Header(Field, String), + /// A specific data type in the body of the response. NewtypeBody(Field), } impl ResponseField { + /// Gets the inner `Field` value. fn field(&self) -> &Field { match *self { - ResponseField::Body(ref field) => field, - ResponseField::Header(ref field, _) => field, - ResponseField::NewtypeBody(ref field) => field, + ResponseField::Body(ref field) + | ResponseField::Header(ref field, _) + | ResponseField::NewtypeBody(ref field) => field, } } + /// Whether or not this response field is a body kind. fn is_body(&self) -> bool { match *self { - ResponseField::Body(..) => true, + ResponseField::Body(_) => true, _ => false, } } + /// Whether or not this response field is a header kind. fn is_header(&self) -> bool { match *self { ResponseField::Header(..) => true, @@ -321,8 +340,12 @@ impl ResponseField { } } +/// The types of fields that a response can have, without their values. enum ResponseFieldKind { + /// See the similarly named variant of `ResponseField`. Body, + /// See the similarly named variant of `ResponseField`. Header, + /// See the similarly named variant of `ResponseField`. NewtypeBody, } diff --git a/src/lib.rs b/src/lib.rs index 8e703817..e260765e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,31 @@ //! //! See the documentation for the `ruma_api!` macro for usage details. -#![deny(missing_debug_implementations)] +#![deny( + missing_copy_implementations, + missing_debug_implementations, + // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released. + warnings +)] +#![warn( + clippy::empty_line_after_outer_attr, + clippy::expl_impl_clone_on_copy, + clippy::if_not_else, + clippy::items_after_statements, + clippy::match_same_arms, + clippy::mem_forget, + clippy::missing_docs_in_private_items, + clippy::multiple_inherent_impl, + clippy::mut_mut, + clippy::needless_borrow, + clippy::needless_continue, + clippy::single_match_else, + clippy::unicode_not_nfc, + clippy::use_self, + clippy::used_underscore_binding, + clippy::wrong_pub_self_convention, + clippy::wrong_self_convention +)] #![recursion_limit = "256"] extern crate proc_macro; From 557ac4b4855d5070e89d9c81687f217438fd9e4d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:06:35 -0700 Subject: [PATCH 096/107] Use the name and description fields to generate better documentation. --- src/api/mod.rs | 10 ++++++++-- src/api/request.rs | 1 - src/api/response.rs | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index d8b3eb03..dc56d608 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -332,6 +332,10 @@ impl ToTokens for Api { } }; + let endpoint_doc = format!("The `{}` API endpoint.\n\n{}", name, description); + let request_doc = format!("Data for a request to the `{}` API endpoint.", name); + let response_doc = format!("Data in the response from the `{}` API endpoint.", name); + let api = quote! { #[allow(unused_imports)] use ::futures::{Future as _, IntoFuture as _, Stream as _}; @@ -341,10 +345,11 @@ impl ToTokens for Api { use ::std::convert::{TryInto as _}; - /// The API endpoint. + #[doc = #endpoint_doc] #[derive(Debug)] pub struct Endpoint; + #[doc = #request_doc] #request_types impl ::std::convert::TryFrom<::http::Request>> for Request { @@ -412,6 +417,7 @@ impl ToTokens for Api { } } + #[doc = #response_doc] #response_types impl ::std::convert::TryFrom for ::http::Response<::hyper::Body> { @@ -457,7 +463,7 @@ impl ToTokens for Api { type Request = Request; type Response = Response; - /// Metadata for this endpoint. + /// Metadata for the `#name` endpoint. const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { description: #description, method: ::http::Method::#method, diff --git a/src/api/request.rs b/src/api/request.rs index af2cb2d3..67d26968 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -250,7 +250,6 @@ impl From> for Request { impl ToTokens for Request { fn to_tokens(&self, tokens: &mut TokenStream) { let request_struct_header = quote! { - /// Data for a request to this API endpoint. #[derive(Debug, Clone)] pub struct Request }; diff --git a/src/api/response.rs b/src/api/response.rs index d860ed39..7405338a 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -236,7 +236,6 @@ impl From> for Response { impl ToTokens for Response { fn to_tokens(&self, tokens: &mut TokenStream) { let response_struct_header = quote! { - /// Data in the response from this API endpoint. #[derive(Debug, Clone)] pub struct Response }; From 82a20e23dafd1a0f709bcf47161c16a75b289e05 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:11:06 -0700 Subject: [PATCH 097/107] Bump dependencies. --- Cargo.toml | 14 +++++++------- src/api/mod.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f12410e..1d15857b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,26 +13,26 @@ edition = "2018" [dependencies] quote = "0.6.12" -ruma-api = "0.7.0" +ruma-api = "0.8.0" [dependencies.syn] -version = "0.15.30" +version = "0.15.35" features = ["full"] [dependencies.proc-macro2] -version = "0.4.27" +version = "0.4.30" features = ["nightly"] [dev-dependencies] -futures = "0.1.26" +futures = "0.1.27" http = "0.1.17" -hyper = "0.12.27" +hyper = "0.12.29" serde_json = "1.0.39" -serde_urlencoded = "0.5.4" +serde_urlencoded = "0.5.5" url = "1.7.2" [dev-dependencies.serde] -version = "1.0.90" +version = "1.0.92" features = ["derive"] [lib] diff --git a/src/api/mod.rs b/src/api/mod.rs index dc56d608..275c29a5 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -454,7 +454,7 @@ impl ToTokens for Api { Box::new(future_response) } else { - Box::new(::futures::future::err(::ruma_api::Error::StatusCode(http_response.status().clone()))) + Box::new(::futures::future::err(http_response.status().clone().into())) } } } From 262b84022c289aec0d9498f1a6315a6db42b4be5 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:11:33 -0700 Subject: [PATCH 098/107] Bump version to 0.5.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d15857b..6a3443fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.4.0" +version = "0.5.0" edition = "2018" [dependencies] From 5461c208758dd3004cbe7415e068bb8ac9d52555 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 16 Jun 2019 16:51:11 -0700 Subject: [PATCH 099/107] Add crates.io categories. [ci skip] --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6a3443fc..2f7ec89f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] authors = ["Jimmy Cuadra "] +categories = ["api-bindings", "web-programming"] description = "A procedural macro for generating ruma-api Endpoints." documentation = "https://docs.rs/ruma-api-macros" homepage = "https://github.com/ruma/ruma-api-macros" From e9df06b30b372254a9b07faee511d67b5ab2cc7f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 17 Jun 2019 17:19:31 -0700 Subject: [PATCH 100/107] Turn off nightly feature for proc-macro2. --- Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f7ec89f..14db7eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,15 +15,12 @@ edition = "2018" [dependencies] quote = "0.6.12" ruma-api = "0.8.0" +proc-macro2 = "0.4.30" [dependencies.syn] version = "0.15.35" features = ["full"] -[dependencies.proc-macro2] -version = "0.4.30" -features = ["nightly"] - [dev-dependencies] futures = "0.1.27" http = "0.1.17" From be79a80467a8accaca40a0321a99606accacc50f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 19 Jul 2019 11:52:49 +0200 Subject: [PATCH 101/107] Update to new ruma-api --- Cargo.toml | 4 +-- README.md | 1 - src/api/mod.rs | 90 ++++++++++++-------------------------------------- 3 files changed, 22 insertions(+), 73 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14db7eb7..c610472c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ edition = "2018" [dependencies] quote = "0.6.12" -ruma-api = "0.8.0" +ruma-api = "0.9.0" proc-macro2 = "0.4.30" [dependencies.syn] @@ -22,9 +22,7 @@ version = "0.15.35" features = ["full"] [dev-dependencies] -futures = "0.1.27" http = "0.1.17" -hyper = "0.12.29" serde_json = "1.0.39" serde_urlencoded = "0.5.5" url = "1.7.2" diff --git a/README.md b/README.md index 494cdd9c..74d14c40 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Here is an example that shows most of the macro's functionality. ``` rust #![feature(proc_macro, try_from)] -extern crate futures; extern crate http; extern crate ruma_api; extern crate ruma_api_macros; diff --git a/src/api/mod.rs b/src/api/mod.rs index 275c29a5..0fc84677 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -221,7 +221,7 @@ impl ToTokens for Api { quote! { let request_body = RequestBody(request.#field_name); - let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?.into()); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -231,11 +231,11 @@ impl ToTokens for Api { #request_body_init_fields }; - let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?.into()); + let mut http_request = ::http::Request::new(::serde_json::to_vec(&request_body)?); } } else { quote! { - let mut http_request = ::http::Request::new(::hyper::Body::empty()); + let mut http_request = ::http::Request::new(Vec::new()); } }; @@ -269,39 +269,20 @@ impl ToTokens for Api { TokenStream::new() }; - let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { + let try_deserialize_response_body = if let Some(field) = self.response.newtype_body_field() + { let field_type = &field.ty; quote! { - let future_response = http_response.into_body() - .fold(Vec::new(), |mut vec, chunk| { - vec.extend(chunk.iter()); - ::futures::future::ok::<_, ::hyper::Error>(vec) - }) - .map_err(::ruma_api::Error::from) - .and_then(|data| - ::serde_json::from_slice::<#field_type>(data.as_slice()) - .map_err(::ruma_api::Error::from) - .into_future() - ) + ::serde_json::from_slice::<#field_type>(http_response.into_body().as_slice())? } } else if self.response.has_body_fields() { quote! { - let future_response = http_response.into_body() - .fold(Vec::new(), |mut vec, chunk| { - vec.extend(chunk.iter()); - ::futures::future::ok::<_, ::hyper::Error>(vec) - }) - .map_err(::ruma_api::Error::from) - .and_then(|data| - ::serde_json::from_slice::(data.as_slice()) - .map_err(::ruma_api::Error::from) - .into_future() - ) + ::serde_json::from_slice::(http_response.into_body().as_slice())? } } else { quote! { - let future_response = ::futures::future::ok(()) + () } }; @@ -321,14 +302,14 @@ impl ToTokens for Api { let serialize_response_headers = self.response.apply_header_fields(); - let serialize_response_body = if self.response.has_body() { + let try_serialize_response_body = if self.response.has_body() { let body = self.response.to_body(); quote! { - .body(::hyper::Body::from(::serde_json::to_vec(&#body)?)) + ::serde_json::to_vec(&#body)? } } else { quote! { - .body(::hyper::Body::from("{}".as_bytes().to_vec())) + "{}".as_bytes().to_vec() } }; @@ -337,8 +318,6 @@ impl ToTokens for Api { let response_doc = format!("Data in the response from the `{}` API endpoint.", name); let api = quote! { - #[allow(unused_imports)] - use ::futures::{Future as _, IntoFuture as _, Stream as _}; use ::ruma_api::Endpoint as _; use ::serde::Deserialize as _; use ::serde::de::{Error as _, IntoDeserializer as _}; @@ -371,27 +350,7 @@ impl ToTokens for Api { } } - impl ::futures::future::FutureFrom<::http::Request<::hyper::Body>> for Request { - type Future = Box<::futures::Future + Send>; - type Error = ::ruma_api::Error; - - #[allow(unused_variables)] - fn future_from(request: ::http::Request<::hyper::Body>) -> Self::Future { - let (parts, body) = request.into_parts(); - let future = body.from_err().fold(Vec::new(), |mut vec, chunk| { - vec.extend(chunk.iter()); - ::futures::future::ok::<_, Self::Error>(vec) - }).and_then(|body| { - ::http::Request::from_parts(parts, body) - .try_into() - .into_future() - .from_err() - }); - Box::new(future) - } - } - - impl ::std::convert::TryFrom for ::http::Request<::hyper::Body> { + impl ::std::convert::TryFrom for ::http::Request> { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -420,7 +379,7 @@ impl ToTokens for Api { #[doc = #response_doc] #response_types - impl ::std::convert::TryFrom for ::http::Response<::hyper::Body> { + impl ::std::convert::TryFrom for ::http::Response> { type Error = ::ruma_api::Error; #[allow(unused_variables)] @@ -428,33 +387,26 @@ impl ToTokens for Api { let response = ::http::Response::builder() .header(::http::header::CONTENT_TYPE, "application/json") #serialize_response_headers - #serialize_response_body + .body(#try_serialize_response_body) .unwrap(); Ok(response) } } - impl ::futures::future::FutureFrom<::http::Response<::hyper::Body>> for Response { - type Future = Box<::futures::Future + Send>; + impl ::std::convert::TryFrom<::http::Response>> for Response { type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response<::hyper::Body>) -> Self::Future { + fn try_from(http_response: ::http::Response>) -> Result { if http_response.status().is_success() { #extract_response_headers - #deserialize_response_body - .and_then(move |response_body| { - let response = Response { - #response_init_fields - }; - - Ok(response) - }); - - Box::new(future_response) + let response_body = #try_deserialize_response_body; + Ok(Response { + #response_init_fields + }) } else { - Box::new(::futures::future::err(http_response.status().clone().into())) + Err(http_response.status().clone().into()) } } } From 11493dede41af33326b6aacb28b2777586b5aff4 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 19 Jul 2019 14:02:53 -0700 Subject: [PATCH 102/107] Bump version to 0.6.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c610472c..02b03e52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api-macros" -version = "0.5.0" +version = "0.6.0" edition = "2018" [dependencies] From fd8367be4c96f9fbb59700be75adc761023fd928 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 23 Jul 2019 09:47:17 -0700 Subject: [PATCH 103/107] Run cargo-audit on CI. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index e9812c08..26a1fc4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ language: "rust" +cache: "cargo" before_script: - "rustup component add rustfmt" - "rustup component add clippy" + - "cargo install --force cargo-audit" + - "cargo generate-lockfile" script: + - "cargo audit" - "cargo fmt --all -- --check" - "cargo clippy --all-targets --all-features -- -D warnings" - "cargo build --verbose" From 777e9c4c70ef00b8e132142ec1df277e39f7c509 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 20 Jul 2019 12:41:54 +0200 Subject: [PATCH 104/107] Stop throwing away span information when parsing metadata --- src/api/metadata.rs | 98 ++++++++++++++++++++------------------------- src/api/mod.rs | 11 +++-- 2 files changed, 50 insertions(+), 59 deletions(-) diff --git a/src/api/metadata.rs b/src/api/metadata.rs index 69fc7523..3d7b6a29 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,21 +1,22 @@ //! Details of the `metadata` section of the procedural macro. -use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member}; +use proc_macro2::Ident; +use syn::{Expr, ExprLit, FieldValue, Lit, LitBool, LitStr, Member}; /// The result of processing the `metadata` section of the macro. pub struct Metadata { /// The description field. - pub description: String, + pub description: LitStr, /// The method field. - pub method: String, + pub method: Ident, /// The name field. - pub name: String, + pub name: LitStr, /// The path field. - pub path: String, + pub path: LitStr, /// The rate_limited field. - pub rate_limited: bool, + pub rate_limited: LitBool, /// The description field. - pub requires_authentication: bool, + pub requires_authentication: LitBool, } impl From> for Metadata { @@ -35,15 +36,13 @@ impl From> for Metadata { match &identifier.to_string()[..] { "description" => { - let expr_lit = match field_value.expr { - Expr::Lit(expr_lit) => expr_lit, - _ => panic!("expected Expr::Lit"), + let literal = match field_value.expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => s, + _ => panic!("expected string literal"), }; - let lit_str = match expr_lit.lit { - Lit::Str(lit_str) => lit_str, - _ => panic!("expected Lit::Str"), - }; - description = Some(lit_str.value()); + description = Some(literal); } "method" => { let expr_path = match field_value.expr { @@ -51,60 +50,49 @@ impl From> for Metadata { _ => panic!("expected Expr::Path"), }; let path = expr_path.path; - let segments = path.segments; - if segments.len() != 1 { - panic!("ruma_api! expects a one component path for `metadata` `method`"); - } - let pair = segments.first().unwrap(); // safe because we just checked - let method_name = match pair { - Pair::End(method_name) => method_name, - _ => panic!("expected Pair::End"), - }; - method = Some(method_name.ident.to_string()); + let mut segments = path.segments.iter(); + let method_name = segments.next().expect("expected non-empty path"); + assert!( + segments.next().is_none(), + "ruma_api! expects a one-component path for `metadata` `method`" + ); + method = Some(method_name.ident.clone()); } "name" => { - let expr_lit = match field_value.expr { - Expr::Lit(expr_lit) => expr_lit, - _ => panic!("expected Expr::Lit"), + let literal = match field_value.expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => s, + _ => panic!("expected string literal"), }; - let lit_str = match expr_lit.lit { - Lit::Str(lit_str) => lit_str, - _ => panic!("expected Lit::Str"), - }; - name = Some(lit_str.value()); + name = Some(literal); } "path" => { - let expr_lit = match field_value.expr { - Expr::Lit(expr_lit) => expr_lit, - _ => panic!("expected Expr::Lit"), + let literal = match field_value.expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => s, + _ => panic!("expected string literal"), }; - let lit_str = match expr_lit.lit { - Lit::Str(lit_str) => lit_str, - _ => panic!("expected Lit::Str"), - }; - path = Some(lit_str.value()); + path = Some(literal); } "rate_limited" => { - let expr_lit = match field_value.expr { - Expr::Lit(expr_lit) => expr_lit, + let literal = match field_value.expr { + Expr::Lit(ExprLit { + lit: Lit::Bool(b), .. + }) => b, _ => panic!("expected Expr::Lit"), }; - let lit_bool = match expr_lit.lit { - Lit::Bool(lit_bool) => lit_bool, - _ => panic!("expected Lit::Bool"), - }; - rate_limited = Some(lit_bool.value) + rate_limited = Some(literal) } "requires_authentication" => { - let expr_lit = match field_value.expr { - Expr::Lit(expr_lit) => expr_lit, + let literal = match field_value.expr { + Expr::Lit(ExprLit { + lit: Lit::Bool(b), .. + }) => b, _ => panic!("expected Expr::Lit"), }; - let lit_bool = match expr_lit.lit { - Lit::Bool(lit_bool) => lit_bool, - _ => panic!("expected Lit::Bool"), - }; - requires_authentication = Some(lit_bool.value) + requires_authentication = Some(literal) } _ => panic!("ruma_api! metadata included unexpected field"), } diff --git a/src/api/mod.rs b/src/api/mod.rs index 0fc84677..00d6eac8 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -65,8 +65,11 @@ impl From for Api { impl ToTokens for Api { fn to_tokens(&self, tokens: &mut TokenStream) { let description = &self.metadata.description; - let method = Ident::new(self.metadata.method.as_ref(), Span::call_site()); - let name = &self.metadata.name; + let method = &self.metadata.method; + // We don't (currently) use this literal as a literal in the generated code. Instead we just + // put it into doc comments, for which the span information is irrelevant. So we can work + // with only the literal's value from here on. + let name = &self.metadata.name.value(); let path = &self.metadata.path; let rate_limited = &self.metadata.rate_limited; let requires_authentication = &self.metadata.requires_authentication; @@ -85,7 +88,7 @@ impl ToTokens for Api { }; let (set_request_path, parse_request_path) = if self.request.has_path_fields() { - let path_str = path.as_str(); + let path_str = path.value(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); assert!( @@ -313,7 +316,7 @@ impl ToTokens for Api { } }; - let endpoint_doc = format!("The `{}` API endpoint.\n\n{}", name, description); + let endpoint_doc = format!("The `{}` API endpoint.\n\n{}", name, description.value()); let request_doc = format!("Data for a request to the `{}` API endpoint.", name); let response_doc = format!("Data in the response from the `{}` API endpoint.", name); From 8f3b141db5918a840943ec54e13587e867b65d57 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 25 Jul 2019 22:00:24 +0200 Subject: [PATCH 105/107] Replace string literals by identifiers in #[ruma_api] attributes --- README.md | 4 +- src/api/attribute.rs | 63 ++++++++++++++++++++++++++++ src/api/mod.rs | 3 +- src/api/request.rs | 91 ++++++++++++++++------------------------ src/api/response.rs | 91 ++++++++++++++++------------------------ src/lib.rs | 4 +- tests/ruma_api_macros.rs | 4 +- 7 files changed, 143 insertions(+), 117 deletions(-) create mode 100644 src/api/attribute.rs diff --git a/README.md b/README.md index 74d14c40..1024dfe7 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ pub mod some_endpoint { pub foo: String, // This value will be put into the "Content-Type" HTTP header. - #[ruma_api(header = "CONTENT_TYPE")] + #[ruma_api(header = CONTENT_TYPE)] pub content_type: String // This value will be put into the query string of the request's URL. @@ -54,7 +54,7 @@ pub mod some_endpoint { response { // This value will be extracted from the "Content-Type" HTTP header. - #[ruma_api(header = "CONTENT_TYPE")] + #[ruma_api(header = CONTENT_TYPE)] pub content_type: String // With no attribute on the field, it will be extracted from the body of the response. diff --git a/src/api/attribute.rs b/src/api/attribute.rs new file mode 100644 index 00000000..0e4cf33f --- /dev/null +++ b/src/api/attribute.rs @@ -0,0 +1,63 @@ +//! Details of the `#[ruma_api(...)]` attributes. + +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + Ident, Token, +}; + +/// Like syn::Meta, but only parses ruma_api attributes +pub enum Meta { + /// A single word, like `query` in `#[ruma_api(query)]` + Word(Ident), + /// A name-value pair, like `header = CONTENT_TYPE` in `#[ruma_api(header = CONTENT_TYPE)]` + NameValue(MetaNameValue), +} + +impl Meta { + pub fn from_attribute(attr: syn::Attribute) -> Result { + match &attr.path { + syn::Path { + leading_colon: None, + segments, + } => { + if segments.len() == 1 && segments[0].ident == "ruma_api" { + Ok( + syn::parse2(attr.tts) + .expect("ruma_api! could not parse request field attributes"), + ) + } else { + Err(attr) + } + } + _ => Err(attr), + } + } +} + +/// Like syn::MetaNameValue, but expects an identifier as the value. Also, we don't care about the +/// the span of the equals sign, so we don't have the `eq_token` field from syn::MetaNameValue. +pub struct MetaNameValue { + /// The part left of the equals sign + pub name: Ident, + /// The part right of the equals sign + pub value: Ident, +} + +impl Parse for Meta { + fn parse(input: ParseStream) -> syn::Result { + let content; + let _ = parenthesized!(content in input); + let ident = content.parse()?; + + if content.peek(Token![=]) { + let _ = content.parse::(); + Ok(Meta::NameValue(MetaNameValue { + name: ident, + value: content.parse()?, + })) + } else { + Ok(Meta::Word(ident)) + } + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 00d6eac8..a656becb 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,4 @@ -//! Details of the `ruma-api` procedural macro. +//! Details of the `ruma_api` procedural macro. use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; @@ -8,6 +8,7 @@ use syn::{ Field, FieldValue, Ident, Meta, Token, }; +mod attribute; mod metadata; mod request; mod response; diff --git a/src/api/request.rs b/src/api/request.rs index 67d26968..865f040e 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,10 +1,13 @@ //! Details of the `request` section of the procedural macro. -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; -use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; +use syn::{spanned::Spanned, Field, Ident}; -use crate::api::strip_serde_attrs; +use crate::api::{ + attribute::{Meta, MetaNameValue}, + strip_serde_attrs, +}; /// The result of processing the `request` section of the macro. pub struct Request { @@ -16,13 +19,12 @@ impl Request { /// Produces code to add necessary HTTP headers to an `http::Request`. pub fn add_headers_to_request(&self) -> TokenStream { let append_stmts = self.header_fields().map(|request_field| { - let (field, header_name_string) = match request_field { - RequestField::Header(field, header_name_string) => (field, header_name_string), + let (field, header_name) = match request_field { + RequestField::Header(field, header_name) => (field, header_name), _ => panic!("expected request field to be header variant"), }; let field_name = &field.ident; - let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); quote! { headers.append( @@ -41,13 +43,13 @@ impl Request { /// Produces code to extract fields from the HTTP headers in an `http::Request`. pub fn parse_headers_from_request(&self) -> TokenStream { let fields = self.header_fields().map(|request_field| { - let (field, header_name_string) = match request_field { - RequestField::Header(field, header_name_string) => (field, header_name_string), + let (field, header_name) = match request_field { + RequestField::Header(field, header_name) => (field, header_name), _ => panic!("expected request field to be header variant"), }; let field_name = &field.ident; - let header_name = Ident::new(header_name_string.as_ref(), Span::call_site()); + let header_name_string = header_name.to_string(); quote! { #field_name: headers.get(::http::header::#header_name) @@ -180,57 +182,36 @@ impl From> for Request { let mut field_kind = RequestFieldKind::Body; let mut header = None; - field.attrs = field.attrs.into_iter().filter(|attr| { - let meta = attr.interpret_meta() - .expect("ruma_api! could not parse request field attributes"); - - let meta_list = match meta { - Meta::List(meta_list) => meta_list, - _ => return true, + field.attrs = field.attrs.into_iter().filter_map(|attr| { + let meta = match Meta::from_attribute(attr) { + Ok(meta) => meta, + Err(attr) => return Some(attr), }; - if &meta_list.ident.to_string() != "ruma_api" { - return true; - } - - for nested_meta_item in meta_list.nested { - match nested_meta_item { - NestedMeta::Meta(meta_item) => { - match meta_item { - Meta::Word(ident) => { - match &ident.to_string()[..] { - "body" => { - has_newtype_body = true; - field_kind = RequestFieldKind::NewtypeBody; - } - "path" => field_kind = RequestFieldKind::Path, - "query" => field_kind = RequestFieldKind::Query, - _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), - } - } - Meta::NameValue(name_value) => { - match &name_value.ident.to_string()[..] { - "header" => { - match name_value.lit { - Lit::Str(lit_str) => header = Some(lit_str.value()), - _ => panic!("ruma_api! header attribute's value must be a string literal"), - } - - field_kind = RequestFieldKind::Header; - } - _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), - } - } - _ => panic!("ruma_api! attributes on requests must be a single word or a name/value pair"), + match meta { + Meta::Word(ident) => { + match &ident.to_string()[..] { + "body" => { + has_newtype_body = true; + field_kind = RequestFieldKind::NewtypeBody; } + "path" => field_kind = RequestFieldKind::Path, + "query" => field_kind = RequestFieldKind::Query, + _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), } - NestedMeta::Literal(_) => panic!( - "ruma_api! attributes on requests must be: body, header, path, or query" - ), + } + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); + + header = Some(value); + field_kind = RequestFieldKind::Header; } } - false + None }).collect(); if field_kind == RequestFieldKind::Body { @@ -370,7 +351,7 @@ pub enum RequestField { /// JSON data in the body of the request. Body(Field), /// Data in an HTTP header. - Header(Field, String), + Header(Field, Ident), /// A specific data type in the body of the request. NewtypeBody(Field), /// Data that appears in the URL path. @@ -381,7 +362,7 @@ pub enum RequestField { impl RequestField { /// Creates a new `RequestField`. - fn new(kind: RequestFieldKind, field: Field, header: Option) -> Self { + fn new(kind: RequestFieldKind, field: Field, header: Option) -> Self { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => { diff --git a/src/api/response.rs b/src/api/response.rs index 7405338a..32263702 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,10 +1,13 @@ //! Details of the `response` section of the procedural macro. -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; -use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta}; +use syn::{spanned::Spanned, Field, Ident}; -use crate::api::strip_serde_attrs; +use crate::api::{ + attribute::{Meta, MetaNameValue}, + strip_serde_attrs, +}; /// The result of processing the `request` section of the macro. pub struct Response { @@ -50,12 +53,11 @@ impl Response { #field_name: response_body.#field_name } } - ResponseField::Header(ref field, ref header) => { + ResponseField::Header(ref field, ref header_name) => { let field_name = field .ident .clone() .expect("expected field to have an identifier"); - let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); quote_spanned! {span=> @@ -87,12 +89,11 @@ impl Response { /// Produces code to add necessary HTTP headers to an `http::Response`. pub fn apply_header_fields(&self) -> TokenStream { let header_calls = self.fields.iter().filter_map(|response_field| { - if let ResponseField::Header(ref field, ref header) = *response_field { + if let ResponseField::Header(ref field, ref header_name) = *response_field { let field_name = field .ident .as_ref() .expect("expected field to have an identifier"); - let header_name = Ident::new(header.as_ref(), Span::call_site()); let span = field.span(); Some(quote_spanned! {span=> @@ -165,64 +166,44 @@ impl From> for Response { let mut field_kind = ResponseFieldKind::Body; let mut header = None; - field.attrs = field.attrs.into_iter().filter(|attr| { - let meta = attr.interpret_meta() - .expect("ruma_api! could not parse response field attributes"); - - let meta_list = match meta { - Meta::List(meta_list) => meta_list, - _ => return true, + field.attrs = field.attrs.into_iter().filter_map(|attr| { + let meta = match Meta::from_attribute(attr) { + Ok(meta) => meta, + Err(attr) => return Some(attr), }; - if &meta_list.ident.to_string() != "ruma_api" { - return true; - } + match meta { + Meta::Word(ident) => { + assert!( + ident == "body", + "ruma_api! single-word attribute on responses must be: body" + ); - for nested_meta_item in meta_list.nested { - match nested_meta_item { - NestedMeta::Meta(meta_item) => { - match meta_item { - Meta::Word(ident) => { - match &ident.to_string()[..] { - "body" => { - has_newtype_body = true; - field_kind = ResponseFieldKind::NewtypeBody; - } - _ => panic!("ruma_api! single-word attribute on responses must be: body"), - } - } - Meta::NameValue(name_value) => { - match &name_value.ident.to_string()[..] { - "header" => { - match name_value.lit { - Lit::Str(lit_str) => header = Some(lit_str.value()), - _ => panic!("ruma_api! header attribute's value must be a string literal"), - } + has_newtype_body = true; + field_kind = ResponseFieldKind::NewtypeBody; + } + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); - field_kind = ResponseFieldKind::Header; - } - _ => panic!("ruma_api! name/value pair attribute on requests must be: header"), - } - } - _ => panic!("ruma_api! attributes on responses must be a single word or a name/value pair"), - } - } - NestedMeta::Literal(_) => panic!( - "ruma_api! attribute meta item on responses must be: header" - ), + header = Some(value); + field_kind = ResponseFieldKind::Header; } } - false + None }).collect(); match field_kind { ResponseFieldKind::Body => { - if has_newtype_body { - panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); - } else { - ResponseField::Body(field) - } + assert!( + !has_newtype_body, + "ruma_api! responses cannot have both normal body fields and a newtype body field" + ); + + ResponseField::Body(field) } ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")), ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), @@ -307,7 +288,7 @@ pub enum ResponseField { /// JSON data in the body of the response. Body(Field), /// Data in an HTTP header. - Header(Field, String), + Header(Field, Ident), /// A specific data type in the body of the response. NewtypeBody(Field), } diff --git a/src/lib.rs b/src/lib.rs index e260765e..909463c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,7 +158,7 @@ mod api; /// request { /// pub foo: String, /// -/// #[ruma_api(header = "CONTENT_TYPE")] +/// #[ruma_api(header = CONTENT_TYPE)] /// pub content_type: String, /// /// #[ruma_api(query)] @@ -169,7 +169,7 @@ mod api; /// } /// /// response { -/// #[ruma_api(header = "CONTENT_TYPE")] +/// #[ruma_api(header = CONTENT_TYPE)] /// pub content_type: String, /// /// pub value: String, diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 55a34917..292f1c7a 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -17,7 +17,7 @@ pub mod some_endpoint { pub foo: String, // This value will be put into the "Content-Type" HTTP header. - #[ruma_api(header = "CONTENT_TYPE")] + #[ruma_api(header = CONTENT_TYPE)] pub content_type: String, // This value will be put into the query string of the request's URL. @@ -32,7 +32,7 @@ pub mod some_endpoint { response { // This value will be extracted from the "Content-Type" HTTP header. - #[ruma_api(header = "CONTENT_TYPE")] + #[ruma_api(header = CONTENT_TYPE)] pub content_type: String, // With no attribute on the field, it will be extracted from the body of the response. From 53cf6c562d6ff1aa27ca231ed6170d4e416279e2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 25 Jul 2019 22:42:47 +0200 Subject: [PATCH 106/107] Re-run rustfmt --- src/api/attribute.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/api/attribute.rs b/src/api/attribute.rs index 0e4cf33f..03d3edd8 100644 --- a/src/api/attribute.rs +++ b/src/api/attribute.rs @@ -22,10 +22,8 @@ impl Meta { segments, } => { if segments.len() == 1 && segments[0].ident == "ruma_api" { - Ok( - syn::parse2(attr.tts) - .expect("ruma_api! could not parse request field attributes"), - ) + Ok(syn::parse2(attr.tts) + .expect("ruma_api! could not parse request field attributes")) } else { Err(attr) } From a89f69e4f35ec50a040fd2c26a2333b842689fb5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 25 Jul 2019 23:59:35 +0200 Subject: [PATCH 107/107] Add documentation to Meta::from_attribute --- src/api/attribute.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/attribute.rs b/src/api/attribute.rs index 03d3edd8..70016d12 100644 --- a/src/api/attribute.rs +++ b/src/api/attribute.rs @@ -15,6 +15,8 @@ pub enum Meta { } impl Meta { + /// Check if the given attribute is a ruma_api attribute. If it is, parse it, if not, return + /// it unchanged. Panics if the argument is an invalid ruma_api attribute. pub fn from_attribute(attr: syn::Attribute) -> Result { match &attr.path { syn::Path {