From 0fd9fb4889828f9d0420b49435bee64d2a8810cd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 15 Dec 2016 01:50:31 -0800 Subject: [PATCH 001/295] ruma-api --- .gitignore | 2 + .travis.yml | 9 +++++ Cargo.toml | 18 +++++++++ LICENSE | 19 ++++++++++ README.md | 14 +++++++ src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fa8d85ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..369c862e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: "rust" +notifications: + email: false + irc: + channels: + - secure: "Zjx9oQaF5PYNCWHIWFAaESlUedHNdXmgT+QO35ETUNtg19w9GrNDAsOWHawuK5wLr8+NicDC0bopJ7fPIbnLz8cL/aluTl8bGlvBf7U+uqvdaxGT2TR0tntnWBzoqhYJVGM2ZspVMOloaJv7jpVdhLtde6w5KhouinaKagQxmX7Mr5ec5r9Xhwv1a72Bk6teLmEqypBmnuuskAD1MOk52piimNzNQIsqs4X+kg+7ZZ+Umx//PHTgh49HMtn/3IdmAZs3xVjrpkgbbQeDi6ynKvxpzS7obizbFB2uQRZedt/+dYVOb2vmVi9WtuNLosIyuwP6rR+A0AYe3pYDoOhUQ3ARb70kVzq9TaXcFXrxH+/Z2LWddmpVOhqDQhBW5S+b2MVXHf5a5yk6QVha68rywd9UPpD4dXsQIfCHZuQ9xLDhOkPbkiVtqSdsiYBjOF+JjFOTKMG7Dx6kACRo74/pwoMWzDDVC0HiSQdesowmoGltB9kKSyT3to651dqGH97iFLBSxsVNYWuAoO/hmLwskbW5EtqC2Crtz+A89KP3es7zsxMKloSOTzrAaYdMSkfDTI1lsHzpdGFMydDEimqRg60XM89CwxkejWPowKErp9kg+8XEh6J8s6W5c814P7oPYdQ9+DgwUr8qi3rrHqdVSa2maK+MLPK+A5riMiqpWqA=" + use_notice: true +rust: + - "nightly" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..e88a5734 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jimmy Cuadra "] +description = "Core API types for Matrix." +documentation = "https://docs.rs/ruma-api" +homepage = "https://github.com/ruma/ruma-api" +keywords = ["matrix", "chat", "messaging", "ruma"] +license = "MIT" +name = "ruma-api" +readme = "README.md" +repository = "https://github.com/ruma/ruma-api" +version = "0.1.0" + +[dependencies] +serde = "0.8.19" + +[dev-dependencies] +ruma-identifiers = "0.5.0" +serde_derive = "0.8.19" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..4d376442 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 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..b0b27d1f --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# ruma-api + +[![Build Status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) + +**ruma-api** contains core types used to define the requests and responses for each endpoint in the various [Matrix](https://matrix.org/) API specifications. +These types can be shared by client and server code for all Matrix APIs. + +## Documentation + +ruma-api has [comprehensive documentation](https://docs.rs/ruma-api) available on docs.rs. + +## License + +[MIT](http://opensource.org/licenses/MIT) diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..c1fd7667 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,105 @@ +//! Crate ruma_api contains core types used to define the requests and responses for each endpoint +//! in the various [Matrix](https://matrix.org) API specifications. +//! These types can be shared by client and server code for all Matrix APIs. +//! +//! When implementing a new Matrix API, each endpoint have a type that implements `Endpoint`, plus +//! any necessary associated types. +//! An implementation of `Endpoint` contains all the information about the HTTP method, the path and +//! input parameters for requests, and the structure of a successful response. +//! Such types can then be used by client code to make requests, and by server code to fulfill +//! those requests. +//! +//! # Example +//! +//! ```rust,no_run +//! # #![feature(proc_macro)] +//! # +//! # extern crate ruma_api; +//! # extern crate ruma_identifiers; +//! # #[macro_use] +//! # extern crate serde_derive; +//! # +//! # fn main() { +//! /// PUT /_matrix/client/r0/directory/room/:room_alias +//! pub mod create { +//! use ruma_api; +//! use ruma_identifiers::{RoomAliasId, RoomId}; +//! +//! /// This API endpoint's body parameters. +//! #[derive(Clone, Debug, Deserialize, Serialize)] +//! pub struct BodyParams { +//! pub room_id: RoomId, +//! } +//! +//! /// This API endpoint's path parameters. +//! pub struct PathParams { +//! pub room_alias: RoomAliasId, +//! } +//! +//! /// Details about this API endpoint. +//! pub struct Endpoint; +//! +//! impl ruma_api::Endpoint for Endpoint { +//! type BodyParams = BodyParams; +//! type PathParams = PathParams; +//! type QueryParams = (); +//! type Response = (); +//! +//! fn method() -> ruma_api::Method { +//! ruma_api::Method::Put +//! } +//! +//! fn request_path(params: Self::PathParams) -> String { +//! format!("/_matrix/client/r0/directory/room/{}", params.room_alias) +//! } +//! +//! fn router_path() -> String { +//! "/_matrix/client/r0/directory/room/:room_alias".to_string() +//! } +//! } +//! } +//! # } + +#![deny(missing_docs)] + +extern crate serde; + +use serde::{Deserialize, Serialize}; + +/// HTTP request methods used in Matrix APIs. +#[derive(Clone, Copy, Debug)] +pub enum Method { + /// DELETE + Delete, + /// GET + Get, + /// POST + Post, + /// PUT + Put, +} + +/// An API endpoint. +pub trait Endpoint { + /// Request parameters supplied via the body of the HTTP request. + type BodyParams: Deserialize + Serialize; + + /// Request parameters supplied via the URL's path. + type PathParams; + + /// Parameters supplied via the URL's query string. + type QueryParams; + + /// The body of the response. + type Response: Deserialize + Serialize; + + /// Returns the HTTP method used by this endpoint. + fn method() -> Method; + + /// Generates the path component of the URL for this endpoint using the supplied parameters. + fn request_path(params: Self::PathParams) -> String; + + /// Generates a generic path component of the URL for this endpoint, suitable for `Router` from + /// the router crate. + fn router_path() -> String; +} From f88588f392d6827ea0c5ce5aa49cd74c287d0e2b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 7 Jan 2017 05:17:57 -0800 Subject: [PATCH 002/295] Require that Endpoint::QueryParams be Deserialize + Serialize. --- Cargo.toml | 6 +++--- src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e88a5734..8be909ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ repository = "https://github.com/ruma/ruma-api" version = "0.1.0" [dependencies] -serde = "0.8.19" +serde = "0.8.21" [dev-dependencies] -ruma-identifiers = "0.5.0" -serde_derive = "0.8.19" +ruma-identifiers = "0.6.0" +serde_derive = "0.8.21" diff --git a/src/lib.rs b/src/lib.rs index c1fd7667..08918723 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub trait Endpoint { type PathParams; /// Parameters supplied via the URL's query string. - type QueryParams; + type QueryParams: Deserialize + Serialize; /// The body of the response. type Response: Deserialize + Serialize; From 99ad992de86683b55d26b12b8dc4ae45f35a33ae Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 7 Jan 2017 05:20:01 -0800 Subject: [PATCH 003/295] 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 8be909ce..52de7624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.1.0" +version = "0.2.0" [dependencies] serde = "0.8.21" From b3eb392ce5e9cebf04359512a3cbe298582454f8 Mon Sep 17 00:00:00 2001 From: Jansen Jan Date: Sat, 31 Dec 2016 11:26:02 +0100 Subject: [PATCH 004/295] Add flags and change String to &'static str --- src/lib.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08918723..8fd48458 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ //! } //! //! /// This API endpoint's path parameters. +//! #[derive(Clone, Debug)] //! pub struct PathParams { //! pub room_alias: RoomAliasId, //! } @@ -53,8 +54,24 @@ //! format!("/_matrix/client/r0/directory/room/{}", params.room_alias) //! } //! -//! fn router_path() -> String { -//! "/_matrix/client/r0/directory/room/:room_alias".to_string() +//! fn router_path() -> &'static str { +//! "/_matrix/client/r0/directory/room/:room_alias" +//! } +//! +//! fn name() -> &'static str { +//! "room_directory" +//! } +//! +//! fn description() -> &'static str { +//! "Matrix implementation of room directory." +//! } +//! +//! fn requires_authentication() -> bool { +//! true +//! } +//! +//! fn rate_limited() -> bool { +//! false //! } //! } //! } @@ -101,5 +118,17 @@ pub trait Endpoint { /// Generates a generic path component of the URL for this endpoint, suitable for `Router` from /// the router crate. - fn router_path() -> String; + fn router_path() -> &'static str; + + /// A unique identifier for this endpoint, suitable for `Router` from the router crate. + fn name() -> &'static str; + + /// A human-readable description of the endpoint. + fn description() -> &'static str; + + /// Whether or not this endpoint requires an authenticated user. + fn requires_authentication() -> bool; + + /// Whether or not this endpoint is rate limited. + fn rate_limited() -> bool; } From b6a743d8219843ebe1af53214186a0661d53477b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 7 Jan 2017 06:14:23 -0800 Subject: [PATCH 005/295] 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 52de7624..ba9c6223 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.2.0" +version = "0.3.0" [dependencies] serde = "0.8.21" From 5a4661c408634c87f735f44bb469ef41771ecd9a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 May 2017 02:39:22 -0700 Subject: [PATCH 006/295] Revise API to convert between associated types and Hyper request/response types. --- Cargo.toml | 11 ++- src/lib.rs | 240 +++++++++++++++++++++++++++-------------------------- 2 files changed, 131 insertions(+), 120 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ba9c6223..0f177ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,13 @@ repository = "https://github.com/ruma/ruma-api" version = "0.3.0" [dependencies] -serde = "0.8.21" + +[dependencies.hyper] +git = "https://github.com/hyperium/hyper" +rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" [dev-dependencies] -ruma-identifiers = "0.6.0" -serde_derive = "0.8.21" +ruma-identifiers = "0.11" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 8fd48458..0fe5181c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,133 +2,139 @@ //! in the various [Matrix](https://matrix.org) API specifications. //! These types can be shared by client and server code for all Matrix APIs. //! -//! When implementing a new Matrix API, each endpoint have a type that implements `Endpoint`, plus -//! any necessary associated types. +//! When implementing a new Matrix API, each endpoint has a type that implements `Endpoint`, plus +//! the necessary associated types. //! An implementation of `Endpoint` contains all the information about the HTTP method, the path and //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. -//! -//! # Example -//! -//! ```rust,no_run -//! # #![feature(proc_macro)] -//! # -//! # extern crate ruma_api; -//! # extern crate ruma_identifiers; -//! # #[macro_use] -//! # extern crate serde_derive; -//! # -//! # fn main() { -//! /// PUT /_matrix/client/r0/directory/room/:room_alias -//! pub mod create { -//! use ruma_api; -//! use ruma_identifiers::{RoomAliasId, RoomId}; -//! -//! /// This API endpoint's body parameters. -//! #[derive(Clone, Debug, Deserialize, Serialize)] -//! pub struct BodyParams { -//! pub room_id: RoomId, -//! } -//! -//! /// This API endpoint's path parameters. -//! #[derive(Clone, Debug)] -//! pub struct PathParams { -//! pub room_alias: RoomAliasId, -//! } -//! -//! /// Details about this API endpoint. -//! pub struct Endpoint; -//! -//! impl ruma_api::Endpoint for Endpoint { -//! type BodyParams = BodyParams; -//! type PathParams = PathParams; -//! type QueryParams = (); -//! type Response = (); -//! -//! fn method() -> ruma_api::Method { -//! ruma_api::Method::Put -//! } -//! -//! fn request_path(params: Self::PathParams) -> String { -//! format!("/_matrix/client/r0/directory/room/{}", params.room_alias) -//! } -//! -//! fn router_path() -> &'static str { -//! "/_matrix/client/r0/directory/room/:room_alias" -//! } -//! -//! fn name() -> &'static str { -//! "room_directory" -//! } -//! -//! fn description() -> &'static str { -//! "Matrix implementation of room directory." -//! } -//! -//! fn requires_authentication() -> bool { -//! true -//! } -//! -//! fn rate_limited() -> bool { -//! false -//! } -//! } -//! } -//! # } +#![deny(missing_debug_implementations)] #![deny(missing_docs)] +#![feature(associated_consts, try_from)] -extern crate serde; +extern crate hyper; +#[cfg(test)] extern crate ruma_identifiers; +#[cfg(test)] extern crate serde; +#[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(test)] extern crate serde_json; -use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; -/// HTTP request methods used in Matrix APIs. -#[derive(Clone, Copy, Debug)] -pub enum Method { - /// DELETE - Delete, - /// GET - Get, - /// POST - Post, - /// PUT - Put, -} +use hyper::{Method, Request, Response}; -/// An API endpoint. +/// A Matrix API endpoint. pub trait Endpoint { - /// Request parameters supplied via the body of the HTTP request. - type BodyParams: Deserialize + Serialize; + /// Data needed to make a request to the endpoint. + type Request: TryInto; + /// Data returned from the endpoint. + type Response: TryFrom; - /// Request parameters supplied via the URL's path. - type PathParams; - - /// Parameters supplied via the URL's query string. - type QueryParams: Deserialize + Serialize; - - /// The body of the response. - type Response: Deserialize + Serialize; - - /// Returns the HTTP method used by this endpoint. - fn method() -> Method; - - /// Generates the path component of the URL for this endpoint using the supplied parameters. - fn request_path(params: Self::PathParams) -> String; - - /// Generates a generic path component of the URL for this endpoint, suitable for `Router` from - /// the router crate. - fn router_path() -> &'static str; - - /// A unique identifier for this endpoint, suitable for `Router` from the router crate. - fn name() -> &'static str; - - /// A human-readable description of the endpoint. - fn description() -> &'static str; - - /// Whether or not this endpoint requires an authenticated user. - fn requires_authentication() -> bool; - - /// Whether or not this endpoint is rate limited. - fn rate_limited() -> bool; + /// Metadata about the endpoint. + const METADATA: Metadata; +} + +/// Metadata about an API endpoint. +#[derive(Clone, Debug)] +pub struct Metadata { + /// A human-readable description of the endpoint. + pub description: &'static str, + /// The HTTP method used by this endpoint. + pub method: Method, + /// A unique identifier for this endpoint. + pub name: &'static str, + /// The path of this endpoint's URL, with variable names where path parameters should be filled + /// in during a request. + pub path: &'static str, + /// Whether or not this endpoint is rate limited by the server. + pub rate_limited: bool, + /// Whether or not the server requires an authenticated user for this endpoint. + pub requires_authentication: bool, +} + +#[cfg(test)] +mod tests { + /// PUT /_matrix/client/r0/directory/room/:room_alias + pub mod create { + use std::convert::TryFrom; + + use hyper::{Method, Request as HyperRequest, Response as HyperResponse, StatusCode}; + use ruma_identifiers::{RoomAliasId, RoomId}; + use serde_json; + + use super::super::{Endpoint as ApiEndpoint, Metadata}; + + #[derive(Debug)] + pub struct Endpoint; + + #[derive(Debug)] + pub struct Error; + + impl ApiEndpoint for Endpoint { + type Request = Request; + type Response = Response; + + const METADATA: Metadata = Metadata { + description: "Add an alias to a room.", + method: Method::Put, + name: "create_alias", + path: "/_matrix/client/r0/directory/room/:room_alias", + rate_limited: false, + requires_authentication: true, + }; + + } + + /// A request to create a new room alias. + #[derive(Debug)] + pub struct Request { + pub room_id: RoomId, // body + pub room_alias: RoomAliasId, // path + } + + #[derive(Debug, Serialize)] + struct RequestBody { + room_id: RoomId, + } + + impl TryFrom for HyperRequest { + type Error = Error; + + fn try_from(request: Request) -> Result { + let metadata = Endpoint::METADATA; + + let path = metadata.path + .to_string() + .replace(":room_alias", &request.room_alias.to_string()); + + let mut hyper_request = HyperRequest::new( + metadata.method, + path.parse().map_err(|_| Error)?, + ); + + let request_body = RequestBody { + room_id: request.room_id, + }; + + hyper_request.set_body(serde_json::to_vec(&request_body).map_err(|_| Error)?); + + Ok(hyper_request) + } + } + + /// The response to a request to create a new room alias. + pub struct Response; + + impl TryFrom for Response { + type Error = Error; + + fn try_from(hyper_response: HyperResponse) -> Result { + if hyper_response.status() == StatusCode::Ok { + Ok(Response) + } else { + Err(Error) + } + } + } + } } From 69522626ff87433a0ca63e7816dcc8d5de301d36 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 12 May 2017 17:35:31 -0700 Subject: [PATCH 007/295] 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 008/295] 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 009/295] 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 010/295] 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 011/295] 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 012/295] 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 013/295] 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 014/295] 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 015/295] 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 016/295] 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 017/295] 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 018/295] 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 019/295] 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 020/295] 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 021/295] 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 022/295] 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 023/295] 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 517235b3e9f00391a75c265f8fa52e028a01e955 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 02:39:09 -0700 Subject: [PATCH 024/295] Add an error type. --- Cargo.toml | 2 +- src/lib.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f177ddc..aaa91d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/ruma/ruma-api" version = "0.3.0" [dependencies] +serde_json = "1.0.2" [dependencies.hyper] git = "https://github.com/hyperium/hyper" @@ -20,4 +21,3 @@ rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" ruma-identifiers = "0.11" serde = "1.0" serde_derive = "1.0" -serde_json = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 0fe5181c..53246077 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,23 +17,65 @@ extern crate hyper; #[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; -#[cfg(test)] extern crate serde_json; +extern crate serde_json; use std::convert::{TryFrom, TryInto}; +use std::io; -use hyper::{Method, Request, Response}; +use hyper::{Method, Request, Response, StatusCode}; +use hyper::error::UriError; /// A Matrix API endpoint. pub trait Endpoint { /// Data needed to make a request to the endpoint. - type Request: TryInto; + type Request: TryInto; /// Data returned from the endpoint. - type Response: TryFrom; + type Response: TryFrom; /// Metadata about the endpoint. const METADATA: Metadata; } +/// An error when converting an `Endpoint::Request` to a `hyper::Request` or a `hyper::Response` to +/// an `Endpoint::Response`. +#[derive(Debug)] +pub enum Error { + /// A Hyper error. + Hyper(hyper::Error), + /// A I/O error. + Io(io::Error), + /// A Serde JSON error. + SerdeJson(serde_json::Error), + /// An HTTP status code indicating error. + StatusCode(StatusCode), + /// A Uri error. + Uri(UriError), +} + +impl From for Error { + fn from(error: hyper::Error) -> Self { + Error::Hyper(error) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) + } +} + +impl From for Error { + fn from(error: serde_json::Error) -> Self { + Error::SerdeJson(error) + } +} + +impl From for Error { + fn from(error: UriError) -> Self { + Error::Uri(error) + } +} + /// Metadata about an API endpoint. #[derive(Clone, Debug)] pub struct Metadata { @@ -62,14 +104,11 @@ mod tests { use ruma_identifiers::{RoomAliasId, RoomId}; use serde_json; - use super::super::{Endpoint as ApiEndpoint, Metadata}; + use super::super::{Endpoint as ApiEndpoint, Error, Metadata}; #[derive(Debug)] pub struct Endpoint; - #[derive(Debug)] - pub struct Error; - impl ApiEndpoint for Endpoint { type Request = Request; type Response = Response; @@ -109,14 +148,14 @@ mod tests { let mut hyper_request = HyperRequest::new( metadata.method, - path.parse().map_err(|_| Error)?, + path.parse().map_err(Error::from)?, ); let request_body = RequestBody { room_id: request.room_id, }; - hyper_request.set_body(serde_json::to_vec(&request_body).map_err(|_| Error)?); + hyper_request.set_body(serde_json::to_vec(&request_body).map_err(Error::from)?); Ok(hyper_request) } @@ -132,7 +171,7 @@ mod tests { if hyper_response.status() == StatusCode::Ok { Ok(Response) } else { - Err(Error) + Err(Error::StatusCode(hyper_response.status().clone())) } } } From 1a56b35f1786877e91f94d37e144dd0f13b1172c Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 03:12:47 -0700 Subject: [PATCH 025/295] 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 3635fe51ac31b9ff899c70a0d1218caa8cf6a8dc Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 04:00:54 -0700 Subject: [PATCH 026/295] Use FutureFrom instead of TryFrom for responses. --- Cargo.toml | 1 + src/lib.rs | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aaa91d47..80520f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/ruma/ruma-api" version = "0.3.0" [dependencies] +futures = "0.1.13" serde_json = "1.0.2" [dependencies.hyper] diff --git a/src/lib.rs b/src/lib.rs index 53246077..9acb415f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,15 +13,17 @@ #![deny(missing_docs)] #![feature(associated_consts, try_from)] +extern crate futures; extern crate hyper; #[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; extern crate serde_json; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use std::io; +use futures::future::FutureFrom; use hyper::{Method, Request, Response, StatusCode}; use hyper::error::UriError; @@ -30,7 +32,7 @@ pub trait Endpoint { /// Data needed to make a request to the endpoint. type Request: TryInto; /// Data returned from the endpoint. - type Response: TryFrom; + type Response: FutureFrom; /// Metadata about the endpoint. const METADATA: Metadata; @@ -100,6 +102,7 @@ mod tests { pub mod create { use std::convert::TryFrom; + use futures::future::{FutureFrom, FutureResult, err, ok}; use hyper::{Method, Request as HyperRequest, Response as HyperResponse, StatusCode}; use ruma_identifiers::{RoomAliasId, RoomId}; use serde_json; @@ -164,14 +167,15 @@ mod tests { /// The response to a request to create a new room alias. pub struct Response; - impl TryFrom for Response { + impl FutureFrom for Response { + type Future = FutureResult; type Error = Error; - fn try_from(hyper_response: HyperResponse) -> Result { + fn future_from(hyper_response: HyperResponse) -> FutureResult { if hyper_response.status() == StatusCode::Ok { - Ok(Response) + ok(Response) } else { - Err(Error::StatusCode(hyper_response.status().clone())) + err(Error::StatusCode(hyper_response.status().clone())) } } } From c55c71dd564128452a38b2f6da4690f6daebe4bc Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 14 May 2017 04:11:35 -0700 Subject: [PATCH 027/295] 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 028/295] 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 029/295] 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 030/295] 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 031/295] 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 032/295] 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 033/295] 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 034/295] 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 035/295] 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 036/295] 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 dd62620a4b87090a96829e91f48a3f21edaf06a4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 14 Jun 2017 09:49:16 +0200 Subject: [PATCH 037/295] Bump dependency versions --- Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80520f4e..08273b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,10 @@ repository = "https://github.com/ruma/ruma-api" version = "0.3.0" [dependencies] -futures = "0.1.13" +futures = "0.1.14" +hyper = "0.11" serde_json = "1.0.2" -[dependencies.hyper] -git = "https://github.com/hyperium/hyper" -rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" - [dev-dependencies] ruma-identifiers = "0.11" serde = "1.0" From fb2082237b76d447f28d2d44417e75814c99e45e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 14 Jun 2017 10:21:15 +0200 Subject: [PATCH 038/295] 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 039/295] 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 040/295] 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 041/295] 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 042/295] 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 043/295] 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 4893be93f86543f9a9e3c5d7205afba769b84aa6 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 30 Jun 2017 18:14:24 -0700 Subject: [PATCH 044/295] Impl conversion from serde_urlencoded errors. --- Cargo.toml | 1 + src/lib.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 08273b2d..47c9570f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ version = "0.3.0" futures = "0.1.14" hyper = "0.11" serde_json = "1.0.2" +serde_urlencoded = "0.5.1" [dev-dependencies] ruma-identifiers = "0.11" diff --git a/src/lib.rs b/src/lib.rs index 9acb415f..5a1e6f76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ extern crate hyper; #[cfg(test)] extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; extern crate serde_json; +extern crate serde_urlencoded; use std::convert::TryInto; use std::io; @@ -48,6 +49,8 @@ pub enum Error { Io(io::Error), /// A Serde JSON error. SerdeJson(serde_json::Error), + /// A Serde URL encoding error. + SerdeUrlEncoded(serde_urlencoded::ser::Error), /// An HTTP status code indicating error. StatusCode(StatusCode), /// A Uri error. @@ -72,6 +75,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: serde_urlencoded::ser::Error) -> Self { + Error::SerdeUrlEncoded(error) + } +} + impl From for Error { fn from(error: UriError) -> Self { Error::Uri(error) From c0c4b0949aaaf30ea3bc93a440b335435481bb0d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 30 Jun 2017 18:33:43 -0700 Subject: [PATCH 045/295] 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 046/295] 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 047/295] 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 048/295] 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 049/295] 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 37a5991fda0cbfd73aa330143a31cf9c96a434b7 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 7 Jul 2017 01:19:03 -0700 Subject: [PATCH 050/295] Bump version to 0.4.0. --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 47c9570f..1c78ef5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Jimmy Cuadra "] -description = "Core API types for Matrix." +description = "An abstraction for Matrix API endpoints." documentation = "https://docs.rs/ruma-api" homepage = "https://github.com/ruma/ruma-api" keywords = ["matrix", "chat", "messaging", "ruma"] @@ -8,11 +8,11 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.3.0" +version = "0.4.0" [dependencies] futures = "0.1.14" -hyper = "0.11" +hyper = "0.11.1" serde_json = "1.0.2" serde_urlencoded = "0.5.1" From d3265f32517a9ba5ee9b27d27acf3d38ec501b2d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 7 Jul 2017 01:26:51 -0700 Subject: [PATCH 051/295] 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 35c2e6edd830390884112f43d6abc8f3679dffe2 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 1 Aug 2017 18:05:44 -0700 Subject: [PATCH 052/295] Swap hyper for the http crate. --- Cargo.toml | 2 +- src/lib.rs | 64 +++++++++++++++++++++++------------------------------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c78ef5e..2839a9ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ version = "0.4.0" [dependencies] futures = "0.1.14" -hyper = "0.11.1" +http = { git = "https://github.com/carllerche/http" } serde_json = "1.0.2" serde_urlencoded = "0.5.1" diff --git a/src/lib.rs b/src/lib.rs index 5a1e6f76..b898a862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,10 @@ #![deny(missing_debug_implementations)] #![deny(missing_docs)] -#![feature(associated_consts, try_from)] +#![feature(try_from)] extern crate futures; -extern crate hyper; +extern crate http; #[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] extern crate serde; #[cfg(test)] #[macro_use] extern crate serde_derive; @@ -25,26 +25,25 @@ use std::convert::TryInto; use std::io; use futures::future::FutureFrom; -use hyper::{Method, Request, Response, StatusCode}; -use hyper::error::UriError; +use http::{Method, Request, Response, StatusCode}; /// A Matrix API endpoint. -pub trait Endpoint { +pub trait Endpoint { /// Data needed to make a request to the endpoint. - type Request: TryInto; + type Request: TryInto, Error = Error>; /// Data returned from the endpoint. - type Response: FutureFrom; + type Response: FutureFrom, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; } -/// An error when converting an `Endpoint::Request` to a `hyper::Request` or a `hyper::Response` to +/// An error when converting an `Endpoint::Request` to a `http::Request` or a `http::Response` to /// an `Endpoint::Response`. #[derive(Debug)] pub enum Error { - /// A Hyper error. - Hyper(hyper::Error), + /// An HTTP error. + Http(http::Error), /// A I/O error. Io(io::Error), /// A Serde JSON error. @@ -53,13 +52,11 @@ pub enum Error { SerdeUrlEncoded(serde_urlencoded::ser::Error), /// An HTTP status code indicating error. StatusCode(StatusCode), - /// A Uri error. - Uri(UriError), } -impl From for Error { - fn from(error: hyper::Error) -> Self { - Error::Hyper(error) +impl From for Error { + fn from(error: http::Error) -> Self { + Error::Http(error) } } @@ -81,12 +78,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: UriError) -> Self { - Error::Uri(error) - } -} - /// Metadata about an API endpoint. #[derive(Clone, Debug)] pub struct Metadata { @@ -112,7 +103,8 @@ mod tests { use std::convert::TryFrom; use futures::future::{FutureFrom, FutureResult, err, ok}; - use hyper::{Method, Request as HyperRequest, Response as HyperResponse, StatusCode}; + use http::method::PUT; + use http::{Request as HttpRequest, Response as HttpResponse}; use ruma_identifiers::{RoomAliasId, RoomId}; use serde_json; @@ -121,13 +113,13 @@ mod tests { #[derive(Debug)] pub struct Endpoint; - impl ApiEndpoint for Endpoint { + impl ApiEndpoint, Vec> for Endpoint { type Request = Request; type Response = Response; const METADATA: Metadata = Metadata { description: "Add an alias to a room.", - method: Method::Put, + method: PUT, name: "create_alias", path: "/_matrix/client/r0/directory/room/:room_alias", rate_limited: false, @@ -148,43 +140,41 @@ mod tests { room_id: RoomId, } - impl TryFrom for HyperRequest { + impl TryFrom for HttpRequest> { type Error = Error; - fn try_from(request: Request) -> Result { + fn try_from(request: Request) -> Result>, Self::Error> { let metadata = Endpoint::METADATA; let path = metadata.path .to_string() .replace(":room_alias", &request.room_alias.to_string()); - let mut hyper_request = HyperRequest::new( - metadata.method, - path.parse().map_err(Error::from)?, - ); - let request_body = RequestBody { room_id: request.room_id, }; - hyper_request.set_body(serde_json::to_vec(&request_body).map_err(Error::from)?); + let http_request = HttpRequest::builder() + .method(metadata.method) + .uri(path.as_ref()) + .body(serde_json::to_vec(&request_body).map_err(Error::from)?)?; - Ok(hyper_request) + Ok(http_request) } } /// The response to a request to create a new room alias. pub struct Response; - impl FutureFrom for Response { + impl FutureFrom>> for Response { type Future = FutureResult; type Error = Error; - fn future_from(hyper_response: HyperResponse) -> FutureResult { - if hyper_response.status() == StatusCode::Ok { + fn future_from(http_response: HttpResponse>) -> FutureResult { + if http_response.status().is_success() { ok(Response) } else { - err(Error::StatusCode(hyper_response.status().clone())) + err(Error::StatusCode(http_response.status().clone())) } } } From 6ef1776aed01d690546832cad0cf653e36678478 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 1 Aug 2017 18:17:09 -0700 Subject: [PATCH 053/295] rustfmt --- src/lib.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b898a862..233dea26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,16 +8,19 @@ //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. - #![deny(missing_debug_implementations)] #![deny(missing_docs)] #![feature(try_from)] extern crate futures; extern crate http; -#[cfg(test)] extern crate ruma_identifiers; -#[cfg(test)] extern crate serde; -#[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(test)] +extern crate ruma_identifiers; +#[cfg(test)] +extern crate serde; +#[cfg(test)] +#[macro_use] +extern crate serde_derive; extern crate serde_json; extern crate serde_urlencoded; @@ -102,7 +105,7 @@ mod tests { pub mod create { use std::convert::TryFrom; - use futures::future::{FutureFrom, FutureResult, err, ok}; + use futures::future::{err, ok, FutureFrom, FutureResult}; use http::method::PUT; use http::{Request as HttpRequest, Response as HttpResponse}; use ruma_identifiers::{RoomAliasId, RoomId}; @@ -125,13 +128,12 @@ mod tests { rate_limited: false, requires_authentication: true, }; - } /// A request to create a new room alias. #[derive(Debug)] pub struct Request { - pub room_id: RoomId, // body + pub room_id: RoomId, // body pub room_alias: RoomAliasId, // path } @@ -146,7 +148,8 @@ mod tests { fn try_from(request: Request) -> Result>, Self::Error> { let metadata = Endpoint::METADATA; - let path = metadata.path + let path = metadata + .path .to_string() .replace(":room_alias", &request.room_alias.to_string()); @@ -170,7 +173,9 @@ mod tests { type Future = FutureResult; type Error = Error; - fn future_from(http_response: HttpResponse>) -> FutureResult { + fn future_from( + http_response: HttpResponse>, + ) -> FutureResult { if http_response.status().is_success() { ok(Response) } else { From 9ff74ba77617941808f5db07063a0e5f9c234432 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 8 Sep 2017 17:29:19 -0700 Subject: [PATCH 054/295] Use the crates.io release of http. --- Cargo.toml | 7 +++---- src/lib.rs | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2839a9ff..6505327c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,10 @@ name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" version = "0.4.0" - [dependencies] -futures = "0.1.14" -http = { git = "https://github.com/carllerche/http" } -serde_json = "1.0.2" +futures = "0.1.15" +http = "0.1.0" +serde_json = "1.0.3" serde_urlencoded = "0.5.1" [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 233dea26..319f3304 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,6 @@ extern crate http; #[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] -extern crate serde; -#[cfg(test)] #[macro_use] extern crate serde_derive; extern crate serde_json; @@ -106,7 +104,7 @@ mod tests { use std::convert::TryFrom; use futures::future::{err, ok, FutureFrom, FutureResult}; - use http::method::PUT; + use http::method::Method; use http::{Request as HttpRequest, Response as HttpResponse}; use ruma_identifiers::{RoomAliasId, RoomId}; use serde_json; @@ -122,7 +120,7 @@ mod tests { const METADATA: Metadata = Metadata { description: "Add an alias to a room.", - method: PUT, + method: Method::PUT, name: "create_alias", path: "/_matrix/client/r0/directory/room/:room_alias", rate_limited: false, From 474644ff6682a915949603754e0c21743b2e8340 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 8 Sep 2017 17:32:42 -0700 Subject: [PATCH 055/295] Add a nonexhaustive variant to Error. --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 319f3304..3846c6fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,10 @@ pub enum Error { SerdeUrlEncoded(serde_urlencoded::ser::Error), /// An HTTP status code indicating error. StatusCode(StatusCode), + /// Standard hack to prevent exhaustive matching. + /// This will be replaced by the #[non_exhaustive] feature when available. + #[doc(hidden)] + __Nonexhaustive, } impl From for Error { From 8970e3d83d30f28f7fe9624067611c881ca40b65 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 8 Sep 2017 17:33:18 -0700 Subject: [PATCH 056/295] Bump version to 0.5.0. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6505327c..b2450067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,8 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.4.0" +version = "0.5.0" + [dependencies] futures = "0.1.15" http = "0.1.0" From 17b11d1a25ba19a4ab50c01d29c9e577f2112df6 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 4 May 2018 03:33:08 -0700 Subject: [PATCH 057/295] 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 058/295] 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 059/295] 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 060/295] 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 061/295] 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 062/295] 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 063/295] 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 064/295] 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 065/295] 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 066/295] 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 067/295] 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 068/295] 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 069/295] 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 070/295] 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 071/295] 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 072/295] 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 073/295] 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 074/295] 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 075/295] 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 076/295] 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 077/295] 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 078/295] 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 079/295] 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 080/295] 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 081/295] 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 082/295] 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 083/295] 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 084/295] 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 085/295] 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 086/295] 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 087/295] 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 088/295] 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 08c0d1422182366383eff9bb010c0b34f046a3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Sommer?= Date: Sat, 25 Aug 2018 16:08:10 +0200 Subject: [PATCH 089/295] Add `Error(Hyper)` for errors originated from Hyper During the parsing of the HTTP responses an error might come from *Hyper*. To pass this error down to the caller, we need an entry in `Error`. --- Cargo.toml | 1 + src/lib.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b2450067..4693fd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ version = "0.5.0" [dependencies] futures = "0.1.15" http = "0.1.0" +hyper = "0.12" serde_json = "1.0.3" serde_urlencoded = "0.5.1" diff --git a/src/lib.rs b/src/lib.rs index 3846c6fb..f271f518 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ extern crate futures; extern crate http; +extern crate hyper; #[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] @@ -45,6 +46,8 @@ pub trait Endpoint { pub enum Error { /// An HTTP error. Http(http::Error), + /// An Hyper error. + Hyper(hyper::Error), /// A I/O error. Io(io::Error), /// A Serde JSON error. @@ -65,6 +68,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: hyper::Error) -> Self { + Error::Hyper(error) + } +} + impl From for Error { fn from(error: io::Error) -> Self { Error::Io(error) From d4578a835dd0ba7918de835ac258d76377725811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Sommer?= Date: Fri, 31 Aug 2018 12:35:44 +0200 Subject: [PATCH 090/295] Use `Hyper::Body` as default for `Endpoint` This makes many code more compact. --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f271f518..f03ec86b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,10 @@ use std::io; use futures::future::FutureFrom; use http::{Method, Request, Response, StatusCode}; +use hyper::Body; /// A Matrix API endpoint. -pub trait Endpoint { +pub trait Endpoint { /// Data needed to make a request to the endpoint. type Request: TryInto, Error = Error>; /// Data returned from the endpoint. 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 091/295] 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 092/295] 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 093/295] 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 74dad1205647f83d22d7de0864e2e64cd5770eaa Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 11 Sep 2018 14:59:58 +0200 Subject: [PATCH 094/295] Fix test compilation --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f03ec86b..8faa644d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,7 @@ mod tests { let http_request = HttpRequest::builder() .method(metadata.method) - .uri(path.as_ref()) + .uri(path) .body(serde_json::to_vec(&request_body).map_err(Error::from)?)?; Ok(http_request) @@ -179,6 +179,7 @@ mod tests { } /// The response to a request to create a new room alias. + #[derive(Debug)] pub struct Response; impl FutureFrom>> for Response { From c71b60ef70dd86e3a3d58dda2045ca5c60a7aeaf Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 11 Sep 2018 14:02:09 +0200 Subject: [PATCH 095/295] Add convertion to/from Request/Response from/to http::Request/Response --- Cargo.toml | 3 ++- src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4693fd0c..63383867 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,9 @@ http = "0.1.0" hyper = "0.12" serde_json = "1.0.3" serde_urlencoded = "0.5.1" +ruma-identifiers = "0.11" [dev-dependencies] -ruma-identifiers = "0.11" serde = "1.0" serde_derive = "1.0" +url = "1.7" diff --git a/src/lib.rs b/src/lib.rs index 8faa644d..3cdea079 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,13 +15,16 @@ extern crate futures; extern crate http; extern crate hyper; -#[cfg(test)] extern crate ruma_identifiers; #[cfg(test)] +extern crate serde; +#[cfg(test)] #[macro_use] extern crate serde_derive; extern crate serde_json; extern crate serde_urlencoded; +#[cfg(test)] +extern crate url; use std::convert::TryInto; use std::io; @@ -33,9 +36,9 @@ use hyper::Body; /// A Matrix API endpoint. pub trait Endpoint { /// Data needed to make a request to the endpoint. - type Request: TryInto, Error = Error>; + type Request: TryInto, Error = Error> + FutureFrom, Error = Error>; /// Data returned from the endpoint. - type Response: FutureFrom, Error = Error>; + type Response: FutureFrom, Error = Error> + TryInto>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -53,8 +56,12 @@ pub enum Error { Io(io::Error), /// A Serde JSON error. SerdeJson(serde_json::Error), + /// A Serde URL decoding error. + SerdeUrlEncodedDe(serde_urlencoded::de::Error), /// A Serde URL encoding error. - SerdeUrlEncoded(serde_urlencoded::ser::Error), + SerdeUrlEncodedSer(serde_urlencoded::ser::Error), + /// A Ruma Identitifiers error. + RumaIdentifiers(ruma_identifiers::Error), /// An HTTP status code indicating error. StatusCode(StatusCode), /// Standard hack to prevent exhaustive matching. @@ -87,9 +94,21 @@ impl From for Error { } } +impl From for Error { + fn from(error: serde_urlencoded::de::Error) -> Self { + Error::SerdeUrlEncodedDe(error) + } +} + impl From for Error { fn from(error: serde_urlencoded::ser::Error) -> Self { - Error::SerdeUrlEncoded(error) + Error::SerdeUrlEncodedSer(error) + } +} + +impl From for Error { + fn from(error: ruma_identifiers::Error) -> Self { + Error::RumaIdentifiers(error) } } @@ -118,10 +137,13 @@ mod tests { use std::convert::TryFrom; use futures::future::{err, ok, FutureFrom, FutureResult}; + use http::header::CONTENT_TYPE; use http::method::Method; use http::{Request as HttpRequest, Response as HttpResponse}; use ruma_identifiers::{RoomAliasId, RoomId}; + use serde::de::{Deserialize, IntoDeserializer}; use serde_json; + use url::percent_encoding; use super::super::{Endpoint as ApiEndpoint, Error, Metadata}; @@ -149,7 +171,7 @@ mod tests { pub room_alias: RoomAliasId, // path } - #[derive(Debug, Serialize)] + #[derive(Debug, Serialize, Deserialize)] struct RequestBody { room_id: RoomId, } @@ -178,6 +200,36 @@ mod tests { } } + impl FutureFrom>> for Request { + type Future = FutureResult; + type Error = Error; + + fn future_from(request: HttpRequest>) -> Self::Future { + FutureResult::from(Self::try_from(request)) + } + } + + impl TryFrom>> for Request { + type Error = Error; + + fn try_from(request: HttpRequest>) -> Result { + let request_body: RequestBody = + ::serde_json::from_slice(request.body().as_slice())?; + let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); + Ok(Request { + room_id: request_body.room_id, + room_alias: { + let segment = path_segments.get(5).unwrap().as_bytes(); + let decoded = + percent_encoding::percent_decode(segment) + .decode_utf8_lossy(); + RoomAliasId::deserialize(decoded.into_deserializer()) + .map_err(|e: serde_json::error::Error| e)? + }, + }) + } + } + /// The response to a request to create a new room alias. #[derive(Debug)] pub struct Response; @@ -196,5 +248,17 @@ mod tests { } } } + + impl TryFrom for HttpResponse> { + type Error = Error; + + fn try_from(_response: Response) -> Result>, Self::Error> { + let response = HttpResponse::builder() + .header(CONTENT_TYPE, "application/json") + .body("{}".as_bytes().to_vec()) + .unwrap(); + Ok(response) + } + } } } From e23eff151bc063387ddf0a36a6cd801b85f50021 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 8 Sep 2018 11:06:20 +0200 Subject: [PATCH 096/295] 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 097/295] 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 098/295] 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 099/295] 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 bfddf743986a1ae324832ea5517b12b7cfc44b82 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 17:56:51 -0800 Subject: [PATCH 100/295] Update dependencies. --- Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 63383867..9e73d558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,14 @@ repository = "https://github.com/ruma/ruma-api" version = "0.5.0" [dependencies] -futures = "0.1.15" -http = "0.1.0" -hyper = "0.12" -serde_json = "1.0.3" -serde_urlencoded = "0.5.1" -ruma-identifiers = "0.11" +futures = "0.1.25" +http = "0.1.14" +hyper = "0.12.16" +serde_json = "1.0.33" +serde_urlencoded = "0.5.4" +ruma-identifiers = "0.11.0" [dev-dependencies] -serde = "1.0" -serde_derive = "1.0" -url = "1.7" +serde = "1.0.80" +serde_derive = "1.0.80" +url = "1.7.2" From 033c04fd12b1ba524a788f260f2074cbb4172f24 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 17:57:58 -0800 Subject: [PATCH 101/295] 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 9e73d558..a380e4c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.5.0" +version = "0.6.0" [dependencies] futures = "0.1.25" From 2f07b803a84ee5183c51d3c0832a58ec073c04dd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 3 Dec 2018 18:06:14 -0800 Subject: [PATCH 102/295] 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 103/295] 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 104/295] 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 105/295] 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 106/295] 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 9d1e8180b8833241e913f4fbbdbee6e0cf388cbe Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 8 Jan 2019 20:17:28 +0100 Subject: [PATCH 107/295] Configure rustfmt for nested imports, re-run 'cargo fmt' --- .rustfmt.toml | 1 + src/lib.rs | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 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/lib.rs b/src/lib.rs index 3cdea079..f629be43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,7 @@ extern crate serde_urlencoded; #[cfg(test)] extern crate url; -use std::convert::TryInto; -use std::io; +use std::{convert::TryInto, io}; use futures::future::FutureFrom; use http::{Method, Request, Response, StatusCode}; @@ -137,9 +136,9 @@ mod tests { use std::convert::TryFrom; use futures::future::{err, ok, FutureFrom, FutureResult}; - use http::header::CONTENT_TYPE; - use http::method::Method; - use http::{Request as HttpRequest, Response as HttpResponse}; + use http::{ + header::CONTENT_TYPE, method::Method, Request as HttpRequest, Response as HttpResponse, + }; use ruma_identifiers::{RoomAliasId, RoomId}; use serde::de::{Deserialize, IntoDeserializer}; use serde_json; @@ -220,11 +219,9 @@ mod tests { room_id: request_body.room_id, room_alias: { let segment = path_segments.get(5).unwrap().as_bytes(); - let decoded = - percent_encoding::percent_decode(segment) - .decode_utf8_lossy(); + let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); RoomAliasId::deserialize(decoded.into_deserializer()) - .map_err(|e: serde_json::error::Error| e)? + .map_err(|e: serde_json::error::Error| e)? }, }) } From d3322bec113e25741375fddb0615f6bab8040806 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 13 Jan 2019 21:39:27 +0100 Subject: [PATCH 108/295] 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 282b952192274c86d9aad0ad5ada57827df54f1b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 13 Jan 2019 21:42:45 +0100 Subject: [PATCH 109/295] Update to Rust 2018 --- Cargo.toml | 1 + src/lib.rs | 22 ++++++---------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a380e4c6..7d51e130 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" version = "0.6.0" +edition = "2018" [dependencies] futures = "0.1.25" diff --git a/src/lib.rs b/src/lib.rs index f629be43..621b42ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,25 +12,14 @@ #![deny(missing_docs)] #![feature(try_from)] -extern crate futures; -extern crate http; -extern crate hyper; -extern crate ruma_identifiers; -#[cfg(test)] -extern crate serde; -#[cfg(test)] -#[macro_use] -extern crate serde_derive; -extern crate serde_json; -extern crate serde_urlencoded; -#[cfg(test)] -extern crate url; - use std::{convert::TryInto, io}; use futures::future::FutureFrom; -use http::{Method, Request, Response, StatusCode}; -use hyper::Body; +use http::{self, Method, Request, Response, StatusCode}; +use hyper::{self, Body}; +use ruma_identifiers; +use serde_json; +use serde_urlencoded; /// A Matrix API endpoint. pub trait Endpoint { @@ -141,6 +130,7 @@ mod tests { }; use ruma_identifiers::{RoomAliasId, RoomId}; use serde::de::{Deserialize, IntoDeserializer}; + use serde_derive::{Deserialize, Serialize}; use serde_json; use url::percent_encoding; From f761a8f837f44915c0abe5ae42a44a09cb832790 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 30 Jan 2019 22:50:06 +0100 Subject: [PATCH 110/295] 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 111/295] 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 112/295] 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 561a254ca86bad7a0b632c2c7e521d5d1b4a7cd7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 6 Feb 2019 20:24:29 +0100 Subject: [PATCH 113/295] Replace serde_derive by re-exports in serde --- Cargo.toml | 6 ++++-- src/lib.rs | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d51e130..20fb2ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ serde_urlencoded = "0.5.4" ruma-identifiers = "0.11.0" [dev-dependencies] -serde = "1.0.80" -serde_derive = "1.0.80" url = "1.7.2" + +[dev-dependencies.serde] +version = "1.0.87" +features = ["derive"] diff --git a/src/lib.rs b/src/lib.rs index 621b42ab..965843d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,8 +129,7 @@ mod tests { header::CONTENT_TYPE, method::Method, Request as HttpRequest, Response as HttpResponse, }; use ruma_identifiers::{RoomAliasId, RoomId}; - use serde::de::{Deserialize, IntoDeserializer}; - use serde_derive::{Deserialize, Serialize}; + use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; use url::percent_encoding; From 728ea7a301900688edaaab6ca731303c73faf31c Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 17:29:43 -0700 Subject: [PATCH 114/295] Add note about minimum Rust version. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b0b27d1f..2258759a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ **ruma-api** contains core types used to define the requests and responses for each endpoint in the various [Matrix](https://matrix.org/) API specifications. These types can be shared by client and server code for all Matrix APIs. +## Minimum Rust version + +ruma-api requires Rust 1.34 or later. + ## Documentation ruma-api has [comprehensive documentation](https://docs.rs/ruma-api) available on docs.rs. From 99b2d6423f488f88e3ca2f125ee26b904886fcdf Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:03:11 -0700 Subject: [PATCH 115/295] Update dependencies. --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 20fb2ee4..d88b2a2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,16 @@ version = "0.6.0" edition = "2018" [dependencies] -futures = "0.1.25" -http = "0.1.14" -hyper = "0.12.16" -serde_json = "1.0.33" +futures = "0.1.26" +http = "0.1.17" +hyper = "0.12.27" +serde_json = "1.0.39" serde_urlencoded = "0.5.4" -ruma-identifiers = "0.11.0" +ruma-identifiers = "0.12.0" [dev-dependencies] url = "1.7.2" [dev-dependencies.serde] -version = "1.0.87" +version = "1.0.90" features = ["derive"] From 376b53613fc51cac0b4c855dede9c2e1f840ccc9 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:03:46 -0700 Subject: [PATCH 116/295] Remove try_from feature. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 965843d5..6d0b3212 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ //! those requests. #![deny(missing_debug_implementations)] #![deny(missing_docs)] -#![feature(try_from)] use std::{convert::TryInto, io}; From 6e3e29fc223fd2c50cf59979cc22628eb49f6b4c Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:05:34 -0700 Subject: [PATCH 117/295] Bump version to 0.7.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d88b2a2b..2be0d088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.6.0" +version = "0.7.0" edition = "2018" [dependencies] From 6659df84957720d77b039346c04774aef7bc94c4 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 11 Apr 2019 18:15:26 -0700 Subject: [PATCH 118/295] 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 119/295] 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 120/295] 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 121/295] 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 122/295] 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 68c446ca642ceb91e08ad357511b56bbd0f4c7b1 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 09:11:56 -0700 Subject: [PATCH 123/295] Use stable Rust on Travis. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 369c862e..c0a5035d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,3 @@ notifications: channels: - secure: "Zjx9oQaF5PYNCWHIWFAaESlUedHNdXmgT+QO35ETUNtg19w9GrNDAsOWHawuK5wLr8+NicDC0bopJ7fPIbnLz8cL/aluTl8bGlvBf7U+uqvdaxGT2TR0tntnWBzoqhYJVGM2ZspVMOloaJv7jpVdhLtde6w5KhouinaKagQxmX7Mr5ec5r9Xhwv1a72Bk6teLmEqypBmnuuskAD1MOk52piimNzNQIsqs4X+kg+7ZZ+Umx//PHTgh49HMtn/3IdmAZs3xVjrpkgbbQeDi6ynKvxpzS7obizbFB2uQRZedt/+dYVOb2vmVi9WtuNLosIyuwP6rR+A0AYe3pYDoOhUQ3ARb70kVzq9TaXcFXrxH+/Z2LWddmpVOhqDQhBW5S+b2MVXHf5a5yk6QVha68rywd9UPpD4dXsQIfCHZuQ9xLDhOkPbkiVtqSdsiYBjOF+JjFOTKMG7Dx6kACRo74/pwoMWzDDVC0HiSQdesowmoGltB9kKSyT3to651dqGH97iFLBSxsVNYWuAoO/hmLwskbW5EtqC2Crtz+A89KP3es7zsxMKloSOTzrAaYdMSkfDTI1lsHzpdGFMydDEimqRg60XM89CwxkejWPowKErp9kg+8XEh6J8s6W5c814P7oPYdQ9+DgwUr8qi3rrHqdVSa2maK+MLPK+A5riMiqpWqA=" use_notice: true -rust: - - "nightly" From a92e71f873ef95eb18c7abe8d7bdbd499545bb40 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 09:12:50 -0700 Subject: [PATCH 124/295] 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 a52e0da7c1ac45900eb6a5b7d0368edc9ffd765f Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 13:16:54 -0700 Subject: [PATCH 125/295] Add clippy lints and add rustfmt and clippy to CI. --- .rustfmt.toml | 1 - .travis.yml | 8 ++++++++ src/lib.rs | 28 ++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 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 c0a5035d..644f170d 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 -- -D warnings" + - "cargo build --verbose" + - "cargo test --verbose" notifications: email: false irc: diff --git a/src/lib.rs b/src/lib.rs index 6d0b3212..08ba9203 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,32 @@ //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] + +#![deny( + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + 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 +)] use std::{convert::TryInto, io}; From 6a09f1f754e36084dd3271bfa437f4ef746873b9 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 17:35:26 -0700 Subject: [PATCH 126/295] 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 66be8d1a98cdfe8af980f3bdda23a57cecaefaab Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 2 Jun 2019 17:36:22 -0700 Subject: [PATCH 127/295] Run clippy on all targets and all features. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 644f170d..c001a352 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ before_script: - "rustup component add clippy" script: - "cargo fmt --all -- --check" - - "cargo clippy -- -D warnings" + - "cargo clippy --all-targets --all-features -- -D warnings" - "cargo build --verbose" - "cargo test --verbose" notifications: From 9805750f83290bb2b2aeac1b4fcdad41f85e86dd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 12:21:39 -0700 Subject: [PATCH 128/295] Bump dependencies. --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2be0d088..55329a79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,16 @@ version = "0.7.0" edition = "2018" [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" -ruma-identifiers = "0.12.0" +serde_urlencoded = "0.5.5" +ruma-identifiers = "0.13.0" [dev-dependencies] url = "1.7.2" [dev-dependencies.serde] -version = "1.0.90" +version = "1.0.92" features = ["derive"] From 432dbf94c80465d70f4a27a8c716bf0978a6d460 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 12:37:55 -0700 Subject: [PATCH 129/295] Revise the error type to hide lower-level errors. --- src/lib.rs | 59 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08ba9203..99c62815 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,12 @@ clippy::wrong_self_convention )] -use std::{convert::TryInto, io}; +use std::{ + convert::TryInto, + error::Error as StdError, + fmt::{Display, Formatter, Result as FmtResult}, + io, +}; use futures::future::FutureFrom; use http::{self, Method, Request, Response, StatusCode}; @@ -58,7 +63,29 @@ pub trait Endpoint { /// An error when converting an `Endpoint::Request` to a `http::Request` or a `http::Response` to /// an `Endpoint::Response`. #[derive(Debug)] -pub enum Error { +pub struct Error(pub(crate) InnerError); + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let message = match self.0 { + InnerError::Http(_) => "An error converting to or from `http` types occurred.".into(), + InnerError::Hyper(_) => "A Hyper error occurred.".into(), + InnerError::Io(_) => "An I/O error occurred.".into(), + InnerError::SerdeJson(_) => "A JSON error occurred.".into(), + InnerError::SerdeUrlEncodedDe(_) => "A URL encoding deserialization error occurred.".into(), + InnerError::SerdeUrlEncodedSer(_) => "A URL encoding serialization error occurred.".into(), + InnerError::RumaIdentifiers(_) => "A ruma-identifiers error occurred.".into(), + InnerError::StatusCode(code) => format!("A HTTP {} error occurred.", code), + }; + + write!(f, "{}", message) + } +} + +impl StdError for Error {} + +#[derive(Debug)] +pub(crate) enum InnerError { /// An HTTP error. Http(http::Error), /// An Hyper error. @@ -75,51 +102,53 @@ pub enum Error { RumaIdentifiers(ruma_identifiers::Error), /// An HTTP status code indicating error. StatusCode(StatusCode), - /// Standard hack to prevent exhaustive matching. - /// This will be replaced by the #[non_exhaustive] feature when available. - #[doc(hidden)] - __Nonexhaustive, } impl From for Error { fn from(error: http::Error) -> Self { - Error::Http(error) + Self(InnerError::Http(error)) } } impl From for Error { fn from(error: hyper::Error) -> Self { - Error::Hyper(error) + Self(InnerError::Hyper(error)) } } impl From for Error { fn from(error: io::Error) -> Self { - Error::Io(error) + Self(InnerError::Io(error)) } } impl From for Error { fn from(error: serde_json::Error) -> Self { - Error::SerdeJson(error) + Self(InnerError::SerdeJson(error)) } } impl From for Error { fn from(error: serde_urlencoded::de::Error) -> Self { - Error::SerdeUrlEncodedDe(error) + Self(InnerError::SerdeUrlEncodedDe(error)) } } impl From for Error { fn from(error: serde_urlencoded::ser::Error) -> Self { - Error::SerdeUrlEncodedSer(error) + Self(InnerError::SerdeUrlEncodedSer(error)) } } impl From for Error { fn from(error: ruma_identifiers::Error) -> Self { - Error::RumaIdentifiers(error) + Self(InnerError::RumaIdentifiers(error)) + } +} + +impl From for Error { + fn from(error: StatusCode) -> Self { + Self(InnerError::StatusCode(error)) } } @@ -240,7 +269,7 @@ mod tests { } /// The response to a request to create a new room alias. - #[derive(Debug)] + #[derive(Clone, Copy, Debug)] pub struct Response; impl FutureFrom>> for Response { @@ -253,7 +282,7 @@ mod tests { if http_response.status().is_success() { ok(Response) } else { - err(Error::StatusCode(http_response.status().clone())) + err(http_response.status().clone().into()) } } } From 3b383d78b70ce5b6717634ebed4cbb15702adc63 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 12:59:11 -0700 Subject: [PATCH 130/295] Bump version to 0.8.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 55329a79..64b7053c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.7.0" +version = "0.8.0" edition = "2018" [dependencies] From 0b75d2f1c33f917512188deb82e8e34932674dd3 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:03:47 -0700 Subject: [PATCH 131/295] Run rustfmt. --- src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 99c62815..98dcbed8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,8 +72,12 @@ impl Display for Error { InnerError::Hyper(_) => "A Hyper error occurred.".into(), InnerError::Io(_) => "An I/O error occurred.".into(), InnerError::SerdeJson(_) => "A JSON error occurred.".into(), - InnerError::SerdeUrlEncodedDe(_) => "A URL encoding deserialization error occurred.".into(), - InnerError::SerdeUrlEncodedSer(_) => "A URL encoding serialization error occurred.".into(), + InnerError::SerdeUrlEncodedDe(_) => { + "A URL encoding deserialization error occurred.".into() + } + InnerError::SerdeUrlEncodedSer(_) => { + "A URL encoding serialization error occurred.".into() + } InnerError::RumaIdentifiers(_) => "A ruma-identifiers error occurred.".into(), InnerError::StatusCode(code) => format!("A HTTP {} error occurred.", code), }; From 557ac4b4855d5070e89d9c81687f217438fd9e4d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:06:35 -0700 Subject: [PATCH 132/295] 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 133/295] 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 134/295] 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 d5ce34d3010dc9fd987a730b67c876157c19b18a Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 12 Jun 2019 13:16:52 -0700 Subject: [PATCH 135/295] Address clippy warnings. --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 98dcbed8..6ba07852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,6 @@ 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 @@ -88,6 +87,7 @@ impl Display for Error { impl StdError for Error {} +/// Internal representation of errors. #[derive(Debug)] pub(crate) enum InnerError { /// An HTTP error. @@ -256,7 +256,7 @@ mod tests { impl TryFrom>> for Request { type Error = Error; - fn try_from(request: HttpRequest>) -> Result { + fn try_from(request: HttpRequest>) -> Result { let request_body: RequestBody = ::serde_json::from_slice(request.body().as_slice())?; let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); @@ -286,7 +286,7 @@ mod tests { if http_response.status().is_success() { ok(Response) } else { - err(http_response.status().clone().into()) + err(http_response.status().into()) } } } @@ -297,7 +297,7 @@ mod tests { fn try_from(_response: Response) -> Result>, Self::Error> { let response = HttpResponse::builder() .header(CONTENT_TYPE, "application/json") - .body("{}".as_bytes().to_vec()) + .body(b"{}".to_vec()) .unwrap(); Ok(response) } From 9a5858cae8911daad2cf2de95e34bb34d593bb9d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 16 Jun 2019 16:50:06 -0700 Subject: [PATCH 136/295] Add crates.io categories. [ci skip] --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 64b7053c..b54bd0c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] authors = ["Jimmy Cuadra "] +categories ["api-bindings", "web-programming"] description = "An abstraction for Matrix API endpoints." documentation = "https://docs.rs/ruma-api" homepage = "https://github.com/ruma/ruma-api" From 5461c208758dd3004cbe7415e068bb8ac9d52555 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sun, 16 Jun 2019 16:51:11 -0700 Subject: [PATCH 137/295] 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 138/295] 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 b0c1997016bd451c2fbad0220eb1c957826e09ff Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Mon, 24 Jun 2019 13:02:54 -0700 Subject: [PATCH 139/295] Fix typo in Cargo.toml. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b54bd0c4..9c70d78b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Jimmy Cuadra "] -categories ["api-bindings", "web-programming"] +categories = ["api-bindings", "web-programming"] description = "An abstraction for Matrix API endpoints." documentation = "https://docs.rs/ruma-api" homepage = "https://github.com/ruma/ruma-api" From 169b6cb9f9a9689cc29adebea09e54fc8e1dc161 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 18 Jul 2019 23:22:53 +0200 Subject: [PATCH 140/295] Remove futures and hyper dependencies --- Cargo.toml | 2 -- src/lib.rs | 47 +++++++++++++---------------------------------- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c70d78b..dcbe4396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,7 @@ version = "0.8.0" edition = "2018" [dependencies] -futures = "0.1.27" http = "0.1.17" -hyper = "0.12.29" serde_json = "1.0.39" serde_urlencoded = "0.5.5" ruma-identifiers = "0.13.0" diff --git a/src/lib.rs b/src/lib.rs index 6ba07852..819752ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,25 +35,26 @@ )] use std::{ - convert::TryInto, + convert::{TryFrom, TryInto}, error::Error as StdError, fmt::{Display, Formatter, Result as FmtResult}, io, }; -use futures::future::FutureFrom; use http::{self, Method, Request, Response, StatusCode}; -use hyper::{self, Body}; use ruma_identifiers; use serde_json; use serde_urlencoded; /// A Matrix API endpoint. -pub trait Endpoint { +pub trait Endpoint { /// Data needed to make a request to the endpoint. - type Request: TryInto, Error = Error> + FutureFrom, Error = Error>; + type Request: TryFrom>, Error = Error> + + TryInto>, Error = Error>; + /// Data returned from the endpoint. - type Response: FutureFrom, Error = Error> + TryInto>; + type Response: TryFrom>, Error = Error> + + TryInto>, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -68,7 +69,6 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let message = match self.0 { InnerError::Http(_) => "An error converting to or from `http` types occurred.".into(), - InnerError::Hyper(_) => "A Hyper error occurred.".into(), InnerError::Io(_) => "An I/O error occurred.".into(), InnerError::SerdeJson(_) => "A JSON error occurred.".into(), InnerError::SerdeUrlEncodedDe(_) => { @@ -92,8 +92,6 @@ impl StdError for Error {} pub(crate) enum InnerError { /// An HTTP error. Http(http::Error), - /// An Hyper error. - Hyper(hyper::Error), /// A I/O error. Io(io::Error), /// A Serde JSON error. @@ -114,12 +112,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: hyper::Error) -> Self { - Self(InnerError::Hyper(error)) - } -} - impl From for Error { fn from(error: io::Error) -> Self { Self(InnerError::Io(error)) @@ -180,7 +172,6 @@ mod tests { pub mod create { use std::convert::TryFrom; - use futures::future::{err, ok, FutureFrom, FutureResult}; use http::{ header::CONTENT_TYPE, method::Method, Request as HttpRequest, Response as HttpResponse, }; @@ -189,12 +180,12 @@ mod tests { use serde_json; use url::percent_encoding; - use super::super::{Endpoint as ApiEndpoint, Error, Metadata}; + use crate::{Endpoint as ApiEndpoint, Error, Metadata}; #[derive(Debug)] pub struct Endpoint; - impl ApiEndpoint, Vec> for Endpoint { + impl ApiEndpoint for Endpoint { type Request = Request; type Response = Response; @@ -244,15 +235,6 @@ mod tests { } } - impl FutureFrom>> for Request { - type Future = FutureResult; - type Error = Error; - - fn future_from(request: HttpRequest>) -> Self::Future { - FutureResult::from(Self::try_from(request)) - } - } - impl TryFrom>> for Request { type Error = Error; @@ -276,17 +258,14 @@ mod tests { #[derive(Clone, Copy, Debug)] pub struct Response; - impl FutureFrom>> for Response { - type Future = FutureResult; + impl TryFrom>> for Response { type Error = Error; - fn future_from( - http_response: HttpResponse>, - ) -> FutureResult { + fn try_from(http_response: HttpResponse>) -> Result { if http_response.status().is_success() { - ok(Response) + Ok(Response) } else { - err(http_response.status().into()) + Err(http_response.status().into()) } } } From 5d0b4dd833afd39451eeb684d908c6ea62e231b4 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 19 Jul 2019 12:30:38 -0700 Subject: [PATCH 141/295] Bump version to 0.9.0. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dcbe4396..c936d3d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.8.0" +version = "0.9.0" edition = "2018" [dependencies] From be79a80467a8accaca40a0321a99606accacc50f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 19 Jul 2019 11:52:49 +0200 Subject: [PATCH 142/295] 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 143/295] 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 23a946c8df102a58f621816cd50afaddaac77e55 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 21 Jul 2019 01:20:49 +0200 Subject: [PATCH 144/295] Simplify the Endpoint trait by using the Request type as Self --- src/lib.rs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 819752ef..734587ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,22 +41,20 @@ use std::{ io, }; -use http::{self, Method, Request, Response, StatusCode}; +use http::{self, Method, Request as HttpRequest, Response as HttpResponse, StatusCode}; use ruma_identifiers; use serde_json; use serde_urlencoded; -/// A Matrix API endpoint. -pub trait Endpoint { - /// Data needed to make a request to the endpoint. - type Request: TryFrom>, Error = Error> - + TryInto>, Error = Error>; +/// A Matrix API endpoint's Request type +pub trait Endpoint: + TryFrom>, Error = Error> + TryInto>, Error = Error> +{ + /// The corresponding Response type + type Response: TryFrom>, Error = Error> + + TryInto>, Error = Error>; - /// Data returned from the endpoint. - type Response: TryFrom>, Error = Error> - + TryInto>, Error = Error>; - - /// Metadata about the endpoint. + /// Metadata about the endpoint const METADATA: Metadata; } @@ -180,13 +178,9 @@ mod tests { use serde_json; use url::percent_encoding; - use crate::{Endpoint as ApiEndpoint, Error, Metadata}; + use crate::{Endpoint, Error, Metadata}; - #[derive(Debug)] - pub struct Endpoint; - - impl ApiEndpoint for Endpoint { - type Request = Request; + impl Endpoint for Request { type Response = Response; const METADATA: Metadata = Metadata { @@ -215,7 +209,7 @@ mod tests { type Error = Error; fn try_from(request: Request) -> Result>, Self::Error> { - let metadata = Endpoint::METADATA; + let metadata = Request::METADATA; let path = metadata .path From f59d9d1fb17487c25276ffeca127ea246edf2b4d Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 23 Jul 2019 09:46:13 -0700 Subject: [PATCH 145/295] Run cargo-audit on CI. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index c001a352..43fd7c0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: "rust" +cache: "cargo" before_script: - "rustup component add rustfmt" - "rustup component add clippy" + - "cargo install --force cargo-audit" script: + - "cargo audit" - "cargo fmt --all -- --check" - "cargo clippy --all-targets --all-features -- -D warnings" - "cargo build --verbose" From fd8367be4c96f9fbb59700be75adc761023fd928 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 23 Jul 2019 09:47:17 -0700 Subject: [PATCH 146/295] 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 db709e2fdd9761c823a870fbefcf62285ffb4627 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Tue, 23 Jul 2019 09:47:56 -0700 Subject: [PATCH 147/295] Run cargo generate-lockfile on CI before running cargo-audit. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 43fd7c0f..d1189142 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ 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" From bb5a159412e1eafa225b71400ef8b21421746fcd Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 25 Jul 2019 10:04:49 -0700 Subject: [PATCH 148/295] Tweak documentation and import names. --- src/lib.rs | 87 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 734587ab..36962a20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,10 @@ -//! Crate ruma_api contains core types used to define the requests and responses for each endpoint +//! Crate `ruma_api` contains core types used to define the requests and responses for each endpoint //! in the various [Matrix](https://matrix.org) API specifications. //! These types can be shared by client and server code for all Matrix APIs. //! -//! When implementing a new Matrix API, each endpoint has a type that implements `Endpoint`, plus -//! the necessary associated types. +//! When implementing a new Matrix API, each endpoint has a request type which implements +//! `Endpoint`, and a response type connected via an associated type. +//! //! An implementation of `Endpoint` contains all the information about the HTTP method, the path and //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill @@ -41,25 +42,27 @@ use std::{ io, }; -use http::{self, Method, Request as HttpRequest, Response as HttpResponse, StatusCode}; +use http::{self, Method, StatusCode}; use ruma_identifiers; use serde_json; use serde_urlencoded; -/// A Matrix API endpoint's Request type +/// A Matrix API endpoint. +/// +/// The type implementing this trait contains any data needed to make a request to the endpoint. pub trait Endpoint: - TryFrom>, Error = Error> + TryInto>, Error = Error> + TryFrom>, Error = Error> + TryInto>, Error = Error> { - /// The corresponding Response type - type Response: TryFrom>, Error = Error> - + TryInto>, Error = Error>; + /// Data returned in a successful response from the endpoint. + type Response: TryFrom>, Error = Error> + + TryInto>, Error = Error>; - /// Metadata about the endpoint + /// Metadata about the endpoint. const METADATA: Metadata; } -/// An error when converting an `Endpoint::Request` to a `http::Request` or a `http::Response` to -/// an `Endpoint::Response`. +/// An error when converting an `Endpoint` request or response to the corresponding type from the +/// `http` crate. #[derive(Debug)] pub struct Error(pub(crate) InnerError); @@ -90,16 +93,22 @@ impl StdError for Error {} pub(crate) enum InnerError { /// An HTTP error. Http(http::Error), + /// A I/O error. Io(io::Error), + /// A Serde JSON error. SerdeJson(serde_json::Error), + /// A Serde URL decoding error. SerdeUrlEncodedDe(serde_urlencoded::de::Error), + /// A Serde URL encoding error. SerdeUrlEncodedSer(serde_urlencoded::ser::Error), + /// A Ruma Identitifiers error. RumaIdentifiers(ruma_identifiers::Error), + /// An HTTP status code indicating error. StatusCode(StatusCode), } @@ -151,15 +160,20 @@ impl From for Error { pub struct Metadata { /// A human-readable description of the endpoint. pub description: &'static str, + /// The HTTP method used by this endpoint. pub method: Method, + /// A unique identifier for this endpoint. pub name: &'static str, + /// The path of this endpoint's URL, with variable names where path parameters should be filled /// in during a request. pub path: &'static str, + /// Whether or not this endpoint is rate limited by the server. pub rate_limited: bool, + /// Whether or not the server requires an authenticated user for this endpoint. pub requires_authentication: bool, } @@ -170,9 +184,7 @@ mod tests { pub mod create { use std::convert::TryFrom; - use http::{ - header::CONTENT_TYPE, method::Method, Request as HttpRequest, Response as HttpResponse, - }; + use http::{self, header::CONTENT_TYPE, method::Method}; use ruma_identifiers::{RoomAliasId, RoomId}; use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; @@ -180,6 +192,13 @@ mod tests { use crate::{Endpoint, Error, Metadata}; + /// A request to create a new room alias. + #[derive(Debug)] + pub struct Request { + pub room_id: RoomId, // body + pub room_alias: RoomAliasId, // path + } + impl Endpoint for Request { type Response = Response; @@ -193,22 +212,10 @@ mod tests { }; } - /// A request to create a new room alias. - #[derive(Debug)] - pub struct Request { - pub room_id: RoomId, // body - pub room_alias: RoomAliasId, // path - } - - #[derive(Debug, Serialize, Deserialize)] - struct RequestBody { - room_id: RoomId, - } - - impl TryFrom for HttpRequest> { + impl TryFrom for http::Request> { type Error = Error; - fn try_from(request: Request) -> Result>, Self::Error> { + fn try_from(request: Request) -> Result>, Self::Error> { let metadata = Request::METADATA; let path = metadata @@ -220,7 +227,7 @@ mod tests { room_id: request.room_id, }; - let http_request = HttpRequest::builder() + let http_request = http::Request::builder() .method(metadata.method) .uri(path) .body(serde_json::to_vec(&request_body).map_err(Error::from)?)?; @@ -229,10 +236,10 @@ mod tests { } } - impl TryFrom>> for Request { + impl TryFrom>> for Request { type Error = Error; - fn try_from(request: HttpRequest>) -> Result { + fn try_from(request: http::Request>) -> Result { let request_body: RequestBody = ::serde_json::from_slice(request.body().as_slice())?; let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); @@ -248,14 +255,19 @@ mod tests { } } + #[derive(Debug, Serialize, Deserialize)] + struct RequestBody { + room_id: RoomId, + } + /// The response to a request to create a new room alias. #[derive(Clone, Copy, Debug)] pub struct Response; - impl TryFrom>> for Response { + impl TryFrom>> for Response { type Error = Error; - fn try_from(http_response: HttpResponse>) -> Result { + fn try_from(http_response: http::Response>) -> Result { if http_response.status().is_success() { Ok(Response) } else { @@ -264,14 +276,15 @@ mod tests { } } - impl TryFrom for HttpResponse> { + impl TryFrom for http::Response> { type Error = Error; - fn try_from(_response: Response) -> Result>, Self::Error> { - let response = HttpResponse::builder() + fn try_from(_: Response) -> Result>, Self::Error> { + let response = http::Response::builder() .header(CONTENT_TYPE, "application/json") .body(b"{}".to_vec()) .unwrap(); + Ok(response) } } From 70cd077f293ebd2c76302a609cef97d00a29b785 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Thu, 25 Jul 2019 10:19:57 -0700 Subject: [PATCH 149/295] Update dependencies. --- Cargo.toml | 9 +++++---- src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c936d3d1..b39c352b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,14 @@ edition = "2018" [dependencies] http = "0.1.17" -serde_json = "1.0.39" +serde_json = "1.0.40" serde_urlencoded = "0.5.5" -ruma-identifiers = "0.13.0" +ruma-identifiers = "0.13.1" [dev-dependencies] -url = "1.7.2" +url = "2.0.0" +percent-encoding = "2.0.0" [dev-dependencies.serde] -version = "1.0.92" +version = "1.0.97" features = ["derive"] diff --git a/src/lib.rs b/src/lib.rs index 36962a20..a1eae490 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,10 +185,10 @@ mod tests { use std::convert::TryFrom; use http::{self, header::CONTENT_TYPE, method::Method}; + use percent_encoding; use ruma_identifiers::{RoomAliasId, RoomId}; use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; - use url::percent_encoding; use crate::{Endpoint, Error, Metadata}; From 777e9c4c70ef00b8e132142ec1df277e39f7c509 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 20 Jul 2019 12:41:54 +0200 Subject: [PATCH 150/295] 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 151/295] 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 152/295] 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 153/295] 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 { From 78507b1d8bb032294eda26f3c6992a44ced246ce Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 30 Jul 2019 17:37:09 +0200 Subject: [PATCH 154/295] Update dependencies, bump version to 0.10.0 --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b39c352b..9ad93586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,19 +9,19 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.9.0" +version = "0.10.0" edition = "2018" [dependencies] -http = "0.1.17" +http = "0.1.18" serde_json = "1.0.40" -serde_urlencoded = "0.5.5" -ruma-identifiers = "0.13.1" +serde_urlencoded = "0.6.0" +ruma-identifiers = "0.14.0" [dev-dependencies] url = "2.0.0" percent-encoding = "2.0.0" [dev-dependencies.serde] -version = "1.0.97" +version = "1.0.98" features = ["derive"] From 4f03c4dce207d23680ec4c55623f7a4b432f2bbe Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 16:08:35 -0700 Subject: [PATCH 155/295] Re-export ruma-api-macros from ruma-api. --- Cargo.toml | 27 ++++++- ruma-api-macros/src/api/mod.rs | 80 +++++++++---------- ruma-api-macros/src/api/request.rs | 16 ++-- ruma-api-macros/src/api/response.rs | 8 +- ruma-api-macros/src/lib.rs | 4 +- src/lib.rs | 16 ++++ .../tests => tests}/ruma_api_macros.rs | 33 +++++++- 7 files changed, 122 insertions(+), 62 deletions(-) rename {ruma-api-macros/tests => tests}/ruma_api_macros.rs (66%) diff --git a/Cargo.toml b/Cargo.toml index 9ad93586..9edc6d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,29 @@ serde_json = "1.0.40" serde_urlencoded = "0.6.0" ruma-identifiers = "0.14.0" -[dev-dependencies] -url = "2.0.0" -percent-encoding = "2.0.0" +[dependencies.percent-encoding] +version = "2.0.0" +optional = true -[dev-dependencies.serde] +[dependencies.ruma-api-macros] +version = "0.6.0" +path = "ruma-api-macros" +optional = true + +[dependencies.serde] version = "1.0.98" features = ["derive"] +optional = true + +[dependencies.url] +version = "2.0.0" +optional = true + +[features] +default = ["with-ruma-api-macros"] +with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] + +[workspace] +members = [ + "ruma-api-macros", +] diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index a656becb..90f7b28b 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -142,10 +142,10 @@ impl ToTokens for Api { #path_var_ident: { let segment = path_segments.get(#i).unwrap().as_bytes(); let decoded = - ::url::percent_encoding::percent_decode(segment) + ruma_api::exports::percent_encoding::percent_decode(segment) .decode_utf8_lossy(); #ty::deserialize(decoded.into_deserializer()) - .map_err(|e: ::serde_json::error::Error| e)? + .map_err(|e: ruma_api::exports::serde_json::error::Error| e)? } } }); @@ -171,7 +171,7 @@ impl ToTokens for Api { #request_query_init_fields }; - url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); + url.set_query(Some(&ruma_api::exports::serde_urlencoded::to_string(request_query)?)); } } else { TokenStream::new() @@ -180,7 +180,7 @@ impl ToTokens for Api { let extract_request_query = if self.request.has_query_fields() { quote! { let request_query: RequestQuery = - ::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; + ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; } } else { TokenStream::new() @@ -225,7 +225,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 = ruma_api::exports::http::Request::new(ruma_api::exports::serde_json::to_vec(&request_body)?); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -235,11 +235,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 = ruma_api::exports::http::Request::new(ruma_api::exports::serde_json::to_vec(&request_body)?); } } else { quote! { - let mut http_request = ::http::Request::new(Vec::new()); + let mut http_request = ruma_api::exports::http::Request::new(Vec::new()); } }; @@ -247,12 +247,12 @@ impl ToTokens for Api { let ty = &field.ty; quote! { let request_body: #ty = - ::serde_json::from_slice(request.body().as_slice())?; + ruma_api::exports::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())?; + ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; } } else { TokenStream::new() @@ -278,11 +278,11 @@ impl ToTokens for Api { let field_type = &field.ty; quote! { - ::serde_json::from_slice::<#field_type>(http_response.into_body().as_slice())? + ruma_api::exports::serde_json::from_slice::<#field_type>(http_response.into_body().as_slice())? } } else if self.response.has_body_fields() { quote! { - ::serde_json::from_slice::(http_response.into_body().as_slice())? + ruma_api::exports::serde_json::from_slice::(http_response.into_body().as_slice())? } } else { quote! { @@ -309,7 +309,7 @@ impl ToTokens for Api { let try_serialize_response_body = if self.response.has_body() { let body = self.response.to_body(); quote! { - ::serde_json::to_vec(&#body)? + ruma_api::exports::serde_json::to_vec(&#body)? } } else { quote! { @@ -317,29 +317,28 @@ impl ToTokens for Api { } }; - 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 request_doc = format!( + "Data for a request to the `{}` API endpoint.\n\n{}", + name, + description.value() + ); let response_doc = format!("Data in the response from the `{}` API endpoint.", name); let api = quote! { - use ::ruma_api::Endpoint as _; - use ::serde::Deserialize as _; - use ::serde::de::{Error as _, IntoDeserializer as _}; + use ruma_api::Endpoint as _; + use ruma_api::exports::serde::Deserialize as _; + use ruma_api::exports::serde::de::{Error as _, IntoDeserializer as _}; - use ::std::convert::{TryInto as _}; - - #[doc = #endpoint_doc] - #[derive(Debug)] - pub struct Endpoint; + use std::convert::{TryInto as _}; #[doc = #request_doc] #request_types - impl ::std::convert::TryFrom<::http::Request>> for Request { - type Error = ::ruma_api::Error; + impl std::convert::TryFrom>> for Request { + type Error = ruma_api::Error; #[allow(unused_variables)] - fn try_from(request: ::http::Request>) -> Result { + fn try_from(request: ruma_api::exports::http::Request>) -> Result { #extract_request_path #extract_request_query #extract_request_headers @@ -354,24 +353,24 @@ impl ToTokens for Api { } } - impl ::std::convert::TryFrom for ::http::Request> { - type Error = ::ruma_api::Error; + impl std::convert::TryFrom for ruma_api::exports::http::Request> { + type Error = ruma_api::Error; #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { - let metadata = Endpoint::METADATA; + let metadata = Request::METADATA; // Use dummy homeserver url which has to be overwritten in // 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(); + let mut url = ruma_api::exports::url::Url::parse("http://invalid-host-please-change/").unwrap(); { #set_request_path } { #set_request_query } #create_http_request - *http_request.method_mut() = ::http::Method::#method; + *http_request.method_mut() = ruma_api::exports::http::Method::#method; *http_request.uri_mut() = url.into_string().parse().unwrap(); { #add_headers_to_request } @@ -383,13 +382,13 @@ impl ToTokens for Api { #[doc = #response_doc] #response_types - impl ::std::convert::TryFrom for ::http::Response> { - type Error = ::ruma_api::Error; + impl std::convert::TryFrom for ruma_api::exports::http::Response> { + 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") + let response = ruma_api::exports::http::Response::builder() + .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") #serialize_response_headers .body(#try_serialize_response_body) .unwrap(); @@ -397,11 +396,11 @@ impl ToTokens for Api { } } - impl ::std::convert::TryFrom<::http::Response>> for Response { - type Error = ::ruma_api::Error; + impl std::convert::TryFrom>> for Response { + type Error = ruma_api::Error; #[allow(unused_variables)] - fn try_from(http_response: ::http::Response>) -> Result { + fn try_from(http_response: ruma_api::exports::http::Response>) -> Result { if http_response.status().is_success() { #extract_response_headers @@ -415,14 +414,13 @@ impl ToTokens for Api { } } - impl ::ruma_api::Endpoint for Endpoint { - type Request = Request; + impl ruma_api::Endpoint for Request { type Response = Response; /// Metadata for the `#name` endpoint. - const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { + const METADATA: ruma_api::Metadata = ruma_api::Metadata { description: #description, - method: ::http::Method::#method, + method: ruma_api::exports::http::Method::#method, name: #name, path: #path, rate_limited: #rate_limited, diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 865f040e..e6ac4451 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -28,8 +28,8 @@ impl Request { quote! { headers.append( - ::http::header::#header_name, - ::http::header::HeaderValue::from_str(request.#field_name.as_ref()) + ruma_api::exports::http::header::#header_name, + ruma_api::exports::http::header::HeaderValue::from_str(request.#field_name.as_ref()) .expect("failed to convert value into HeaderValue"), ); } @@ -52,9 +52,9 @@ impl Request { let header_name_string = header_name.to_string(); quote! { - #field_name: headers.get(::http::header::#header_name) + #field_name: headers.get(ruma_api::exports::http::header::#header_name) .and_then(|v| v.to_str().ok()) - .ok_or(::serde_json::Error::missing_field(#header_name_string))? + .ok_or(ruma_api::exports::serde_json::Error::missing_field(#header_name_string))? .to_owned() } }); @@ -261,7 +261,7 @@ impl ToTokens for Request { quote_spanned! {span=> /// Data in the request body. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestBody(#ty); } } else if self.has_body_fields() { @@ -278,7 +278,7 @@ impl ToTokens for Request { quote! { /// Data in the request body. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestBody { #(#fields),* } @@ -302,7 +302,7 @@ impl ToTokens for Request { quote! { /// Data in the request path. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestPath { #(#fields),* } @@ -325,7 +325,7 @@ impl ToTokens for Request { quote! { /// Data in the request's query string. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestQuery { #(#fields),* } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 32263702..2bac5765 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -61,7 +61,7 @@ impl Response { let span = field.span(); quote_spanned! {span=> - #field_name: headers.remove(::http::header::#header_name) + #field_name: headers.remove(ruma_api::exports::http::header::#header_name) .expect("response missing expected header") .to_str() .expect("failed to convert HeaderValue to str") @@ -97,7 +97,7 @@ impl Response { let span = field.span(); Some(quote_spanned! {span=> - .header(::http::header::#header_name, response.#field_name) + .header(ruma_api::exports::http::header::#header_name, response.#field_name) }) } else { None @@ -247,7 +247,7 @@ impl ToTokens for Response { quote_spanned! {span=> /// Data in the response body. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct ResponseBody(#ty); } } else if self.has_body_fields() { @@ -264,7 +264,7 @@ impl ToTokens for Response { quote! { /// Data in the response body. - #[derive(Debug, Deserialize, Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct ResponseBody { #(#fields),* } diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 909463c5..b87f0100 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -139,11 +139,10 @@ mod api; /// /// # Examples /// -/// ```rust,no_run +/// ```rust,ignore /// # fn main() { /// pub mod some_endpoint { /// use ruma_api_macros::ruma_api; -/// use serde::{Deserialize, Serialize}; /// /// ruma_api! { /// metadata { @@ -179,7 +178,6 @@ mod api; /// /// pub mod newtype_body_endpoint { /// use ruma_api_macros::ruma_api; -/// use serde::{Deserialize, Serialize}; /// /// #[derive(Clone, Debug, Deserialize, Serialize)] /// pub struct MyCustomType { diff --git a/src/lib.rs b/src/lib.rs index a1eae490..0c5f66ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,22 @@ use ruma_identifiers; use serde_json; use serde_urlencoded; +#[cfg(feature = "with-ruma-api-macros")] +pub use ruma_api_macros::ruma_api; + +#[cfg(feature = "with-ruma-api-macros")] +#[doc(hidden)] +/// This module is used to support the generated code from ruma-api-macros. +/// It is not considered part of ruma-api's public API. +pub mod exports { + pub use http; + pub use percent_encoding; + pub use serde; + pub use serde_json; + pub use serde_urlencoded; + pub use url; +} + /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. diff --git a/ruma-api-macros/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs similarity index 66% rename from ruma-api-macros/tests/ruma_api_macros.rs rename to tests/ruma_api_macros.rs index 292f1c7a..40cc030e 100644 --- a/ruma-api-macros/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,6 +1,5 @@ pub mod some_endpoint { - use ruma_api_macros::ruma_api; - use serde::{Deserialize, Serialize}; + use ruma_api::ruma_api; ruma_api! { metadata { @@ -40,3 +39,33 @@ pub mod some_endpoint { } } } + +pub mod newtype_body_endpoint { + use ruma_api_macros::ruma_api; + + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + pub struct MyCustomType { + pub foo: String, + } + + ruma_api! { + metadata { + description: "Does something.", + 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, + } + } +} From 6e7e6b5aec3e4ac63927c43c46c6505e959d74ed Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:07:33 -0700 Subject: [PATCH 156/295] Run CI against all workspace members. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d1189142..1cf8fb23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ before_script: script: - "cargo audit" - "cargo fmt --all -- --check" - - "cargo clippy --all-targets --all-features -- -D warnings" - - "cargo build --verbose" - - "cargo test --verbose" + - "cargo clippy --all --all-targets --all-features -- -D warnings" + - "cargo build --all --verbose" + - "cargo test --all --verbose" notifications: email: false irc: From 1bbdbff5928117a1c5ab071b781552e138dcb658 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:37:26 -0700 Subject: [PATCH 157/295] Only build PRs and the master branch on CI. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1cf8fb23..d81ab5dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ script: - "cargo clippy --all --all-targets --all-features -- -D warnings" - "cargo build --all --verbose" - "cargo test --all --verbose" +if: "type != push OR (tag IS blank AND branch = master)" notifications: email: false irc: From 3e9fd80a7ec2b0338ef463904024aca40bd0c0da Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:45:18 -0700 Subject: [PATCH 158/295] Update dependencies and remove unnecessary ones. --- Cargo.toml | 2 +- ruma-api-macros/Cargo.toml | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9edc6d6c..c61d30b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" [dependencies] http = "0.1.18" serde_json = "1.0.40" -serde_urlencoded = "0.6.0" +serde_urlencoded = "0.6.1" ruma-identifiers = "0.14.0" [dependencies.percent-encoding] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 02b03e52..34ff775f 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -13,23 +13,13 @@ version = "0.6.0" edition = "2018" [dependencies] -quote = "0.6.12" +quote = "0.6.13" ruma-api = "0.9.0" proc-macro2 = "0.4.30" [dependencies.syn] -version = "0.15.35" +version = "0.15.42" features = ["full"] -[dev-dependencies] -http = "0.1.17" -serde_json = "1.0.39" -serde_urlencoded = "0.5.5" -url = "1.7.2" - -[dev-dependencies.serde] -version = "1.0.92" -features = ["derive"] - [lib] proc-macro = true From 4436bbfe5dbffd0638b09388948b4b4b6264ca78 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:47:56 -0700 Subject: [PATCH 159/295] Update ruma-api-macros README and remove redundant files. --- ruma-api-macros/.gitignore | 2 -- ruma-api-macros/.travis.yml | 19 ------------------- ruma-api-macros/LICENSE | 19 ------------------- ruma-api-macros/README.md | 13 +------------ 4 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 ruma-api-macros/.gitignore delete mode 100644 ruma-api-macros/.travis.yml delete mode 100644 ruma-api-macros/LICENSE diff --git a/ruma-api-macros/.gitignore b/ruma-api-macros/.gitignore deleted file mode 100644 index 96ef6c0b..00000000 --- a/ruma-api-macros/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -Cargo.lock diff --git a/ruma-api-macros/.travis.yml b/ruma-api-macros/.travis.yml deleted file mode 100644 index 26a1fc4f..00000000 --- a/ruma-api-macros/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -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" - - "cargo test --verbose" -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 diff --git a/ruma-api-macros/LICENSE b/ruma-api-macros/LICENSE deleted file mode 100644 index 8a75e6ee..00000000 --- a/ruma-api-macros/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -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/ruma-api-macros/README.md b/ruma-api-macros/README.md index 1024dfe7..5fe82430 100644 --- a/ruma-api-macros/README.md +++ b/ruma-api-macros/README.md @@ -1,6 +1,6 @@ # ruma-api-macros -[![Build Status](https://travis-ci.org/ruma/ruma-api-macros.svg?branch=master)](https://travis-ci.org/ruma/ruma-api-macros) +[![Build Status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) **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. @@ -10,17 +10,6 @@ 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(proc_macro, try_from)] - -extern crate http; -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; From 97c1a37f0c8c38a181d6bddd79eb2439012ef65e Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:49:06 -0700 Subject: [PATCH 160/295] Update homepage and repository URLs for ruma-api-macros. --- ruma-api-macros/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 34ff775f..8eafa0d8 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -3,12 +3,12 @@ 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" +homepage = "https://github.com/ruma/ruma-api" keywords = ["matrix", "chat", "messaging", "ruma"] license = "MIT" name = "ruma-api-macros" readme = "README.md" -repository = "https://github.com/ruma/ruma-api-macros" +repository = "https://github.com/ruma/ruma-api" version = "0.6.0" edition = "2018" From 12cfa48fc501841605222084eb0873f284ec0934 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 17:50:11 -0700 Subject: [PATCH 161/295] Bump ruma-api-macros to 0.7.0. --- Cargo.toml | 2 +- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c61d30b3..bd577013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ version = "2.0.0" optional = true [dependencies.ruma-api-macros] -version = "0.6.0" +version = "0.7.0" path = "ruma-api-macros" optional = true diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 8eafa0d8..4aa4dee6 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.6.0" +version = "0.7.0" edition = "2018" [dependencies] From d8830770db28773ee54015284abae018f33c8e5e Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 18:40:21 -0700 Subject: [PATCH 162/295] Remove ruma-api as a dependency from ruma-api-macros. --- ruma-api-macros/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 4aa4dee6..daa70b3b 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -14,7 +14,6 @@ edition = "2018" [dependencies] quote = "0.6.13" -ruma-api = "0.9.0" proc-macro2 = "0.4.30" [dependencies.syn] From ef2535a7c21458bb776266a8f79a321b48b75659 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Fri, 2 Aug 2019 18:41:03 -0700 Subject: [PATCH 163/295] Bump ruma-api-macros to 0.7.1. --- ruma-api-macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index daa70b3b..0044f964 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.7.0" +version = "0.7.1" edition = "2018" [dependencies] From 3b3744153fc0493447d5ecbd9f57bbd968eaa5c7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 16 Aug 2019 21:42:26 +0200 Subject: [PATCH 164/295] Update dependencies --- Cargo.toml | 8 ++++---- ruma-api-macros/Cargo.toml | 6 +++--- ruma-api-macros/src/api/attribute.rs | 14 ++++++-------- ruma-api-macros/src/api/mod.rs | 17 +++++++---------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd577013..57a6d5d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,21 +19,21 @@ serde_urlencoded = "0.6.1" ruma-identifiers = "0.14.0" [dependencies.percent-encoding] -version = "2.0.0" +version = "2.1.0" optional = true [dependencies.ruma-api-macros] -version = "0.7.0" +version = "0.7.1" path = "ruma-api-macros" optional = true [dependencies.serde] -version = "1.0.98" +version = "1.0.99" features = ["derive"] optional = true [dependencies.url] -version = "2.0.0" +version = "2.1.0" optional = true [features] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 0044f964..2f78b45c 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -13,11 +13,11 @@ version = "0.7.1" edition = "2018" [dependencies] -quote = "0.6.13" -proc-macro2 = "0.4.30" +quote = "1.0.1" +proc-macro2 = "1.0.1" [dependencies.syn] -version = "0.15.42" +version = "1.0.2" features = ["full"] [lib] diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index 70016d12..0e8c55ee 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -1,7 +1,6 @@ //! Details of the `#[ruma_api(...)]` attributes. use syn::{ - parenthesized, parse::{Parse, ParseStream}, Ident, Token, }; @@ -24,7 +23,8 @@ impl Meta { segments, } => { if segments.len() == 1 && segments[0].ident == "ruma_api" { - Ok(syn::parse2(attr.tts) + Ok(attr + .parse_args() .expect("ruma_api! could not parse request field attributes")) } else { Err(attr) @@ -46,15 +46,13 @@ pub struct MetaNameValue { impl Parse for Meta { fn parse(input: ParseStream) -> syn::Result { - let content; - let _ = parenthesized!(content in input); - let ident = content.parse()?; + let ident = input.parse()?; - if content.peek(Token![=]) { - let _ = content.parse::(); + if input.peek(Token![=]) { + let _ = input.parse::(); Ok(Meta::NameValue(MetaNameValue { name: ident, - value: content.parse()?, + value: input.parse()?, })) } else { Ok(Meta::Word(ident)) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 90f7b28b..08781213 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -24,19 +24,16 @@ pub fn strip_serde_attrs(field: &Field) -> Field { .into_iter() .filter(|attr| { let meta = attr - .interpret_meta() + .parse_meta() .expect("ruma_api! could not parse field attributes"); - let meta_list = match meta { - Meta::List(meta_list) => meta_list, - _ => return true, - }; - - if &meta_list.ident.to_string() == "serde" { - return false; + match meta { + Meta::List(meta_list) => { + let segments = &meta_list.path.segments; + segments.len() != 1 || segments[0].ident != "serde" + } + _ => true, } - - true }) .collect(); From 1c57a2380d93c0f582d13280d3e40d24e372a4a6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 16 Aug 2019 23:22:34 +0200 Subject: [PATCH 165/295] Disable clippy lint cognitive_complexity for ruma-api-macros --- ruma-api-macros/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index b87f0100..65c34d5d 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -28,6 +28,7 @@ clippy::wrong_pub_self_convention, clippy::wrong_self_convention )] +#![allow(clippy::cognitive_complexity)] #![recursion_limit = "256"] extern crate proc_macro; From 83bb889b643e5b890ca8c0a2617dbeb8474ee7a3 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 00:14:00 +0100 Subject: [PATCH 166/295] Bump MSRV from 1.34 to 1.34.2, test it in CI --- .travis.yml | 40 +++++++++++++++++++++++++++++++--------- README.md | 2 +- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index d81ab5dd..026c942d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,38 @@ language: "rust" cache: "cargo" +rust: + - 1.34.2 + - stable + - beta + - nightly +jobs: + allow_failures: + - rust: nightly + fast_finish: true + before_script: - - "rustup component add rustfmt" - - "rustup component add clippy" - - "cargo install --force cargo-audit" - - "cargo generate-lockfile" + - rustup component add rustfmt + - | + if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ]; then + rustup component add clippy + fi + - | + if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then + cargo install --force cargo-audit + fi + - cargo generate-lockfile script: - - "cargo audit" - - "cargo fmt --all -- --check" - - "cargo clippy --all --all-targets --all-features -- -D warnings" - - "cargo build --all --verbose" - - "cargo test --all --verbose" + - | + if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then + cargo audit + fi + - cargo fmt --all -- --check + - | + if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ]; then + cargo clippy --all --all-targets --all-features -- -D warnings + fi + - cargo build --all --verbose + - cargo test --all --verbose if: "type != push OR (tag IS blank AND branch = master)" notifications: email: false diff --git a/README.md b/README.md index 2258759a..eea65d54 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ These types can be shared by client and server code for all Matrix APIs. ## Minimum Rust version -ruma-api requires Rust 1.34 or later. +ruma-api requires Rust 1.34.2 or later. ## Documentation From 7e7041fbef738f9f66c24569cf7821a3cd56ec73 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 11:02:25 +0100 Subject: [PATCH 167/295] Allow clippy::use_self --- ruma-api-macros/src/lib.rs | 2 ++ src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 65c34d5d..5d969104 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -29,6 +29,8 @@ clippy::wrong_self_convention )] #![allow(clippy::cognitive_complexity)] +// Since we support Rust 1.34.2, we can't apply this suggestion yet +#![allow(clippy::use_self)] #![recursion_limit = "256"] extern crate proc_macro; diff --git a/src/lib.rs b/src/lib.rs index 0c5f66ef..4e83f749 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,8 @@ clippy::wrong_pub_self_convention, clippy::wrong_self_convention )] +// Since we support Rust 1.34.2, we can't apply this suggestion yet +#![allow(clippy::use_self)] use std::{ convert::{TryFrom, TryInto}, From 481f4c682ce75de67df5bf2a80ca431ce1c311ff Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 12:01:09 +0100 Subject: [PATCH 168/295] Remove unnecessary main declaration in doctest (found by clippy) --- ruma-api-macros/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 5d969104..dbcbc4db 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -143,7 +143,6 @@ mod api; /// # Examples /// /// ```rust,ignore -/// # fn main() { /// pub mod some_endpoint { /// use ruma_api_macros::ruma_api; /// @@ -208,7 +207,6 @@ mod api; /// } /// } /// } -/// # } /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { From 958a0a01c47c051eebf493234c314bc101609f63 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 12:11:38 +0100 Subject: [PATCH 169/295] Remove server-side functionality This is a temporary change that is done to get one release of ruma-client-api with ruma-events' EventResult API out. After ruma-api 0.11 is released, getting the server-side functionality to work again will be a priority. --- ruma-api-macros/src/api/mod.rs | 155 +--------------------------- ruma-api-macros/src/api/request.rs | 54 +--------- ruma-api-macros/src/api/response.rs | 66 +----------- src/lib.rs | 45 +------- 4 files changed, 14 insertions(+), 306 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 08781213..717c0c44 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -77,15 +77,7 @@ impl ToTokens for Api { let response = &self.response; let response_types = quote! { #response }; - 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 set_request_path = if self.request.has_path_fields() { let path_str = path.value(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); @@ -111,7 +103,7 @@ impl ToTokens for Api { } }); - let set_tokens = quote! { + quote! { let request_path = RequestPath { #request_path_init_fields }; @@ -121,43 +113,11 @@ impl ToTokens for Api { // the case for our placeholder url. let mut path_segments = url.path_segments_mut().unwrap(); #(#path_segment_push)* - }; - - 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()); - let path_field = self - .request - .path_field(path_var) - .expect("expected request to have path field"); - let ty = &path_field.ty; - - quote! { - #path_var_ident: { - let segment = path_segments.get(#i).unwrap().as_bytes(); - let decoded = - ruma_api::exports::percent_encoding::percent_decode(segment) - .decode_utf8_lossy(); - #ty::deserialize(decoded.into_deserializer()) - .map_err(|e: ruma_api::exports::serde_json::error::Error| e)? - } - } - }); - - let parse_tokens = quote! { - #(#path_fields,)* - }; - - (set_tokens, parse_tokens) + } } else { - let set_tokens = quote! { + 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() { @@ -174,21 +134,6 @@ impl ToTokens for Api { TokenStream::new() }; - let extract_request_query = if self.request.has_query_fields() { - quote! { - let request_query: RequestQuery = - ruma_api::exports::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 add_headers = self.request.add_headers_to_request(); quote! { @@ -199,20 +144,6 @@ 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 @@ -240,36 +171,6 @@ 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 = - ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; - } - } else if self.request.has_body_fields() { - quote! { - let request_body: RequestBody = - ruma_api::exports::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 try_deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { let field_type = &field.ty; @@ -301,19 +202,6 @@ impl ToTokens for Api { TokenStream::new() }; - let serialize_response_headers = self.response.apply_header_fields(); - - let try_serialize_response_body = if self.response.has_body() { - let body = self.response.to_body(); - quote! { - ruma_api::exports::serde_json::to_vec(&#body)? - } - } else { - quote! { - "{}".as_bytes().to_vec() - } - }; - let request_doc = format!( "Data for a request to the `{}` API endpoint.\n\n{}", name, @@ -331,25 +219,6 @@ impl ToTokens for Api { #[doc = #request_doc] #request_types - impl std::convert::TryFrom>> for Request { - type Error = ruma_api::Error; - - #[allow(unused_variables)] - fn try_from(request: ruma_api::exports::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 std::convert::TryFrom for ruma_api::exports::http::Request> { type Error = ruma_api::Error; @@ -379,20 +248,6 @@ impl ToTokens for Api { #[doc = #response_doc] #response_types - impl std::convert::TryFrom for ruma_api::exports::http::Response> { - type Error = ruma_api::Error; - - #[allow(unused_variables)] - fn try_from(response: Response) -> Result { - let response = ruma_api::exports::http::Response::builder() - .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") - #serialize_response_headers - .body(#try_serialize_response_body) - .unwrap(); - Ok(response) - } - } - impl std::convert::TryFrom>> for Response { type Error = ruma_api::Error; diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index e6ac4451..c8e04e40 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -40,30 +40,6 @@ 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) = 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_string = header_name.to_string(); - - quote! { - #field_name: headers.get(ruma_api::exports::http::header::#header_name) - .and_then(|v| v.to_str().ok()) - .ok_or(ruma_api::exports::serde_json::Error::missing_field(#header_name_string))? - .to_owned() - } - }); - - quote! { - #(#fields,)* - } - } - /// 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()) @@ -73,6 +49,7 @@ impl Request { 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()) @@ -93,20 +70,6 @@ impl Request { 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_of_kind(RequestFieldKind::Path)) - .find(|field| { - field - .ident - .as_ref() - .expect("expected field to have an identifier") - == name - }) - } - /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { @@ -136,17 +99,6 @@ impl Request { 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( @@ -261,7 +213,7 @@ impl ToTokens for Request { quote_spanned! {span=> /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Serialize)] struct RequestBody(#ty); } } else if self.has_body_fields() { @@ -278,7 +230,7 @@ impl ToTokens for Request { quote! { /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Serialize)] struct RequestBody { #(#fields),* } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 2bac5765..9f1754d5 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -31,11 +31,6 @@ impl Response { 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 @@ -86,63 +81,6 @@ 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_name) = *response_field { - let field_name = field - .ident - .as_ref() - .expect("expected field to have an identifier"); - let span = field.span(); - - Some(quote_spanned! {span=> - .header(ruma_api::exports::http::header::#header_name, response.#field_name) - }) - } else { - None - } - }); - - quote! { - #(#header_calls)* - } - } - - /// Produces code to initialize the struct that will be used to create the response body. - pub fn to_body(&self) -> TokenStream { - if let Some(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),* - } - } - } - } - /// 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() { @@ -247,7 +185,7 @@ impl ToTokens for Response { quote_spanned! {span=> /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize)] struct ResponseBody(#ty); } } else if self.has_body_fields() { @@ -264,7 +202,7 @@ impl ToTokens for Response { quote! { /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize)] struct ResponseBody { #(#fields),* } diff --git a/src/lib.rs b/src/lib.rs index 4e83f749..b85256b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,7 +58,6 @@ pub use ruma_api_macros::ruma_api; /// It is not considered part of ruma-api's public API. pub mod exports { pub use http; - pub use percent_encoding; pub use serde; pub use serde_json; pub use serde_urlencoded; @@ -68,12 +67,9 @@ pub mod exports { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: - TryFrom>, Error = Error> + TryInto>, Error = Error> -{ +pub trait Endpoint: TryInto>, Error = Error> { /// Data returned in a successful response from the endpoint. - type Response: TryFrom>, Error = Error> - + TryInto>, Error = Error>; + type Response: TryFrom>, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -202,10 +198,9 @@ mod tests { pub mod create { use std::convert::TryFrom; - use http::{self, header::CONTENT_TYPE, method::Method}; - use percent_encoding; + use http::{self, method::Method}; use ruma_identifiers::{RoomAliasId, RoomId}; - use serde::{de::IntoDeserializer, Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; use serde_json; use crate::{Endpoint, Error, Metadata}; @@ -254,25 +249,6 @@ mod tests { } } - impl TryFrom>> for Request { - type Error = Error; - - fn try_from(request: http::Request>) -> Result { - let request_body: RequestBody = - ::serde_json::from_slice(request.body().as_slice())?; - let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); - Ok(Request { - room_id: request_body.room_id, - room_alias: { - let segment = path_segments.get(5).unwrap().as_bytes(); - let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); - RoomAliasId::deserialize(decoded.into_deserializer()) - .map_err(|e: serde_json::error::Error| e)? - }, - }) - } - } - #[derive(Debug, Serialize, Deserialize)] struct RequestBody { room_id: RoomId, @@ -293,18 +269,5 @@ mod tests { } } } - - impl TryFrom for http::Response> { - type Error = Error; - - fn try_from(_: Response) -> Result>, Self::Error> { - let response = http::Response::builder() - .header(CONTENT_TYPE, "application/json") - .body(b"{}".to_vec()) - .unwrap(); - - Ok(response) - } - } } } From 540a69a8e45ff117f0a1bb0272bbc40c526ed12e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 19:56:00 +0100 Subject: [PATCH 170/295] Remove percent-encoding dependency (not currently used) --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57a6d5d1..a6aaaf55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,6 @@ serde_json = "1.0.40" serde_urlencoded = "0.6.1" ruma-identifiers = "0.14.0" -[dependencies.percent-encoding] -version = "2.1.0" -optional = true - [dependencies.ruma-api-macros] version = "0.7.1" path = "ruma-api-macros" @@ -38,7 +34,7 @@ optional = true [features] default = ["with-ruma-api-macros"] -with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] +with-ruma-api-macros = ["ruma-api-macros", "serde", "url"] [workspace] members = [ From 4a03892ad63196ac48a37baa3ddfec419c516965 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 20:05:07 +0100 Subject: [PATCH 171/295] Bump versions --- Cargo.toml | 3 +-- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6aaaf55..53308567 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.10.0" +version = "0.11.0" edition = "2018" [dependencies] @@ -19,7 +19,6 @@ serde_urlencoded = "0.6.1" ruma-identifiers = "0.14.0" [dependencies.ruma-api-macros] -version = "0.7.1" path = "ruma-api-macros" optional = true diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 2f78b45c..1d15f5aa 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.7.1" +version = "0.8" edition = "2018" [dependencies] From 0889d1e0efd38aea688034e27d7b8e383e7d13b9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 29 Oct 2019 20:06:16 +0100 Subject: [PATCH 172/295] Fix version of ruma-api-macros --- ruma-api-macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 1d15f5aa..a379d8eb 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.8" +version = "0.8.0" edition = "2018" [dependencies] From 59056808de84b315b59c8a5479778d7681ab41af Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 2 Nov 2019 14:15:49 +0100 Subject: [PATCH 173/295] =?UTF-8?q?Remove=20#![deny(warnings)],=20#![warn(?= =?UTF-8?q?clippy::=E2=80=A6)]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b85256b2..acc02fa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,26 +13,7 @@ #![deny( missing_copy_implementations, missing_debug_implementations, - missing_docs, - 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::used_underscore_binding, - clippy::wrong_pub_self_convention, - clippy::wrong_self_convention + missing_docs )] // Since we support Rust 1.34.2, we can't apply this suggestion yet #![allow(clippy::use_self)] From e5a3027b3b858b38d0eb93676ffaf0b4208af96d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 4 Nov 2019 11:37:56 +0100 Subject: [PATCH 174/295] Add CHANGELOG.md, ruma-api-macros/CHANGELOG.md --- CHANGELOG.md | 91 ++++++++++++++++++++++++++++++ ruma-api-macros/CHANGELOG.md | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 ruma-api-macros/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..6f75af79 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,91 @@ +# [unreleased] + +# 0.11.0 + +Breaking changes: + +* To be able to use ruma-event's `EventResult` in ruma-client without large-ish refactorings to ruma-api, we removed support for the server-side use case in ruma-api 0.11.0. It will be added back in a future release. + +Improvements: + +* Our CI now tests ruma-api on Rust 1.34.2, beta and nightly in addition to stable +* Updated syn and quote to 1.0 + +# 0.10.0 + +Breaking changes: + +* The `Endpoint` trait is now implemented directly on the relevant request type rather than having both the request and response be associated types. + +Improvements: + +* ruma-api now re-exports the `ruma_api` macro from ruma-api-macros. Downstream crates no longer need to depend on ruma-api-macros directly. +* The ruma-api and ruma-api-macros repositories have been merged into one Cargo workspace for easier dependency management and development. + +# 0.9.0 + +Breaking changes: + +* The `Request` and `Response` associated types on the `Endpoint` trait are now bounded by `std::convert::TryFrom` instead of `futures::future::FutureFrom`. This was done in preparation for futures 0.3 which does not have this trait. +* The conversions required to and from `http::Request` and `http::Response` for the `Request` and `Response` associated types on the `Endpoint` trait now use `Vec` as the body type. This removes the dependency on hyper. It's possible this will change again in a future release. See https://github.com/rustasync/team/issues/84 for details. + +Improvements: + +* Internal code quality improvements via clippy and rustfmt. + +# 0.8.0 + +Breaking changes: + +* The `Error` type is now an opaque struct that hides implementation details. +* Updates to ruma-identifiers 0.13. + +Improvements: + +* ruma-api now uses clippy to improve code quality. + +# 0.7.0 + +Improvements: + +* ruma-api now runs on stable Rust, requiring version 1.34 or higher. +* Updated all dependencies for upstream improvements. +* Updated all code to use Rust edition 2018. + +# 0.6.0 + +Breaking changes: + +* Hyper has been updated to version 0.12. +* A new variant to the `Error` enum for hyper errors has been added. +* Conversions between this crate's request and response types and the http crate's request and response types are now bidirectional. + +# 0.5.0 + +Breaking changes: + +* Types from hyper have been replaced with types from the http crate. +* The `Error` enum can no longer be matched exhaustively, to allow for future expansion without breaking the crate's API. + +# 0.4.0 + +Breaking changes: + +The crate has been redesign to focus on conversions between an endpoint's request and response types and Hyper request and response types. Implementations are expected to be generated via [ruma-api-macros](https://github.com/ruma/ruma-api-macros). + +# 0.3.0 + +Breaking changes: + +* `Endpoint::router_path` now returns a `&'static str` +* Added new required methods to `Endpoint`: `name`, `description`, `requires_authentication`, and `rate_limited`. + +# 0.2.0 + +Breaking changes: + +* `Endpoint::Query_params` must now be `Deserialize + Serialize`. + +# 0.1.0 + +Initial release. diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md new file mode 100644 index 00000000..71dff348 --- /dev/null +++ b/ruma-api-macros/CHANGELOG.md @@ -0,0 +1,104 @@ +# [unreleased] + +# 0.7.1 + +Bug fixes: + +* Removed unnecessary dependency on ruma-api 0.9.0. + +# 0.7.0 + +Breaking changes: + +* Updated to ruma-api 0.10.0. + +Improvements: + +* ruma-api now re-exports the `ruma_api` macro from ruma-api-macros. Downstream crates no longer need to depend on ruma-api-macros directly. +* The code generated by the `ruma_api` macro now refers to external dependencies via re-exports in ruma-api, so it is no longer necessary to add them to the dependencies of downstream crates directly. +* The ruma-api and ruma-api-macros repositories have been merged into one Cargo workspace for easier dependency management and development. + +# 0.6.0 + +Breaking changes: + +* Updated to ruma-api 0.9.0. + +# 0.5.0 + +Breaking changes: + +* Updated to ruma-api 0.8.0. + +Improvements: + +* Generated documentation now includes the names and descriptions of API endpoints. +* Remove unidiomatic use of `Tokens::append_all` from the `quote` crate. +* ruma-api-macros now uses clippy to improve code quality. + +# 0.4.0 + +Improvements: + +* ruma-api-macros now runs on stable Rust, requiring version 1.34 or higher. +* Updated all dependencies for upstream improvements. + +# 0.3.1 + +Improved: + +* Code updated to use Rust 2018 idioms. + +Bug fixes: + +* The crate will now compile when serde's `derive` feature is enabled. + +# 0.3.0 + +Breaking changes: + +* The procedural macro now uses hyper's `Body` type instead of `Vec`. This may prove to be a temporary change as ideally we want ruma-api-macros to be agnostic to HTTP client library. + +Improvements: + +* Updated to the latest versions of all dependencies. +* Improved error reporting for the procedural macro. +* Conversions between this crate's request and response types and the http crate's request and response types are now bidirectional. +* Request and response types now implement `Clone`. + +# 0.2.2 + +Improvements: + +* Updated to proc-macro2 0.4 for compatibility with the latest nightly Rust. + +Bug fixes: + +* Attributes that don't affect the macro are now ignored instead of causing a panic. +* Added missing commas in request query struct initialization that was causing a syntax error. +* Fixed stripping of serde attributes that was causing them to leak through and trigger a custom attribute error. +* Fixed creation of requests with an empty body that were not correctly using a `Vec` as the body type. + +# 0.2.1 + +Version 0.2.1 was yanked from crates.io due to a dependency issue. Changes since version 0.2.0 are in the release notes for version 0.2.2. + +# 0.2.0 + +Breaking changes: + +* The dependency on the `hyper` crate has been removed. The macro now uses types from the `http` crate. The macro is also compatible with the forthcoming version 0.12 of `hyper`. +* The `method` field in the `metadata` block is now written as the name of an associated constant from `http::Method`, e.g. `GET`. +* HTTP headers are now specified in request and response blocks using `String` as the type, and the name of the constant of the header from `http::header` in the field's attributes. For example: + ``` rust + #[ruma_api(header = "CONTENT_TYPE")] + pub content_type: String + ``` + +Improvements: + +* The macro is built using version 0.13 of the `syn` crate. + +# 0.1.0 + +Initial release. From e8858f119d4a0edda19385bd54b83be9b374d63d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 22:28:24 +0100 Subject: [PATCH 175/295] Fix outdated doc comment --- ruma-api-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index dbcbc4db..586e0014 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -121,7 +121,7 @@ mod api; /// There is also a special attribute available to control how the struct is created from a /// `http::Request`: /// -/// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP +/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP /// headers on the response. /// The value must implement `AsRef`. /// Generally this is a `String`. From 47267cc2ba8f6a90014ce62c4ada1d739fec20b2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 22:28:43 +0100 Subject: [PATCH 176/295] Slightly simplify attribute parsing code --- ruma-api-macros/src/api/attribute.rs | 20 ++++++++++++-------- ruma-api-macros/src/api/request.rs | 10 +++++----- ruma-api-macros/src/api/response.rs | 10 +++++----- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index 0e8c55ee..b587fe65 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -14,23 +14,27 @@ 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 { + /// Check if the given attribute is a ruma_api attribute. If it is, parse it. + /// + /// # Panics + /// + /// Panics if the given attribute is a ruma_api attribute, but fails to parse. + pub fn from_attribute(attr: &syn::Attribute) -> Option { match &attr.path { syn::Path { leading_colon: None, segments, } => { if segments.len() == 1 && segments[0].ident == "ruma_api" { - Ok(attr - .parse_args() - .expect("ruma_api! could not parse request field attributes")) + Some( + attr.parse_args() + .expect("ruma_api! could not parse request field attributes"), + ) } else { - Err(attr) + None } } - _ => Err(attr), + _ => None, } } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index c8e04e40..a4c271a7 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -134,10 +134,10 @@ impl From> for Request { let mut field_kind = RequestFieldKind::Body; let mut header = None; - field.attrs = field.attrs.into_iter().filter_map(|attr| { + field.attrs.retain(|attr| { let meta = match Meta::from_attribute(attr) { - Ok(meta) => meta, - Err(attr) => return Some(attr), + Some(meta) => meta, + None => return true, }; match meta { @@ -163,8 +163,8 @@ impl From> for Request { } } - None - }).collect(); + false + }); if field_kind == RequestFieldKind::Body { assert!( diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 9f1754d5..b0ad5165 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -104,10 +104,10 @@ impl From> for Response { let mut field_kind = ResponseFieldKind::Body; let mut header = None; - field.attrs = field.attrs.into_iter().filter_map(|attr| { + field.attrs.retain(|attr| { let meta = match Meta::from_attribute(attr) { - Ok(meta) => meta, - Err(attr) => return Some(attr), + Some(meta) => meta, + None => return true, }; match meta { @@ -131,8 +131,8 @@ impl From> for Response { } } - None - }).collect(); + false + }); match field_kind { ResponseFieldKind::Body => { From c8f13421441719ef8777d6173ded0b08ddf970a7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 22:32:10 +0100 Subject: [PATCH 177/295] Disallow multiple ruma_api(body) attributes in one request / response --- ruma-api-macros/src/api/request.rs | 5 +++++ ruma-api-macros/src/api/response.rs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index a4c271a7..5028724d 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -144,6 +144,11 @@ impl From> for Request { Meta::Word(ident) => { match &ident.to_string()[..] { "body" => { + assert!( + !has_newtype_body, + "ruma_api! body attribute can only be used once per request definition" + ); + has_newtype_body = true; field_kind = RequestFieldKind::NewtypeBody; } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index b0ad5165..8bf74528 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -116,6 +116,10 @@ impl From> for Response { ident == "body", "ruma_api! single-word attribute on responses must be: body" ); + assert!( + !has_newtype_body, + "ruma_api! body attribute can only be used once per response definition" + ); has_newtype_body = true; field_kind = ResponseFieldKind::NewtypeBody; From d6f19268325c0c6d0d56f68434e8eadf6386e06e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 22:55:04 +0100 Subject: [PATCH 178/295] Disallow body fields in GET endpoints --- ruma-api-macros/src/api/mod.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 717c0c44..b09f371f 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -52,11 +52,19 @@ pub struct Api { impl From for Api { fn from(raw_api: RawApi) -> Self { - Self { + let res = Self { metadata: raw_api.metadata.into(), request: raw_api.request.into(), response: raw_api.response.into(), - } + }; + + assert!( + !(res.metadata.method == "GET" + && (res.request.has_body_fields() || res.request.newtype_body_field().is_some())), + "GET endpoints can't have body fields" + ); + + res } } From 68aaa59edaabcac64bcb0c1d401943be7fbdd7f9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 23:06:21 +0100 Subject: [PATCH 179/295] Update formatting in quote! invocations --- ruma-api-macros/src/api/mod.rs | 34 +++++++++++++++++++++--------- ruma-api-macros/src/api/request.rs | 12 +++++++++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index b09f371f..8a92c012 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -136,7 +136,9 @@ impl ToTokens for Api { #request_query_init_fields }; - url.set_query(Some(&ruma_api::exports::serde_urlencoded::to_string(request_query)?)); + url.set_query(Some(&ruma_api::exports::serde_urlencoded::to_string( + request_query, + )?)); } } else { TokenStream::new() @@ -161,7 +163,9 @@ impl ToTokens for Api { quote! { let request_body = RequestBody(request.#field_name); - let mut http_request = ruma_api::exports::http::Request::new(ruma_api::exports::serde_json::to_vec(&request_body)?); + let mut http_request = ruma_api::exports::http::Request::new( + ruma_api::exports::serde_json::to_vec(&request_body)?, + ); } } else if self.request.has_body_fields() { let request_body_init_fields = self.request.request_body_init_fields(); @@ -171,7 +175,9 @@ impl ToTokens for Api { #request_body_init_fields }; - let mut http_request = ruma_api::exports::http::Request::new(ruma_api::exports::serde_json::to_vec(&request_body)?); + let mut http_request = ruma_api::exports::http::Request::new( + ruma_api::exports::serde_json::to_vec(&request_body)?, + ); } } else { quote! { @@ -184,11 +190,15 @@ impl ToTokens for Api { let field_type = &field.ty; quote! { - ruma_api::exports::serde_json::from_slice::<#field_type>(http_response.into_body().as_slice())? + ruma_api::exports::serde_json::from_slice::<#field_type>( + http_response.into_body().as_slice(), + )? } } else if self.response.has_body_fields() { quote! { - ruma_api::exports::serde_json::from_slice::(http_response.into_body().as_slice())? + ruma_api::exports::serde_json::from_slice::( + http_response.into_body().as_slice(), + )? } } else { quote! { @@ -218,11 +228,11 @@ impl ToTokens for Api { let response_doc = format!("Data in the response from the `{}` API endpoint.", name); let api = quote! { - use ruma_api::Endpoint as _; - use ruma_api::exports::serde::Deserialize as _; use ruma_api::exports::serde::de::{Error as _, IntoDeserializer as _}; + use ruma_api::exports::serde::Deserialize as _; + use ruma_api::Endpoint as _; - use std::convert::{TryInto as _}; + use std::convert::TryInto as _; #[doc = #request_doc] #request_types @@ -237,7 +247,9 @@ impl ToTokens for Api { // Use dummy homeserver url which has to be overwritten in // the calling code. Previously (with http::Uri) this was // not required, but Url::parse only accepts absolute urls. - let mut url = ruma_api::exports::url::Url::parse("http://invalid-host-please-change/").unwrap(); + let mut url = + ruma_api::exports::url::Url::parse("http://invalid-host-please-change/") + .unwrap(); { #set_request_path } { #set_request_query } @@ -260,7 +272,9 @@ impl ToTokens for Api { type Error = ruma_api::Error; #[allow(unused_variables)] - fn try_from(http_response: ruma_api::exports::http::Response>) -> Result { + fn try_from( + http_response: ruma_api::exports::http::Response>, + ) -> Result { if http_response.status().is_success() { #extract_response_headers diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 5028724d..c5c41964 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -259,7 +259,11 @@ impl ToTokens for Request { quote! { /// Data in the request path. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive( + Debug, + ruma_api::exports::serde::Deserialize, + ruma_api::exports::serde::Serialize, + )] struct RequestPath { #(#fields),* } @@ -282,7 +286,11 @@ impl ToTokens for Request { quote! { /// Data in the request's query string. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] + #[derive( + Debug, + ruma_api::exports::serde::Deserialize, + ruma_api::exports::serde::Serialize, + )] struct RequestQuery { #(#fields),* } From ed42aaef6a13507f813a54ca2ae226055f677af0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 23:07:10 +0100 Subject: [PATCH 180/295] Fix tests --- tests/ruma_api_macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 40cc030e..ddc7b014 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -4,7 +4,7 @@ pub mod some_endpoint { ruma_api! { metadata { description: "Does something.", - method: GET, // An `http::Method` constant. No imports required. + method: POST, // An `http::Method` constant. No imports required. name: "some_endpoint", path: "/_matrix/some/endpoint/:baz", rate_limited: false, @@ -51,7 +51,7 @@ pub mod newtype_body_endpoint { ruma_api! { metadata { description: "Does something.", - method: GET, + method: PUT, name: "newtype_body_endpoint", path: "/_matrix/some/newtype/body/endpoint", rate_limited: false, From 1965efc5c7f8ee6470f9aad05e23b090c7259686 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 11 Nov 2019 23:08:24 +0100 Subject: [PATCH 181/295] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f75af79..1110bd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +Improvements: + +* Add more sanity checks + * No multiple `#[ruma_api(body)]` fields in one request / response definition + * No (newtype) body fields in GET endpoints + # 0.11.0 Breaking changes: From 5c202dac49f83fe494be5fbc3910327f76442abd Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 12 Nov 2019 01:17:52 +0100 Subject: [PATCH 182/295] Add #![warn(rust_2018_idioms)], fix warning --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index acc02fa9..8491de61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. +#![warn(rust_2018_idioms)] #![deny( missing_copy_implementations, missing_debug_implementations, @@ -62,7 +63,7 @@ pub trait Endpoint: TryInto>, Error = Error> { pub struct Error(pub(crate) InnerError); impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let message = match self.0 { InnerError::Http(_) => "An error converting to or from `http` types occurred.".into(), InnerError::Io(_) => "An I/O error occurred.".into(), From fa9ec7f14591388a0023870f0050322def3c817f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 20:37:29 +0100 Subject: [PATCH 183/295] Update doc comment --- ruma-api-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 586e0014..6249f4f4 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -100,7 +100,7 @@ mod api; /// There are also a few special attributes available to control how the struct is converted into a /// `http::Request`: /// -/// * `#[ruma_api(header = "HEADER_NAME")]`: Fields with this attribute will be treated as HTTP +/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP /// headers on the request. /// The value must implement `AsRef`. /// Generally this is a `String`. From 9699f5c68f0874fbbdb3b4f9422912e6a53baa42 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 22:24:35 +0100 Subject: [PATCH 184/295] Allow ruma_api attributes to contain multiple items, forbid some nonsensical attribute usage --- ruma-api-macros/src/api/attribute.rs | 70 ++++++++++++------ ruma-api-macros/src/api/request.rs | 81 ++++++++++---------- ruma-api-macros/src/api/response.rs | 106 +++++++++++++++------------ 3 files changed, 153 insertions(+), 104 deletions(-) diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index b587fe65..add646a8 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -1,10 +1,22 @@ //! Details of the `#[ruma_api(...)]` attributes. +use std::vec; + use syn::{ parse::{Parse, ParseStream}, + punctuated::{Pair, Punctuated}, Ident, Token, }; +/// 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, +} + /// Like syn::Meta, but only parses ruma_api attributes pub enum Meta { /// A single word, like `query` in `#[ruma_api(query)]` @@ -13,7 +25,26 @@ pub enum Meta { NameValue(MetaNameValue), } -impl Meta { +impl Parse for Meta { + fn parse(input: ParseStream) -> syn::Result { + let ident = input.parse()?; + + if input.peek(Token![=]) { + let _ = input.parse::(); + Ok(Meta::NameValue(MetaNameValue { + name: ident, + value: input.parse()?, + })) + } else { + Ok(Meta::Word(ident)) + } + } +} + +/// List of `Meta`s +pub struct MetaList(Vec); + +impl MetaList { /// Check if the given attribute is a ruma_api attribute. If it is, parse it. /// /// # Panics @@ -39,27 +70,22 @@ impl Meta { } } -/// 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 { +impl Parse for MetaList { fn parse(input: ParseStream) -> syn::Result { - let ident = input.parse()?; - - if input.peek(Token![=]) { - let _ = input.parse::(); - Ok(Meta::NameValue(MetaNameValue { - name: ident, - value: input.parse()?, - })) - } else { - Ok(Meta::Word(ident)) - } + Ok(MetaList( + Punctuated::::parse_terminated(input)? + .into_pairs() + .map(Pair::into_value) + .collect(), + )) + } +} + +impl IntoIterator for MetaList { + type Item = Meta; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index c5c41964..2d425872 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -5,7 +5,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ - attribute::{Meta, MetaNameValue}, + attribute::{Meta, MetaList, MetaNameValue}, strip_serde_attrs, }; @@ -128,59 +128,61 @@ impl Request { impl From> for Request { fn from(fields: Vec) -> Self { - let mut has_newtype_body = false; - - let fields = fields.into_iter().map(|mut field| { - let mut field_kind = RequestFieldKind::Body; + let fields: Vec<_> = fields.into_iter().map(|mut field| { + let mut field_kind = None; let mut header = None; field.attrs.retain(|attr| { - let meta = match Meta::from_attribute(attr) { - Some(meta) => meta, + let meta_list = match MetaList::from_attribute(attr) { + Some(list) => list, None => return true, }; - match meta { - Meta::Word(ident) => { - match &ident.to_string()[..] { - "body" => { - assert!( - !has_newtype_body, - "ruma_api! body attribute can only be used once per request definition" - ); + for meta in meta_list { - 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"), + match meta { + Meta::Word(ident) => { + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); + + field_kind = Some(match &ident.to_string()[..] { + "body" => RequestFieldKind::NewtypeBody, + "path" => RequestFieldKind::Path, + "query" => RequestFieldKind::Query, + _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), + }); } - } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on requests must be: header" - ); + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - header = Some(value); - field_kind = RequestFieldKind::Header; + header = Some(value); + field_kind = Some(RequestFieldKind::Header); + } } } false }); - 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(field_kind, field, header) + RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header) }).collect(); + if fields.len() > 1 { + assert!( + !fields.iter().any(|field| field.is_newtype_body()), + "ruma_api! newtype body has to be the only response field" + ) + } + Self { fields } } } @@ -360,6 +362,11 @@ impl RequestField { self.kind() == RequestFieldKind::Header } + /// Whether or not this request field is a newtype body kind. + fn is_newtype_body(&self) -> bool { + self.kind() == RequestFieldKind::NewtypeBody + } + /// Whether or not this request field is a path kind. fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 8bf74528..3daf613b 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -5,7 +5,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ - attribute::{Meta, MetaNameValue}, + attribute::{Meta, MetaList, MetaNameValue}, strip_serde_attrs, }; @@ -98,59 +98,67 @@ impl Response { impl From> for Response { fn from(fields: Vec) -> Self { - let mut has_newtype_body = false; + let fields: Vec<_> = fields + .into_iter() + .map(|mut field| { + let mut field_kind = None; + let mut header = None; - let fields = fields.into_iter().map(|mut field| { - let mut field_kind = ResponseFieldKind::Body; - let mut header = None; + field.attrs.retain(|attr| { + let meta_list = match MetaList::from_attribute(attr) { + Some(list) => list, + None => return true, + }; - field.attrs.retain(|attr| { - let meta = match Meta::from_attribute(attr) { - Some(meta) => meta, - None => return true, - }; + for meta in meta_list { + match meta { + Meta::Word(ident) => { + assert!( + ident == "body", + "ruma_api! single-word attribute on responses must be: body" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - match meta { - Meta::Word(ident) => { - assert!( - ident == "body", - "ruma_api! single-word attribute on responses must be: body" - ); - assert!( - !has_newtype_body, - "ruma_api! body attribute can only be used once per response definition" - ); + field_kind = Some(ResponseFieldKind::NewtypeBody); + } + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - has_newtype_body = true; - field_kind = ResponseFieldKind::NewtypeBody; + header = Some(value); + field_kind = Some(ResponseFieldKind::Header); + } + } } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on requests must be: header" - ); - header = Some(value); - field_kind = ResponseFieldKind::Header; + false + }); + + match field_kind.unwrap_or(ResponseFieldKind::Body) { + ResponseFieldKind::Body => ResponseField::Body(field), + ResponseFieldKind::Header => { + ResponseField::Header(field, header.expect("missing header name")) } + ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } + }) + .collect(); - false - }); - - match field_kind { - ResponseFieldKind::Body => { - 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), - } - }).collect(); + if fields.len() > 1 { + assert!( + !fields.iter().any(|field| field.is_newtype_body()), + "ruma_api! newtype body has to be the only response field" + ) + } Self { fields } } @@ -260,6 +268,14 @@ impl ResponseField { _ => false, } } + + /// Whether or not this response field is a newtype body kind. + fn is_newtype_body(&self) -> bool { + match *self { + ResponseField::NewtypeBody(..) => true, + _ => false, + } + } } /// The types of fields that a response can have, without their values. From 1334b6c9d92b9d0755f2ed4ca652111ae546fd70 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 22:44:30 +0100 Subject: [PATCH 185/295] Update readme, remove pointless newline --- CHANGELOG.md | 1 + ruma-api-macros/src/api/request.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1110bd79..1b76bbe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Improvements: * Add more sanity checks * No multiple `#[ruma_api(body)]` fields in one request / response definition + * No multiple field kind declarations `#[ruma_api(body|query|path)]` on one field * No (newtype) body fields in GET endpoints # 0.11.0 diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 2d425872..cfd9bdde 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -139,7 +139,6 @@ impl From> for Request { }; for meta in meta_list { - match meta { Meta::Word(ident) => { assert!( From 4d8cc3624f313c3c2c7a90afa196a66121919fb3 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 00:40:24 +0100 Subject: [PATCH 186/295] Partially revert previous changes since they were preparing for something that's now not going to happen, namely a use case for multiple meta items in one #[ruma_api(..)] attribute --- ruma-api-macros/src/api/attribute.rs | 52 +++++++------------------- ruma-api-macros/src/api/request.rs | 56 ++++++++++++++-------------- ruma-api-macros/src/api/response.rs | 54 +++++++++++++-------------- 3 files changed, 66 insertions(+), 96 deletions(-) diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index add646a8..df610dd9 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -1,10 +1,7 @@ //! Details of the `#[ruma_api(...)]` attributes. -use std::vec; - use syn::{ parse::{Parse, ParseStream}, - punctuated::{Pair, Punctuated}, Ident, Token, }; @@ -25,26 +22,7 @@ pub enum Meta { NameValue(MetaNameValue), } -impl Parse for Meta { - fn parse(input: ParseStream) -> syn::Result { - let ident = input.parse()?; - - if input.peek(Token![=]) { - let _ = input.parse::(); - Ok(Meta::NameValue(MetaNameValue { - name: ident, - value: input.parse()?, - })) - } else { - Ok(Meta::Word(ident)) - } - } -} - -/// List of `Meta`s -pub struct MetaList(Vec); - -impl MetaList { +impl Meta { /// Check if the given attribute is a ruma_api attribute. If it is, parse it. /// /// # Panics @@ -70,22 +48,18 @@ impl MetaList { } } -impl Parse for MetaList { +impl Parse for Meta { fn parse(input: ParseStream) -> syn::Result { - Ok(MetaList( - Punctuated::::parse_terminated(input)? - .into_pairs() - .map(Pair::into_value) - .collect(), - )) - } -} - -impl IntoIterator for MetaList { - type Item = Meta; - type IntoIter = vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + let ident = input.parse()?; + + if input.peek(Token![=]) { + let _ = input.parse::(); + Ok(Meta::NameValue(MetaNameValue { + name: ident, + value: input.parse()?, + })) + } else { + Ok(Meta::Word(ident)) + } } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index cfd9bdde..e4862249 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -5,7 +5,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ - attribute::{Meta, MetaList, MetaNameValue}, + attribute::{Meta, MetaNameValue}, strip_serde_attrs, }; @@ -133,39 +133,37 @@ impl From> for Request { let mut header = None; field.attrs.retain(|attr| { - let meta_list = match MetaList::from_attribute(attr) { - Some(list) => list, + let meta = match Meta::from_attribute(attr) { + Some(m) => m, None => return true, }; - for meta in meta_list { - match meta { - Meta::Word(ident) => { - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); + match meta { + Meta::Word(ident) => { + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - field_kind = Some(match &ident.to_string()[..] { - "body" => RequestFieldKind::NewtypeBody, - "path" => RequestFieldKind::Path, - "query" => RequestFieldKind::Query, - _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), - }); - } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on requests must be: header" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); + field_kind = Some(match &ident.to_string()[..] { + "body" => RequestFieldKind::NewtypeBody, + "path" => RequestFieldKind::Path, + "query" => RequestFieldKind::Query, + _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), + }); + } + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - header = Some(value); - field_kind = Some(RequestFieldKind::Header); - } + header = Some(value); + field_kind = Some(RequestFieldKind::Header); } } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 3daf613b..a5e3e632 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -5,7 +5,7 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ - attribute::{Meta, MetaList, MetaNameValue}, + attribute::{Meta, MetaNameValue}, strip_serde_attrs, }; @@ -105,38 +105,36 @@ impl From> for Response { let mut header = None; field.attrs.retain(|attr| { - let meta_list = match MetaList::from_attribute(attr) { - Some(list) => list, + let meta = match Meta::from_attribute(attr) { + Some(m) => m, None => return true, }; - for meta in meta_list { - match meta { - Meta::Word(ident) => { - assert!( - ident == "body", - "ruma_api! single-word attribute on responses must be: body" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); + match meta { + Meta::Word(ident) => { + assert!( + ident == "body", + "ruma_api! single-word attribute on responses must be: body" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - field_kind = Some(ResponseFieldKind::NewtypeBody); - } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on requests must be: header" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); + field_kind = Some(ResponseFieldKind::NewtypeBody); + } + Meta::NameValue(MetaNameValue { name, value }) => { + assert!( + name == "header", + "ruma_api! name/value pair attribute on requests must be: header" + ); + assert!( + field_kind.is_none(), + "ruma_api! field kind can only be set once per field" + ); - header = Some(value); - field_kind = Some(ResponseFieldKind::Header); - } + header = Some(value); + field_kind = Some(ResponseFieldKind::Header); } } From 02e9ff2ae69cb1ec211be356a38f27ac84dba5f9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 21:48:43 +0100 Subject: [PATCH 187/295] Update macro crate's attributes --- ruma-api-macros/src/lib.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 6249f4f4..b16e11dc 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -7,26 +7,6 @@ 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 )] #![allow(clippy::cognitive_complexity)] // Since we support Rust 1.34.2, we can't apply this suggestion yet From 69ee18e0a92c883bf9dd1221e5a7160e52b7f838 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 14:05:23 +0100 Subject: [PATCH 188/295] Simplify ruma_api_macros::api::strip_serde_attrs --- ruma-api-macros/src/api/mod.rs | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 8a92c012..19afe859 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -5,7 +5,7 @@ use quote::{quote, ToTokens}; use syn::{ braced, parse::{Parse, ParseStream, Result}, - Field, FieldValue, Ident, Meta, Token, + Field, FieldValue, Ident, Token, }; mod attribute; @@ -18,25 +18,9 @@ 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(); - - field.attrs = field + field .attrs - .into_iter() - .filter(|attr| { - let meta = attr - .parse_meta() - .expect("ruma_api! could not parse field attributes"); - - match meta { - Meta::List(meta_list) => { - let segments = &meta_list.path.segments; - segments.len() != 1 || segments[0].ident != "serde" - } - _ => true, - } - }) - .collect(); - + .retain(|attr| attr.path.segments.len() != 1 || attr.path.segments[0].ident != "serde"); field } From 2b9742b99e380483b154ce0bec307cc3abfd9d29 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 14:06:02 +0100 Subject: [PATCH 189/295] Slightly simplify ruma_api! expansion code --- ruma-api-macros/src/api/request.rs | 73 ++++++++++++++--------------- ruma-api-macros/src/api/response.rs | 36 ++++++-------- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index e4862249..6e637200 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -194,14 +194,10 @@ impl ToTokens for Request { let request_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self.fields.iter().map(|request_field| { - let field = request_field.field(); - let span = field.span(); - - let stripped_field = strip_serde_attrs(field); - - quote_spanned!(span=> #stripped_field) - }); + let fields = self + .fields + .iter() + .map(|request_field| strip_serde_attrs(request_field.field())); quote! { { @@ -221,16 +217,7 @@ 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 { - RequestField::Body(ref field) => { - let span = field.span(); - Some(quote_spanned!(span=> #field)) - } - _ => None, - }); + let fields = self.fields.iter().filter_map(RequestField::as_body_field); quote! { /// Data in the request body. @@ -244,17 +231,7 @@ 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 { - RequestField::Path(ref field) => { - let span = field.span(); - - Some(quote_spanned!(span=> #field)) - } - _ => None, - }); + let fields = self.fields.iter().filter_map(RequestField::as_path_field); quote! { /// Data in the request path. @@ -272,16 +249,7 @@ 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 { - RequestField::Query(ref field) => { - let span = field.span(); - Some(quote_spanned!(span=> #field)) - } - _ => None, - }); + let fields = self.fields.iter().filter_map(RequestField::as_query_field); quote! { /// Data in the request's query string. @@ -374,6 +342,33 @@ impl RequestField { self.kind() == RequestFieldKind::Query } + /// Return the contained field if this response field is a body kind. + fn as_body_field(&self) -> Option<&Field> { + if let RequestField::Body(field) = self { + Some(field) + } else { + None + } + } + + /// Return the contained field if this response field is a path kind. + fn as_path_field(&self) -> Option<&Field> { + if let RequestField::Path(field) = self { + Some(field) + } else { + None + } + } + + /// Return the contained field if this response field is a query kind. + fn as_query_field(&self) -> Option<&Field> { + if let RequestField::Query(field) = self { + Some(field) + } else { + None + } + } + /// Gets the inner `Field` value. fn field(&self) -> &Field { match *self { diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index a5e3e632..e785a006 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -172,14 +172,10 @@ impl ToTokens for Response { let response_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self.fields.iter().map(|response_field| { - let field = response_field.field(); - let span = field.span(); - - let stripped_field = strip_serde_attrs(field); - - quote_spanned!(span=> #stripped_field) - }); + let fields = self + .fields + .iter() + .map(|response_field| strip_serde_attrs(response_field.field())); quote! { { @@ -199,16 +195,7 @@ 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 { - ResponseField::Body(ref field) => { - let span = field.span(); - Some(quote_spanned!(span=> #field)) - } - _ => None, - }); + let fields = self.fields.iter().filter_map(ResponseField::as_body_field); quote! { /// Data in the response body. @@ -253,10 +240,7 @@ impl ResponseField { /// Whether or not this response field is a body kind. fn is_body(&self) -> bool { - match *self { - ResponseField::Body(_) => true, - _ => false, - } + self.as_body_field().is_some() } /// Whether or not this response field is a header kind. @@ -267,6 +251,14 @@ impl ResponseField { } } + /// Return the contained field if this response field is a body kind. + fn as_body_field(&self) -> Option<&Field> { + match self { + ResponseField::Body(field) => Some(field), + _ => None, + } + } + /// Whether or not this response field is a newtype body kind. fn is_newtype_body(&self) -> bool { match *self { From 18ed83ef764153d2c1e3f43700e5e760788eb8f5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 15:21:18 +0100 Subject: [PATCH 190/295] Fix a few bugs in ruma_api_macros --- ruma-api-macros/src/api/request.rs | 20 +++++++++++++------- ruma-api-macros/src/api/response.rs | 22 ++++++++++++++-------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 6e637200..637ac0f7 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -173,11 +173,17 @@ impl From> for Request { RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header) }).collect(); - if fields.len() > 1 { + let num_body_fields = fields.iter().filter(|f| f.is_body()).count(); + let num_newtype_body_fields = fields.iter().filter(|f| f.is_newtype_body()).count(); + assert!( + num_newtype_body_fields <= 1, + "ruma_api! request can only have one newtype body field" + ); + if num_newtype_body_fields == 1 { assert!( - !fields.iter().any(|field| field.is_newtype_body()), - "ruma_api! newtype body has to be the only response field" - ) + num_body_fields == 0, + "ruma_api! request can't have both regular body fields and a newtype body field" + ); } Self { fields } @@ -342,7 +348,7 @@ impl RequestField { self.kind() == RequestFieldKind::Query } - /// Return the contained field if this response field is a body kind. + /// Return the contained field if this request field is a body kind. fn as_body_field(&self) -> Option<&Field> { if let RequestField::Body(field) = self { Some(field) @@ -351,7 +357,7 @@ impl RequestField { } } - /// Return the contained field if this response field is a path kind. + /// Return the contained field if this request field is a path kind. fn as_path_field(&self) -> Option<&Field> { if let RequestField::Path(field) = self { Some(field) @@ -360,7 +366,7 @@ impl RequestField { } } - /// Return the contained field if this response field is a query kind. + /// Return the contained field if this request field is a query kind. fn as_query_field(&self) -> Option<&Field> { if let RequestField::Query(field) = self { Some(field) diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index e785a006..e9e46708 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -9,7 +9,7 @@ use crate::api::{ strip_serde_attrs, }; -/// The result of processing the `request` section of the macro. +/// The result of processing the `response` section of the macro. pub struct Response { /// The fields of the response. fields: Vec, @@ -31,7 +31,7 @@ impl Response { self.fields.iter().any(|field| field.is_header()) } - /// Produces code for a request struct initializer. + /// Produces code for a response struct initializer. pub fn init_fields(&self) -> TokenStream { let fields = self .fields @@ -81,7 +81,7 @@ impl Response { } } - /// Gets the newtype body field, if this request has one. + /// Gets the newtype body field, if this response has one. pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { @@ -126,7 +126,7 @@ impl From> for Response { Meta::NameValue(MetaNameValue { name, value }) => { assert!( name == "header", - "ruma_api! name/value pair attribute on requests must be: header" + "ruma_api! name/value pair attribute on responses must be: header" ); assert!( field_kind.is_none(), @@ -151,11 +151,17 @@ impl From> for Response { }) .collect(); - if fields.len() > 1 { + let num_body_fields = fields.iter().filter(|f| f.is_body()).count(); + let num_newtype_body_fields = fields.iter().filter(|f| f.is_newtype_body()).count(); + assert!( + num_newtype_body_fields <= 1, + "ruma_api! response can only have one newtype body field" + ); + if num_newtype_body_fields == 1 { assert!( - !fields.iter().any(|field| field.is_newtype_body()), - "ruma_api! newtype body has to be the only response field" - ) + num_body_fields == 0, + "ruma_api! response can't have both regular body fields and a newtype body field" + ); } Self { fields } From 772c378fe2ee0c6c6625ef9cd31844ce460d100c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 15:40:26 +0100 Subject: [PATCH 191/295] ruma_api_macros: Use find_map in Response::newtype_body_field --- ruma-api-macros/src/api/response.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index e9e46708..cd984862 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -83,16 +83,9 @@ impl Response { /// Gets the newtype body field, if this response has one. 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 + self.fields + .iter() + .find_map(ResponseField::as_newtype_body_field) } } @@ -257,6 +250,11 @@ impl ResponseField { } } + /// Whether or not this response field is a newtype body kind. + fn is_newtype_body(&self) -> bool { + self.as_newtype_body_field().is_some() + } + /// Return the contained field if this response field is a body kind. fn as_body_field(&self) -> Option<&Field> { match self { @@ -265,11 +263,11 @@ impl ResponseField { } } - /// Whether or not this response field is a newtype body kind. - fn is_newtype_body(&self) -> bool { - match *self { - ResponseField::NewtypeBody(..) => true, - _ => false, + /// Return the contained field if this response field is a newtype body kind. + fn as_newtype_body_field(&self) -> Option<&Field> { + match self { + ResponseField::NewtypeBody(field) => Some(field), + _ => None, } } } From d0d1d97ee49c2a42998d7aa5bb0858c77519c83d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 15:55:54 +0100 Subject: [PATCH 192/295] ruma_api_macros: Simplify RequestField::as_* methods --- ruma-api-macros/src/api/request.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 637ac0f7..457c799d 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -350,29 +350,17 @@ impl RequestField { /// Return the contained field if this request field is a body kind. fn as_body_field(&self) -> Option<&Field> { - if let RequestField::Body(field) = self { - Some(field) - } else { - None - } + self.field_of_kind(RequestFieldKind::Body) } /// Return the contained field if this request field is a path kind. fn as_path_field(&self) -> Option<&Field> { - if let RequestField::Path(field) = self { - Some(field) - } else { - None - } + self.field_of_kind(RequestFieldKind::Path) } /// Return the contained field if this request field is a query kind. fn as_query_field(&self) -> Option<&Field> { - if let RequestField::Query(field) = self { - Some(field) - } else { - None - } + self.field_of_kind(RequestFieldKind::Query) } /// Gets the inner `Field` value. From 2261067251a705bbb77edae1f5542031d7598c59 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 15:58:48 +0100 Subject: [PATCH 193/295] ruma_api_macros: Use find_map in Request::newtype_body_field --- ruma-api-macros/src/api/request.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 457c799d..a19b83b9 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -72,16 +72,9 @@ impl Request { /// Returns the body field. 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 + self.fields + .iter() + .find_map(RequestField::as_newtype_body_field) } /// Produces code for a struct initializer for body fields on a variable named `request`. @@ -353,6 +346,11 @@ impl RequestField { self.field_of_kind(RequestFieldKind::Body) } + /// Return the contained field if this request field is a body kind. + fn as_newtype_body_field(&self) -> Option<&Field> { + self.field_of_kind(RequestFieldKind::NewtypeBody) + } + /// Return the contained field if this request field is a path kind. fn as_path_field(&self) -> Option<&Field> { self.field_of_kind(RequestFieldKind::Path) From 3e8938fc1a45d898a9e55892a5f4fe0bd8b89aa8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 16:00:21 +0100 Subject: [PATCH 194/295] ruma-api-macros: Remove ref from match bindings since it is no longer necessary with our MSRV --- ruma-api-macros/src/api/request.rs | 14 +++++++------- ruma-api-macros/src/api/response.rs | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index a19b83b9..9bbbcf29 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -307,7 +307,7 @@ impl RequestField { /// Gets the kind of the request field. fn kind(&self) -> RequestFieldKind { - match *self { + match self { RequestField::Body(..) => RequestFieldKind::Body, RequestField::Header(..) => RequestFieldKind::Header, RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, @@ -363,12 +363,12 @@ impl RequestField { /// Gets the inner `Field` value. fn field(&self) -> &Field { - match *self { - RequestField::Body(ref field) - | RequestField::Header(ref field, _) - | RequestField::NewtypeBody(ref field) - | RequestField::Path(ref field) - | RequestField::Query(ref field) => field, + match self { + RequestField::Body(field) + | RequestField::Header(field, _) + | RequestField::NewtypeBody(field) + | RequestField::Path(field) + | RequestField::Query(field) => field, } } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index cd984862..f682d13e 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -36,8 +36,8 @@ impl Response { let fields = self .fields .iter() - .map(|response_field| match *response_field { - ResponseField::Body(ref field) => { + .map(|response_field| match response_field { + ResponseField::Body(field) => { let field_name = field .ident .clone() @@ -48,7 +48,7 @@ impl Response { #field_name: response_body.#field_name } } - ResponseField::Header(ref field, ref header_name) => { + ResponseField::Header(field, header_name) => { let field_name = field .ident .clone() @@ -63,7 +63,7 @@ impl Response { .to_owned() } } - ResponseField::NewtypeBody(ref field) => { + ResponseField::NewtypeBody(field) => { let field_name = field .ident .clone() @@ -230,10 +230,10 @@ pub enum ResponseField { impl ResponseField { /// Gets the inner `Field` value. fn field(&self) -> &Field { - match *self { - ResponseField::Body(ref field) - | ResponseField::Header(ref field, _) - | ResponseField::NewtypeBody(ref field) => field, + match self { + ResponseField::Body(field) + | ResponseField::Header(field, _) + | ResponseField::NewtypeBody(field) => field, } } @@ -244,7 +244,7 @@ impl ResponseField { /// Whether or not this response field is a header kind. fn is_header(&self) -> bool { - match *self { + match self { ResponseField::Header(..) => true, _ => false, } From 8fe74552b1ecfacd901268aea62dbf81b4e12f90 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 16 Nov 2019 16:46:43 +0100 Subject: [PATCH 195/295] Remove unnecessary cloning --- ruma-api-macros/src/api/mod.rs | 2 +- ruma-api-macros/src/api/request.rs | 3 +-- ruma-api-macros/src/api/response.rs | 9 ++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 19afe859..74bd76f3 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -81,7 +81,7 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); let path_segments = path_str[1..].split('/'); - let path_segment_push = path_segments.clone().map(|segment| { + let path_segment_push = path_segments.map(|segment| { let arg = if segment.starts_with(':') { let path_var = &segment[1..]; let path_var_ident = Ident::new(path_var, Span::call_site()); diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 9bbbcf29..f47cd85d 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -205,8 +205,7 @@ impl ToTokens for Request { } }; - let request_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { - let field = newtype_body_field.clone(); + let request_body_struct = if let Some(field) = self.newtype_body_field() { let ty = &field.ty; let span = field.span(); diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index f682d13e..aa45c001 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -40,7 +40,7 @@ impl Response { ResponseField::Body(field) => { let field_name = field .ident - .clone() + .as_ref() .expect("expected field to have an identifier"); let span = field.span(); @@ -51,7 +51,7 @@ impl Response { ResponseField::Header(field, header_name) => { let field_name = field .ident - .clone() + .as_ref() .expect("expected field to have an identifier"); let span = field.span(); @@ -66,7 +66,7 @@ impl Response { ResponseField::NewtypeBody(field) => { let field_name = field .ident - .clone() + .as_ref() .expect("expected field to have an identifier"); let span = field.span(); @@ -183,8 +183,7 @@ impl ToTokens for Response { } }; - let response_body_struct = if let Some(newtype_body_field) = self.newtype_body_field() { - let field = newtype_body_field.clone(); + let response_body_struct = if let Some(field) = self.newtype_body_field() { let ty = &field.ty; let span = field.span(); From 41c1a224c216462e65421adb89a5a24dcf1adf77 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 17 Nov 2019 23:53:19 +0100 Subject: [PATCH 196/295] ruma-api-macros: Improve error handling for invalid metadata --- ruma-api-macros/src/api/metadata.rs | 150 ++++++++++++++-------------- ruma-api-macros/src/api/mod.rs | 52 +++++++--- ruma-api-macros/src/api/request.rs | 10 +- ruma-api-macros/src/api/response.rs | 10 +- ruma-api-macros/src/lib.rs | 10 +- 5 files changed, 132 insertions(+), 100 deletions(-) diff --git a/ruma-api-macros/src/api/metadata.rs b/ruma-api-macros/src/api/metadata.rs index 3d7b6a29..3f97216d 100644 --- a/ruma-api-macros/src/api/metadata.rs +++ b/ruma-api-macros/src/api/metadata.rs @@ -1,7 +1,10 @@ //! Details of the `metadata` section of the procedural macro. -use proc_macro2::Ident; -use syn::{Expr, ExprLit, FieldValue, Lit, LitBool, LitStr, Member}; +use std::convert::TryFrom; + +use syn::{Expr, ExprLit, ExprPath, Ident, Lit, LitBool, LitStr, Member}; + +use crate::api::RawMetadata; /// The result of processing the `metadata` section of the macro. pub struct Metadata { @@ -19,8 +22,10 @@ pub struct Metadata { pub requires_authentication: LitBool, } -impl From> for Metadata { - fn from(field_values: Vec) -> Self { +impl TryFrom for Metadata { + type Error = syn::Error; + + fn try_from(raw: RawMetadata) -> syn::Result { let mut description = None; let mut method = None; let mut name = None; @@ -28,84 +33,81 @@ impl From> for Metadata { let mut rate_limited = None; let mut requires_authentication = None; - for field_value in field_values { - let identifier = match field_value.member { + for field_value in raw.field_values { + let identifier = match field_value.member.clone() { Member::Named(identifier) => identifier, _ => panic!("expected Member::Named"), }; + let expr = field_value.expr.clone(); match &identifier.to_string()[..] { - "description" => { - let literal = match field_value.expr { - Expr::Lit(ExprLit { - lit: Lit::Str(s), .. - }) => s, - _ => panic!("expected string literal"), - }; - description = Some(literal); - } - "method" => { - let expr_path = match field_value.expr { - Expr::Path(expr_path) => expr_path, - _ => panic!("expected Expr::Path"), - }; - let path = expr_path.path; - 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 literal = match field_value.expr { - Expr::Lit(ExprLit { - lit: Lit::Str(s), .. - }) => s, - _ => panic!("expected string literal"), - }; - name = Some(literal); - } - "path" => { - let literal = match field_value.expr { - Expr::Lit(ExprLit { - lit: Lit::Str(s), .. - }) => s, - _ => panic!("expected string literal"), - }; - path = Some(literal); - } - "rate_limited" => { - let literal = match field_value.expr { - Expr::Lit(ExprLit { - lit: Lit::Bool(b), .. - }) => b, - _ => panic!("expected Expr::Lit"), - }; - rate_limited = Some(literal) - } - "requires_authentication" => { - let literal = match field_value.expr { - Expr::Lit(ExprLit { - lit: Lit::Bool(b), .. - }) => b, - _ => panic!("expected Expr::Lit"), - }; - requires_authentication = Some(literal) - } - _ => panic!("ruma_api! metadata included unexpected field"), + "description" => match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(literal), + .. + }) => { + description = Some(literal); + } + _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), + }, + "method" => match expr { + Expr::Path(ExprPath { path, .. }) if path.segments.len() == 1 => { + method = Some(path.segments[0].ident.clone()); + } + _ => return Err(syn::Error::new_spanned(expr, "expected an identifier")), + }, + "name" => match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(literal), + .. + }) => { + name = Some(literal); + } + _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), + }, + "path" => match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(literal), + .. + }) => { + path = Some(literal); + } + _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), + }, + "rate_limited" => match expr { + Expr::Lit(ExprLit { + lit: Lit::Bool(literal), + .. + }) => { + rate_limited = Some(literal); + } + _ => return Err(syn::Error::new_spanned(expr, "expected a bool literal")), + }, + "requires_authentication" => match expr { + Expr::Lit(ExprLit { + lit: Lit::Bool(literal), + .. + }) => { + requires_authentication = Some(literal); + } + _ => return Err(syn::Error::new_spanned(expr, "expected a bool literal")), + }, + _ => return Err(syn::Error::new_spanned(field_value, "unexpected field")), } } - 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`"), - path: path.expect("ruma_api! `metadata` is missing `path`"), - rate_limited: rate_limited.expect("ruma_api! `metadata` is missing `rate_limited`"), + let metadata_kw = raw.metadata_kw; + let missing_field = + |name| syn::Error::new_spanned(metadata_kw, format!("missing field `{}`", name)); + + Ok(Self { + description: description.ok_or_else(|| missing_field("description"))?, + method: method.ok_or_else(|| missing_field("method"))?, + name: name.ok_or_else(|| missing_field("name"))?, + path: path.ok_or_else(|| missing_field("path"))?, + rate_limited: rate_limited.ok_or_else(|| missing_field("rate_limited"))?, requires_authentication: requires_authentication - .expect("ruma_api! `metadata` is missing `requires_authentication`"), - } + .ok_or_else(|| missing_field("requires_authentication"))?, + }) } } diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 74bd76f3..16b42848 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -1,10 +1,12 @@ //! Details of the `ruma_api` procedural macro. +use std::convert::{TryFrom, TryInto as _}; + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ braced, - parse::{Parse, ParseStream, Result}, + parse::{Parse, ParseStream}, Field, FieldValue, Ident, Token, }; @@ -34,12 +36,14 @@ pub struct Api { response: Response, } -impl From for Api { - fn from(raw_api: RawApi) -> Self { +impl TryFrom for Api { + type Error = syn::Error; + + fn try_from(raw_api: RawApi) -> syn::Result { let res = Self { - metadata: raw_api.metadata.into(), - request: raw_api.request.into(), - response: raw_api.response.into(), + metadata: raw_api.metadata.try_into()?, + request: raw_api.request.try_into()?, + response: raw_api.response.try_into()?, }; assert!( @@ -48,7 +52,7 @@ impl From for Api { "GET endpoints can't have body fields" ); - res + Ok(res) } } @@ -303,7 +307,7 @@ mod kw { /// 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, + pub metadata: RawMetadata, /// The `request` section of the macro. pub request: Vec, /// The `response` section of the macro. @@ -311,10 +315,8 @@ pub struct RawApi { } impl Parse for RawApi { - fn parse(input: ParseStream<'_>) -> Result { - input.parse::()?; - let metadata; - braced!(metadata in input); + fn parse(input: ParseStream<'_>) -> syn::Result { + let metadata = input.parse::()?; input.parse::()?; let request; @@ -325,10 +327,7 @@ impl Parse for RawApi { braced!(response in input); Ok(Self { - metadata: metadata - .parse_terminated::(FieldValue::parse)? - .into_iter() - .collect(), + metadata, request: request .parse_terminated::(Field::parse_named)? .into_iter() @@ -340,3 +339,24 @@ impl Parse for RawApi { }) } } + +pub struct RawMetadata { + pub metadata_kw: kw::metadata, + pub field_values: Vec, +} + +impl Parse for RawMetadata { + fn parse(input: ParseStream<'_>) -> syn::Result { + let metadata_kw = input.parse::()?; + let field_values; + braced!(field_values in input); + + Ok(Self { + metadata_kw, + field_values: field_values + .parse_terminated::(FieldValue::parse)? + .into_iter() + .collect(), + }) + } +} diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index f47cd85d..622f01b1 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -1,5 +1,7 @@ //! Details of the `request` section of the procedural macro. +use std::convert::TryFrom; + use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; @@ -119,8 +121,10 @@ impl Request { } } -impl From> for Request { - fn from(fields: Vec) -> Self { +impl TryFrom> for Request { + type Error = syn::Error; + + fn try_from(fields: Vec) -> syn::Result { let fields: Vec<_> = fields.into_iter().map(|mut field| { let mut field_kind = None; let mut header = None; @@ -179,7 +183,7 @@ impl From> for Request { ); } - Self { fields } + Ok(Self { fields }) } } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index aa45c001..86c76c65 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -1,5 +1,7 @@ //! Details of the `response` section of the procedural macro. +use std::convert::TryFrom; + use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; use syn::{spanned::Spanned, Field, Ident}; @@ -89,8 +91,10 @@ impl Response { } } -impl From> for Response { - fn from(fields: Vec) -> Self { +impl TryFrom> for Response { + type Error = syn::Error; + + fn try_from(fields: Vec) -> syn::Result { let fields: Vec<_> = fields .into_iter() .map(|mut field| { @@ -157,7 +161,7 @@ impl From> for Response { ); } - Self { fields } + Ok(Self { fields }) } } diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index b16e11dc..9ac63182 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -15,6 +15,8 @@ extern crate proc_macro; +use std::convert::TryFrom as _; + use proc_macro::TokenStream; use quote::ToTokens; @@ -191,8 +193,8 @@ mod api; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { let raw_api = syn::parse_macro_input!(input as RawApi); - - let api = Api::from(raw_api); - - api.into_token_stream().into() + match Api::try_from(raw_api) { + Ok(api) => api.into_token_stream().into(), + Err(err) => err.to_compile_error().into(), + } } From 6ea5b9b7a1694c1105bd2a66811eef4ee0e4df23 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 18 Nov 2019 00:06:46 +0100 Subject: [PATCH 197/295] Fix 1.34.2 incompatibility from last commit --- ruma-api-macros/src/api/metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/src/api/metadata.rs b/ruma-api-macros/src/api/metadata.rs index 3f97216d..20c32ec1 100644 --- a/ruma-api-macros/src/api/metadata.rs +++ b/ruma-api-macros/src/api/metadata.rs @@ -51,7 +51,7 @@ impl TryFrom for Metadata { _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), }, "method" => match expr { - Expr::Path(ExprPath { path, .. }) if path.segments.len() == 1 => { + Expr::Path(ExprPath { ref path, .. }) if path.segments.len() == 1 => { method = Some(path.segments[0].ident.clone()); } _ => return Err(syn::Error::new_spanned(expr, "expected an identifier")), From eab2374253ce9e451b87f4d6151040f5a1e26615 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 18 Nov 2019 02:22:00 +0100 Subject: [PATCH 198/295] ruma-api-macros: Slight simplification of api/attribute.rs --- ruma-api-macros/src/api/attribute.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index df610dd9..b0bbd858 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -33,16 +33,10 @@ impl Meta { syn::Path { leading_colon: None, segments, - } => { - if segments.len() == 1 && segments[0].ident == "ruma_api" { - Some( - attr.parse_args() - .expect("ruma_api! could not parse request field attributes"), - ) - } else { - None - } - } + } if segments.len() == 1 && segments[0].ident == "ruma_api" => Some( + attr.parse_args() + .expect("ruma_api! could not parse request field attributes"), + ), _ => None, } } From c277c0d25754152e8920e27e762ecad8f45088a5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 18 Nov 2019 22:18:38 +0100 Subject: [PATCH 199/295] ruma-api-macros: Add more error spans for request block --- ruma-api-macros/src/api/mod.rs | 33 +++++-- ruma-api-macros/src/api/request.rs | 135 +++++++++++++++++------------ 2 files changed, 102 insertions(+), 66 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 16b42848..39b663cf 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -309,7 +309,7 @@ pub struct RawApi { /// The `metadata` section of the macro. pub metadata: RawMetadata, /// The `request` section of the macro. - pub request: Vec, + pub request: RawRequest, /// The `response` section of the macro. pub response: Vec, } @@ -317,10 +317,7 @@ pub struct RawApi { impl Parse for RawApi { fn parse(input: ParseStream<'_>) -> syn::Result { let metadata = input.parse::()?; - - input.parse::()?; - let request; - braced!(request in input); + let request = input.parse::()?; input.parse::()?; let response; @@ -328,10 +325,7 @@ impl Parse for RawApi { Ok(Self { metadata, - request: request - .parse_terminated::(Field::parse_named)? - .into_iter() - .collect(), + request, response: response .parse_terminated::(Field::parse_named)? .into_iter() @@ -360,3 +354,24 @@ impl Parse for RawMetadata { }) } } + +pub struct RawRequest { + pub request_kw: kw::request, + pub fields: Vec, +} + +impl Parse for RawRequest { + fn parse(input: ParseStream<'_>) -> syn::Result { + let request_kw = input.parse::()?; + let fields; + braced!(fields in input); + + Ok(Self { + request_kw, + fields: fields + .parse_terminated::(Field::parse_named)? + .into_iter() + .collect(), + }) + } +} diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 622f01b1..22eb3eef 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -1,6 +1,6 @@ //! Details of the `request` section of the procedural macro. -use std::convert::TryFrom; +use std::{convert::TryFrom, mem}; use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; @@ -8,7 +8,7 @@ use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ attribute::{Meta, MetaNameValue}, - strip_serde_attrs, + strip_serde_attrs, RawRequest, }; /// The result of processing the `request` section of the macro. @@ -121,66 +121,92 @@ impl Request { } } -impl TryFrom> for Request { +impl TryFrom for Request { type Error = syn::Error; - fn try_from(fields: Vec) -> syn::Result { - let fields: Vec<_> = fields.into_iter().map(|mut field| { - let mut field_kind = None; - let mut header = None; + fn try_from(raw: RawRequest) -> syn::Result { + let mut newtype_body_field = None; - field.attrs.retain(|attr| { - let meta = match Meta::from_attribute(attr) { - Some(m) => m, - None => return true, - }; + let fields = raw + .fields + .into_iter() + .map(|mut field| { + let mut field_kind = None; + let mut header = None; - match meta { - Meta::Word(ident) => { - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); + for attr in mem::replace(&mut field.attrs, Vec::new()) { + let meta = match Meta::from_attribute(&attr) { + Some(m) => m, + None => { + field.attrs.push(attr); + continue; + } + }; - field_kind = Some(match &ident.to_string()[..] { - "body" => RequestFieldKind::NewtypeBody, - "path" => RequestFieldKind::Path, - "query" => RequestFieldKind::Query, - _ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), - }); + if field_kind.is_some() { + return Err(syn::Error::new_spanned( + attr, + "There can only be one field kind attribute", + )); } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on requests must be: header" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); - header = Some(value); - field_kind = Some(RequestFieldKind::Header); - } + field_kind = Some(match meta { + Meta::Word(ident) => { + match &ident.to_string()[..] { + "body" => { + if let Some(f) = &newtype_body_field { + let mut error = syn::Error::new_spanned( + field, + "There can only be one newtype body field", + ); + error.combine(syn::Error::new_spanned( + f, + "Previous newtype body field", + )); + return Err(error); + } + + newtype_body_field = Some(field.clone()); + RequestFieldKind::NewtypeBody + } + "path" => RequestFieldKind::Path, + "query" => RequestFieldKind::Query, + _ => { + return Err(syn::Error::new_spanned( + ident, + "Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`", + )); + } + } + } + Meta::NameValue(MetaNameValue { name, value }) => { + if name != "header" { + return Err(syn::Error::new_spanned( + name, + "Invalid #[ruma_api] argument with value, expected `header`" + )); + } + + header = Some(value); + RequestFieldKind::Header + } + }); } - false - }); + Ok(RequestField::new( + field_kind.unwrap_or(RequestFieldKind::Body), + field, + header, + )) + }) + .collect::>>()?; - RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header) - }).collect(); - - let num_body_fields = fields.iter().filter(|f| f.is_body()).count(); - let num_newtype_body_fields = fields.iter().filter(|f| f.is_newtype_body()).count(); - assert!( - num_newtype_body_fields <= 1, - "ruma_api! request can only have one newtype body field" - ); - if num_newtype_body_fields == 1 { - assert!( - num_body_fields == 0, - "ruma_api! request can't have both regular body fields and a newtype body field" - ); + if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) { + return Err(syn::Error::new_spanned( + // TODO: raw, + raw.request_kw, + "Can't have both a newtype body field and regular body fields", + )); } Ok(Self { fields }) @@ -329,11 +355,6 @@ impl RequestField { self.kind() == RequestFieldKind::Header } - /// Whether or not this request field is a newtype body kind. - fn is_newtype_body(&self) -> bool { - self.kind() == RequestFieldKind::NewtypeBody - } - /// Whether or not this request field is a path kind. fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path From 9b3eef4f8d7a5c9e85d2a1e3833bb5cd9ba23abb Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 18 Nov 2019 22:36:37 +0100 Subject: [PATCH 200/295] ruma-api-macros: Add more error spans for response block --- ruma-api-macros/src/api/mod.rs | 39 +++++---- ruma-api-macros/src/api/response.rs | 120 +++++++++++++++------------- 2 files changed, 90 insertions(+), 69 deletions(-) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 39b663cf..164543ae 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -311,25 +311,15 @@ pub struct RawApi { /// The `request` section of the macro. pub request: RawRequest, /// The `response` section of the macro. - pub response: Vec, + pub response: RawResponse, } impl Parse for RawApi { fn parse(input: ParseStream<'_>) -> syn::Result { - let metadata = input.parse::()?; - let request = input.parse::()?; - - input.parse::()?; - let response; - braced!(response in input); - Ok(Self { - metadata, - request, - response: response - .parse_terminated::(Field::parse_named)? - .into_iter() - .collect(), + metadata: input.parse()?, + request: input.parse()?, + response: input.parse()?, }) } } @@ -375,3 +365,24 @@ impl Parse for RawRequest { }) } } + +pub struct RawResponse { + pub response_kw: kw::response, + pub fields: Vec, +} + +impl Parse for RawResponse { + fn parse(input: ParseStream<'_>) -> syn::Result { + let response_kw = input.parse::()?; + let fields; + braced!(fields in input); + + Ok(Self { + response_kw, + fields: fields + .parse_terminated::(Field::parse_named)? + .into_iter() + .collect(), + }) + } +} diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 86c76c65..c50566ce 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -1,6 +1,6 @@ //! Details of the `response` section of the procedural macro. -use std::convert::TryFrom; +use std::{convert::TryFrom, mem}; use proc_macro2::TokenStream; use quote::{quote, quote_spanned, ToTokens}; @@ -8,7 +8,7 @@ use syn::{spanned::Spanned, Field, Ident}; use crate::api::{ attribute::{Meta, MetaNameValue}, - strip_serde_attrs, + strip_serde_attrs, RawResponse, }; /// The result of processing the `response` section of the macro. @@ -91,74 +91,89 @@ impl Response { } } -impl TryFrom> for Response { +impl TryFrom for Response { type Error = syn::Error; - fn try_from(fields: Vec) -> syn::Result { - let fields: Vec<_> = fields + fn try_from(raw: RawResponse) -> syn::Result { + let mut newtype_body_field = None; + + let fields = raw + .fields .into_iter() .map(|mut field| { let mut field_kind = None; let mut header = None; - field.attrs.retain(|attr| { - let meta = match Meta::from_attribute(attr) { + for attr in mem::replace(&mut field.attrs, Vec::new()) { + let meta = match Meta::from_attribute(&attr) { Some(m) => m, - None => return true, + None => { + field.attrs.push(attr); + continue; + } }; - match meta { - Meta::Word(ident) => { - assert!( - ident == "body", - "ruma_api! single-word attribute on responses must be: body" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); - - field_kind = Some(ResponseFieldKind::NewtypeBody); - } - Meta::NameValue(MetaNameValue { name, value }) => { - assert!( - name == "header", - "ruma_api! name/value pair attribute on responses must be: header" - ); - assert!( - field_kind.is_none(), - "ruma_api! field kind can only be set once per field" - ); - - header = Some(value); - field_kind = Some(ResponseFieldKind::Header); - } + if field_kind.is_some() { + return Err(syn::Error::new_spanned( + attr, + "There can only be one field kind attribute", + )); } - false - }); + field_kind = Some(match meta { + Meta::Word(ident) => { + if ident != "body" { + return Err(syn::Error::new_spanned( + ident, + "Invalid #[ruma_api] argument with value, expected `body`", + )); + } - match field_kind.unwrap_or(ResponseFieldKind::Body) { + if let Some(f) = &newtype_body_field { + let mut error = syn::Error::new_spanned( + field, + "There can only be one newtype body field", + ); + error.combine(syn::Error::new_spanned( + f, + "Previous newtype body field", + )); + return Err(error); + } + + newtype_body_field = Some(field.clone()); + ResponseFieldKind::NewtypeBody + } + Meta::NameValue(MetaNameValue { name, value }) => { + if name != "header" { + return Err(syn::Error::new_spanned( + name, + "Invalid #[ruma_api] argument with value, expected `header`", + )); + } + + header = Some(value); + ResponseFieldKind::Header + } + }); + } + + Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) { ResponseFieldKind::Body => ResponseField::Body(field), ResponseFieldKind::Header => { ResponseField::Header(field, header.expect("missing header name")) } ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), - } + }) }) - .collect(); + .collect::>>()?; - let num_body_fields = fields.iter().filter(|f| f.is_body()).count(); - let num_newtype_body_fields = fields.iter().filter(|f| f.is_newtype_body()).count(); - assert!( - num_newtype_body_fields <= 1, - "ruma_api! response can only have one newtype body field" - ); - if num_newtype_body_fields == 1 { - assert!( - num_body_fields == 0, - "ruma_api! response can't have both regular body fields and a newtype body field" - ); + if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) { + return Err(syn::Error::new_spanned( + // TODO: raw, + raw.response_kw, + "Can't have both a newtype body field and regular body fields", + )); } Ok(Self { fields }) @@ -253,11 +268,6 @@ impl ResponseField { } } - /// Whether or not this response field is a newtype body kind. - fn is_newtype_body(&self) -> bool { - self.as_newtype_body_field().is_some() - } - /// Return the contained field if this response field is a body kind. fn as_body_field(&self) -> Option<&Field> { match self { From 6b14591d3195224ba75941d90e9b2ec215703cfe Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 18 Nov 2019 22:52:14 +0100 Subject: [PATCH 201/295] ruma-api-macros: Rewrite a few leftover errors without spans --- ruma-api-macros/src/api/attribute.rs | 11 +++++----- ruma-api-macros/src/api/mod.rs | 31 ++++++++++++++++++++++------ ruma-api-macros/src/api/request.rs | 9 ++++++-- ruma-api-macros/src/api/response.rs | 2 +- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index b0bbd858..c2b771d7 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -28,16 +28,15 @@ impl Meta { /// # Panics /// /// Panics if the given attribute is a ruma_api attribute, but fails to parse. - pub fn from_attribute(attr: &syn::Attribute) -> Option { + pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { match &attr.path { syn::Path { leading_colon: None, segments, - } if segments.len() == 1 && segments[0].ident == "ruma_api" => Some( - attr.parse_args() - .expect("ruma_api! could not parse request field attributes"), - ), - _ => None, + } if segments.len() == 1 && segments[0].ident == "ruma_api" => { + attr.parse_args().map(Some) + } + _ => Ok(None), } } } diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 164543ae..5a4755bd 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -46,13 +46,32 @@ impl TryFrom for Api { response: raw_api.response.try_into()?, }; - assert!( - !(res.metadata.method == "GET" - && (res.request.has_body_fields() || res.request.newtype_body_field().is_some())), - "GET endpoints can't have body fields" - ); + let newtype_body_field = res.request.newtype_body_field(); + if res.metadata.method == "GET" + && (res.request.has_body_fields() || newtype_body_field.is_some()) + { + let mut combined_error: Option = None; + let mut add_error = |field| { + let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields"); + if let Some(combined_error_ref) = &mut combined_error { + combined_error_ref.combine(error); + } else { + combined_error = Some(error); + } + }; - Ok(res) + for field in res.request.body_fields() { + add_error(field); + } + + if let Some(field) = newtype_body_field { + add_error(field); + } + + Err(combined_error.unwrap()) + } else { + Ok(res) + } } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 22eb3eef..b8f95cd5 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -23,7 +23,7 @@ impl Request { let append_stmts = self.header_fields().map(|request_field| { let (field, header_name) = match request_field { RequestField::Header(field, header_name) => (field, header_name), - _ => panic!("expected request field to be header variant"), + _ => unreachable!("expected request field to be header variant"), }; let field_name = &field.ident; @@ -62,6 +62,11 @@ impl Request { self.fields.iter().any(|field| field.is_query()) } + /// Produces an iterator over all the body fields. + pub fn body_fields(&self) -> impl Iterator { + self.fields.iter().filter_map(|field| field.as_body_field()) + } + /// Produces an iterator over all the header fields. pub fn header_fields(&self) -> impl Iterator { self.fields.iter().filter(|field| field.is_header()) @@ -135,7 +140,7 @@ impl TryFrom for Request { let mut header = None; for attr in mem::replace(&mut field.attrs, Vec::new()) { - let meta = match Meta::from_attribute(&attr) { + let meta = match Meta::from_attribute(&attr)? { Some(m) => m, None => { field.attrs.push(attr); diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index c50566ce..05433d4d 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -105,7 +105,7 @@ impl TryFrom for Response { let mut header = None; for attr in mem::replace(&mut field.attrs, Vec::new()) { - let meta = match Meta::from_attribute(&attr) { + let meta = match Meta::from_attribute(&attr)? { Some(m) => m, None => { field.attrs.push(attr); From a969fcf625dabb227b22d45864e7b9acc1090a21 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 19 Nov 2019 10:59:05 +0100 Subject: [PATCH 202/295] Update dependencies, use one line per dep in toml files --- Cargo.toml | 22 ++++++---------------- ruma-api-macros/Cargo.toml | 9 +++------ 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53308567..83c4f322 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,23 +13,13 @@ version = "0.11.0" edition = "2018" [dependencies] -http = "0.1.18" -serde_json = "1.0.40" -serde_urlencoded = "0.6.1" +http = "0.1.19" +ruma-api-macros = { path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" - -[dependencies.ruma-api-macros] -path = "ruma-api-macros" -optional = true - -[dependencies.serde] -version = "1.0.99" -features = ["derive"] -optional = true - -[dependencies.url] -version = "2.1.0" -optional = true +serde = { version = "1.0.102", features = ["derive"], optional = true } +serde_json = "1.0.41" +serde_urlencoded = "0.6.1" +url = { version = "2.1.0", optional = true } [features] default = ["with-ruma-api-macros"] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index a379d8eb..c7c112f2 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -13,12 +13,9 @@ version = "0.8.0" edition = "2018" [dependencies] -quote = "1.0.1" -proc-macro2 = "1.0.1" - -[dependencies.syn] -version = "1.0.2" -features = ["full"] +proc-macro2 = "1.0.6" +quote = "1.0.2" +syn = { version = "1.0.8", features = ["full"] } [lib] proc-macro = true From 5a6557e7d706825f20168d2cf7c5d5f97620e79e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 19 Nov 2019 11:01:31 +0100 Subject: [PATCH 203/295] Configure rustfmt to produce denser code --- .rustfmt.toml | 3 ++ ruma-api-macros/src/api/attribute.rs | 12 ++--- ruma-api-macros/src/api/metadata.rs | 25 ++-------- ruma-api-macros/src/api/mod.rs | 11 +---- ruma-api-macros/src/api/request.rs | 16 ++---- ruma-api-macros/src/api/response.rs | 73 ++++++++++++---------------- src/lib.rs | 10 +--- 7 files changed, 51 insertions(+), 99 deletions(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..ad5d280d --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +edition = "2018" +merge_imports = true +use_small_heuristics = "Max" diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index c2b771d7..ffedffdd 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -30,10 +30,9 @@ impl Meta { /// Panics if the given attribute is a ruma_api attribute, but fails to parse. pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { match &attr.path { - syn::Path { - leading_colon: None, - segments, - } if segments.len() == 1 && segments[0].ident == "ruma_api" => { + syn::Path { leading_colon: None, segments } + if segments.len() == 1 && segments[0].ident == "ruma_api" => + { attr.parse_args().map(Some) } _ => Ok(None), @@ -47,10 +46,7 @@ impl Parse for Meta { if input.peek(Token![=]) { let _ = input.parse::(); - Ok(Meta::NameValue(MetaNameValue { - name: ident, - value: input.parse()?, - })) + Ok(Meta::NameValue(MetaNameValue { name: ident, value: input.parse()? })) } else { Ok(Meta::Word(ident)) } diff --git a/ruma-api-macros/src/api/metadata.rs b/ruma-api-macros/src/api/metadata.rs index 20c32ec1..c52c17f1 100644 --- a/ruma-api-macros/src/api/metadata.rs +++ b/ruma-api-macros/src/api/metadata.rs @@ -42,10 +42,7 @@ impl TryFrom for Metadata { match &identifier.to_string()[..] { "description" => match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(literal), - .. - }) => { + Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => { description = Some(literal); } _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), @@ -57,37 +54,25 @@ impl TryFrom for Metadata { _ => return Err(syn::Error::new_spanned(expr, "expected an identifier")), }, "name" => match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(literal), - .. - }) => { + Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => { name = Some(literal); } _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), }, "path" => match expr { - Expr::Lit(ExprLit { - lit: Lit::Str(literal), - .. - }) => { + Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => { path = Some(literal); } _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")), }, "rate_limited" => match expr { - Expr::Lit(ExprLit { - lit: Lit::Bool(literal), - .. - }) => { + Expr::Lit(ExprLit { lit: Lit::Bool(literal), .. }) => { rate_limited = Some(literal); } _ => return Err(syn::Error::new_spanned(expr, "expected a bool literal")), }, "requires_authentication" => match expr { - Expr::Lit(ExprLit { - lit: Lit::Bool(literal), - .. - }) => { + Expr::Lit(ExprLit { lit: Lit::Bool(literal), .. }) => { requires_authentication = Some(literal); } _ => return Err(syn::Error::new_spanned(expr, "expected a bool literal")), diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api/mod.rs index 5a4755bd..b9a82a92 100644 --- a/ruma-api-macros/src/api/mod.rs +++ b/ruma-api-macros/src/api/mod.rs @@ -162,10 +162,7 @@ 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); @@ -335,11 +332,7 @@ pub struct RawApi { impl Parse for RawApi { fn parse(input: ParseStream<'_>) -> syn::Result { - Ok(Self { - metadata: input.parse()?, - request: input.parse()?, - response: input.parse()?, - }) + Ok(Self { metadata: input.parse()?, request: input.parse()?, response: input.parse()? }) } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index b8f95cd5..ef297bed 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -79,9 +79,7 @@ impl Request { /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { - self.fields - .iter() - .find_map(RequestField::as_newtype_body_field) + self.fields.iter().find_map(RequestField::as_newtype_body_field) } /// Produces code for a struct initializer for body fields on a variable named `request`. @@ -108,10 +106,8 @@ impl Request { ) -> TokenStream { let fields = self.fields.iter().filter_map(|f| { f.field_of_kind(request_field_kind).map(|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=> @@ -228,10 +224,8 @@ impl ToTokens for Request { let request_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self - .fields - .iter() - .map(|request_field| strip_serde_attrs(request_field.field())); + let fields = + self.fields.iter().map(|request_field| strip_serde_attrs(request_field.field())); quote! { { diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 05433d4d..d1881297 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -35,48 +35,39 @@ impl Response { /// Produces code for a response struct initializer. pub fn init_fields(&self) -> TokenStream { - let fields = self - .fields - .iter() - .map(|response_field| match response_field { - ResponseField::Body(field) => { - let field_name = field - .ident - .as_ref() - .expect("expected field to have an identifier"); - let span = field.span(); + let fields = self.fields.iter().map(|response_field| match response_field { + ResponseField::Body(field) => { + let field_name = + field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); - quote_spanned! {span=> - #field_name: response_body.#field_name - } + quote_spanned! {span=> + #field_name: response_body.#field_name } - ResponseField::Header(field, header_name) => { - let field_name = field - .ident - .as_ref() - .expect("expected field to have an identifier"); - let span = field.span(); + } + ResponseField::Header(field, header_name) => { + let field_name = + field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); - quote_spanned! {span=> - #field_name: headers.remove(ruma_api::exports::http::header::#header_name) - .expect("response missing expected header") - .to_str() - .expect("failed to convert HeaderValue to str") - .to_owned() - } + quote_spanned! {span=> + #field_name: headers.remove(ruma_api::exports::http::header::#header_name) + .expect("response missing expected header") + .to_str() + .expect("failed to convert HeaderValue to str") + .to_owned() } - ResponseField::NewtypeBody(field) => { - let field_name = field - .ident - .as_ref() - .expect("expected field to have an identifier"); - let span = field.span(); + } + ResponseField::NewtypeBody(field) => { + let field_name = + field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); - quote_spanned! {span=> - #field_name: response_body - } + quote_spanned! {span=> + #field_name: response_body } - }); + } + }); quote! { #(#fields,)* @@ -85,9 +76,7 @@ impl Response { /// Gets the newtype body field, if this response has one. pub fn newtype_body_field(&self) -> Option<&Field> { - self.fields - .iter() - .find_map(ResponseField::as_newtype_body_field) + self.fields.iter().find_map(ResponseField::as_newtype_body_field) } } @@ -190,10 +179,8 @@ impl ToTokens for Response { let response_struct_body = if self.fields.is_empty() { quote!(;) } else { - let fields = self - .fields - .iter() - .map(|response_field| strip_serde_attrs(response_field.field())); + let fields = + self.fields.iter().map(|response_field| strip_serde_attrs(response_field.field())); quote! { { diff --git a/src/lib.rs b/src/lib.rs index 8491de61..66e7cfbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,7 @@ //! those requests. #![warn(rust_2018_idioms)] -#![deny( - missing_copy_implementations, - missing_debug_implementations, - missing_docs -)] +#![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)] // Since we support Rust 1.34.2, we can't apply this suggestion yet #![allow(clippy::use_self)] @@ -218,9 +214,7 @@ mod tests { .to_string() .replace(":room_alias", &request.room_alias.to_string()); - let request_body = RequestBody { - room_id: request.room_id, - }; + let request_body = RequestBody { room_id: request.room_id }; let http_request = http::Request::builder() .method(metadata.method) From 71372ae91084c6703b17aad6a0f0cd39b9bb06b4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 19 Nov 2019 11:25:41 +0100 Subject: [PATCH 204/295] ruma-api-macros: mv src/api/mod.rs src/api.rs --- ruma-api-macros/src/{api/mod.rs => api.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ruma-api-macros/src/{api/mod.rs => api.rs} (100%) diff --git a/ruma-api-macros/src/api/mod.rs b/ruma-api-macros/src/api.rs similarity index 100% rename from ruma-api-macros/src/api/mod.rs rename to ruma-api-macros/src/api.rs From f0e724d39156db4cf48ae82b75cf7664a7f6c900 Mon Sep 17 00:00:00 2001 From: Wim de With Date: Wed, 20 Nov 2019 14:31:38 +0100 Subject: [PATCH 205/295] Add query_map attribute to ruma_api --- ruma-api-macros/src/api.rs | 36 +++++++++++++++++- ruma-api-macros/src/api/request.rs | 61 ++++++++++++++++++++++++++++-- tests/ruma_api_macros.rs | 23 +++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index b9a82a92..9ddb4b12 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -135,7 +135,41 @@ impl ToTokens for Api { } }; - let set_request_query = if self.request.has_query_fields() { + let set_request_query = if let Some(field) = self.request.query_map_field() { + let field_name = field.ident.as_ref().expect("expected field to have identifier"); + let field_type = &field.ty; + + quote! { + // This function exists so that the compiler will throw an + // error when the type of the field with the query_map + // attribute doesn't implement IntoIterator + // + // This is necessary because the serde_urlencoded::to_string + // call will result in a runtime error when the type cannot be + // encoded as a list key-value pairs (?key1=value1&key2=value2) + // + // By asserting that it implements the iterator trait, we can + // ensure that it won't fail. + fn assert_trait_impl() + where + T: std::iter::IntoIterator, + {} + assert_trait_impl::<#field_type>(); + + let request_query = RequestQuery(request.#field_name); + let query_str = ruma_api::exports::serde_urlencoded::to_string( + request_query, + )?; + + let query_opt: Option<&str> = if query_str.is_empty() { + None + } else { + Some(&query_str) + }; + + url.set_query(query_opt); + } + } else if self.request.has_query_fields() { let request_query_init_fields = self.request.request_query_init_fields(); quote! { diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index ef297bed..3a741289 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -82,6 +82,11 @@ impl Request { self.fields.iter().find_map(RequestField::as_newtype_body_field) } + /// Returns the query map field. + pub fn query_map_field(&self) -> Option<&Field> { + self.fields.iter().find_map(RequestField::as_query_map_field) + } + /// 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)) @@ -127,6 +132,7 @@ impl TryFrom for Request { fn try_from(raw: RawRequest) -> syn::Result { let mut newtype_body_field = None; + let mut query_map_field = None; let fields = raw .fields @@ -172,10 +178,26 @@ impl TryFrom for Request { } "path" => RequestFieldKind::Path, "query" => RequestFieldKind::Query, + "query_map" => { + if let Some(f) = &query_map_field { + let mut error = syn::Error::new_spanned( + field, + "There can only be one query map field", + ); + error.combine(syn::Error::new_spanned( + f, + "Previous query map field", + )); + return Err(error); + } + + query_map_field = Some(field.clone()); + RequestFieldKind::QueryMap + }, _ => { return Err(syn::Error::new_spanned( ident, - "Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`", + "Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`, `query_map`", )); } } @@ -210,6 +232,14 @@ impl TryFrom for Request { )); } + if query_map_field.is_some() && fields.iter().any(|f| f.is_query()) { + return Err(syn::Error::new_spanned( + // TODO: raw, + raw.request_kw, + "Can't have both a query map field and regular query fields", + )); + } + Ok(Self { fields }) } } @@ -275,7 +305,20 @@ impl ToTokens for Request { TokenStream::new() }; - let request_query_struct = if self.has_query_fields() { + let request_query_struct = if let Some(field) = self.query_map_field() { + let ty = &field.ty; + let span = field.span(); + + quote_spanned! {span=> + /// Data in the request's query string. + #[derive( + Debug, + ruma_api::exports::serde::Deserialize, + ruma_api::exports::serde::Serialize, + )] + struct RequestQuery(#ty); + } + } else if self.has_query_fields() { let fields = self.fields.iter().filter_map(RequestField::as_query_field); quote! { @@ -317,6 +360,8 @@ pub enum RequestField { Path(Field), /// Data that appears in the query string. Query(Field), + /// Data that appears in the query string as dynamic key-value pairs. + QueryMap(Field), } impl RequestField { @@ -330,6 +375,7 @@ impl RequestField { RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), + RequestFieldKind::QueryMap => RequestField::QueryMap(field), } } @@ -341,6 +387,7 @@ impl RequestField { RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, RequestField::Path(..) => RequestFieldKind::Path, RequestField::Query(..) => RequestFieldKind::Query, + RequestField::QueryMap(..) => RequestFieldKind::QueryMap, } } @@ -384,6 +431,11 @@ impl RequestField { self.field_of_kind(RequestFieldKind::Query) } + /// Return the contained field if this request field is a query map kind. + fn as_query_map_field(&self) -> Option<&Field> { + self.field_of_kind(RequestFieldKind::QueryMap) + } + /// Gets the inner `Field` value. fn field(&self) -> &Field { match self { @@ -391,7 +443,8 @@ impl RequestField { | RequestField::Header(field, _) | RequestField::NewtypeBody(field) | RequestField::Path(field) - | RequestField::Query(field) => field, + | RequestField::Query(field) + | RequestField::QueryMap(field) => field, } } @@ -418,4 +471,6 @@ enum RequestFieldKind { Path, /// See the similarly named variant of `RequestField`. Query, + /// See the similarly named variant of `RequestField`. + QueryMap, } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index ddc7b014..fc033277 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -69,3 +69,26 @@ pub mod newtype_body_endpoint { } } } + +pub mod query_map_endpoint { + use ruma_api_macros::ruma_api; + + ruma_api! { + metadata { + description: "Does something.", + method: GET, + name: "newtype_body_endpoint", + path: "/_matrix/some/query/map/endpoint", + rate_limited: false, + requires_authentication: false, + } + + request { + #[ruma_api(query_map)] + pub fields: Vec<(String, String)>, + } + + response { + } + } +} From 8c898f3766f893aa1f928a8726b87af10f407604 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 19 Nov 2019 20:38:27 +0100 Subject: [PATCH 206/295] ruma-api-macros: Use syn::Path::is_ident --- ruma-api-macros/src/api.rs | 4 +--- ruma-api-macros/src/api/attribute.rs | 11 ++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 9ddb4b12..80cdf2b4 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -20,9 +20,7 @@ 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(); - field - .attrs - .retain(|attr| attr.path.segments.len() != 1 || attr.path.segments[0].ident != "serde"); + field.attrs.retain(|attr| attr.path.is_ident("serde")); field } diff --git a/ruma-api-macros/src/api/attribute.rs b/ruma-api-macros/src/api/attribute.rs index ffedffdd..d958ac13 100644 --- a/ruma-api-macros/src/api/attribute.rs +++ b/ruma-api-macros/src/api/attribute.rs @@ -29,13 +29,10 @@ impl Meta { /// /// Panics if the given attribute is a ruma_api attribute, but fails to parse. pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { - match &attr.path { - syn::Path { leading_colon: None, segments } - if segments.len() == 1 && segments[0].ident == "ruma_api" => - { - attr.parse_args().map(Some) - } - _ => Ok(None), + if attr.path.is_ident("ruma_api") { + attr.parse_args().map(Some) + } else { + Ok(None) } } } From d054bbb407971dbdca642a2fc2b7442a69344485 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 20 Nov 2019 23:28:50 +0100 Subject: [PATCH 207/295] Update changelogs --- CHANGELOG.md | 7 ------- ruma-api-macros/CHANGELOG.md | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b76bbe5..6f75af79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,5 @@ # [unreleased] -Improvements: - -* Add more sanity checks - * No multiple `#[ruma_api(body)]` fields in one request / response definition - * No multiple field kind declarations `#[ruma_api(body|query|path)]` on one field - * No (newtype) body fields in GET endpoints - # 0.11.0 Breaking changes: diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 71dff348..07e8e1c0 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,17 @@ # [unreleased] +Improvements: + +* Add spans to almost every error that can come up in `ruma_api!` +* Add a new field kind: `#[ruma_api(query_map)]` + * This allows endpoints that have a dynamic set of query parameters to be implemented + * For details see the documentation of `ruma_api!` +* Add more sanity checks + * No multiple `#[ruma_api(body)]` fields in one request / response definition + * No multiple field kind declarations `#[ruma_api(body|query|path)]` on one field + * No (newtype) body fields in GET endpoints +* Lots of refactoring of the internals + # 0.7.1 Bug fixes: From 65568dbf1bdda4fff39d97b2666c732979859ecd Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 20 Nov 2019 23:36:11 +0100 Subject: [PATCH 208/295] Update ruma_api! docs --- ruma-api-macros/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 9ac63182..8ed72b7b 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -28,7 +28,7 @@ mod api; /// /// The macro expects the following structure as input: /// -/// ```text +/// ```rust /// ruma_api! { /// metadata { /// description: &'static str @@ -49,7 +49,6 @@ mod api; /// // in the response from this API endpoint. /// } /// } -/// # } /// ``` /// /// This will generate a `ruma_api::Metadata` value to be used for the `ruma_api::Endpoint`'s @@ -92,6 +91,10 @@ mod api; /// component of the request URL. /// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query /// string. +/// * `#[ruma_api(query_map)]`: Instead of individual query fields, one query_map field, of any +/// type that implements `IntoIterator` (e.g. +/// `HashMap`, can be used for cases where an endpoint supports arbitrary query +/// parameters. /// /// Any field that does not include one of these attributes will be part of the request's JSON /// body. From 6f5c2833baef4a1713e423872c1723c880a05257 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 20 Nov 2019 23:36:43 +0100 Subject: [PATCH 209/295] Bump versions --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- ruma-api-macros/CHANGELOG.md | 2 ++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f75af79..b559748e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.11.1 + +Improvements: + +* Update ruma-api-macros to 0.8.1 + # 0.11.0 Breaking changes: diff --git a/Cargo.toml b/Cargo.toml index 83c4f322..9b290a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.11.0" +version = "0.11.1" edition = "2018" [dependencies] diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 07e8e1c0..36a509d8 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.8.1 + Improvements: * Add spans to almost every error that can come up in `ruma_api!` diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index c7c112f2..400a042e 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.8.0" +version = "0.8.1" edition = "2018" [dependencies] From d83c79d444170a9eb4ebeded1a9e97ef64443855 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 20 Nov 2019 23:53:38 +0100 Subject: [PATCH 210/295] Fix doctest that was accidentally not ignored --- ruma-api-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 8ed72b7b..8b9c3240 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -28,7 +28,7 @@ mod api; /// /// The macro expects the following structure as input: /// -/// ```rust +/// ```ignore /// ruma_api! { /// metadata { /// description: &'static str From ca5344582b2a8cea822f53bb097c82a278163ff7 Mon Sep 17 00:00:00 2001 From: Aaron DeVore Date: Wed, 20 Nov 2019 23:22:26 -0800 Subject: [PATCH 211/295] Fix inverted logic in strip_serde_attrs --- ruma-api-macros/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 80cdf2b4..e854d332 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -20,7 +20,7 @@ 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(); - field.attrs.retain(|attr| attr.path.is_ident("serde")); + field.attrs.retain(|attr| !attr.path.is_ident("serde")); field } From f0bdc9a7fb9cc7bc336b36850033f5db2fd0692b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 21 Nov 2019 11:01:08 +0100 Subject: [PATCH 212/295] Add field with serde attribute to test to catch issues with serde attribute stripping in tests --- tests/ruma_api_macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index fc033277..a99d24dd 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -36,6 +36,10 @@ pub mod some_endpoint { // With no attribute on the field, it will be extracted from the body of the response. pub value: String, + + // You can use serde attributes on any kind of field + #[serde(skip_serializing_if = "Option::is_none")] + pub optional_flag: Option, } } } From 4c44a6a5b99866f6e5d09a2fecda6e05b722a173 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 21 Nov 2019 11:13:37 +0100 Subject: [PATCH 213/295] Allow CI to pass even if clippy is not available --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 026c942d..87cbf132 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_script: - rustup component add rustfmt - | if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ]; then - rustup component add clippy + rustup component add clippy || true fi - | if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then @@ -28,7 +28,7 @@ script: fi - cargo fmt --all -- --check - | - if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ]; then + if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ] && ( rustup component list | grep -q clippy ); then cargo clippy --all --all-targets --all-features -- -D warnings fi - cargo build --all --verbose From 924cd4c36cf30e804a0e8a22734ea41180437651 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 21 Nov 2019 11:53:37 +0100 Subject: [PATCH 214/295] Update changelogs, bump versions --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- ruma-api-macros/CHANGELOG.md | 12 +++++++++++- ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b559748e..ecced0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.11.2 + +Improvements: + +* Update ruma-api-macros to 0.8.2 + # 0.11.1 Improvements: diff --git a/Cargo.toml b/Cargo.toml index 9b290a7b..6c39191b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.11.1" +version = "0.11.2" edition = "2018" [dependencies] diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 36a509d8..67e46644 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,11 +1,19 @@ # [unreleased] +# 0.8.2 + +Bug fixes: + +* Fix handling of `request` / `response` blocks containing fields with serde attributes ([#31][]) + +[#31]: https://github.com/ruma/ruma-api/pull/31 + # 0.8.1 Improvements: * Add spans to almost every error that can come up in `ruma_api!` -* Add a new field kind: `#[ruma_api(query_map)]` +* Add a new field kind: `#[ruma_api(query_map)]` ([#30][]) * This allows endpoints that have a dynamic set of query parameters to be implemented * For details see the documentation of `ruma_api!` * Add more sanity checks @@ -14,6 +22,8 @@ Improvements: * No (newtype) body fields in GET endpoints * Lots of refactoring of the internals +[#30]: https://github.com/ruma/ruma-api/pull/30 + # 0.7.1 Bug fixes: diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 400a042e..88b85b2d 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.8.1" +version = "0.8.2" edition = "2018" [dependencies] From 15f875a06e2ca053b9304f8801c6829ffa94eead Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 22 Nov 2019 11:03:48 +0100 Subject: [PATCH 215/295] Bump MSRV --- .travis.yml | 2 +- CHANGELOG.md | 4 ++++ README.md | 2 +- ruma-api-macros/src/lib.rs | 2 +- src/lib.rs | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87cbf132..04c81b29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: "rust" cache: "cargo" rust: - - 1.34.2 + - 1.36.0 - stable - beta - nightly diff --git a/CHANGELOG.md b/CHANGELOG.md index ecced0ad..a6229d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # [unreleased] +Breaking changes: + +* Our Minimum Supported Rust Version is now 1.36.0 + # 0.11.2 Improvements: diff --git a/README.md b/README.md index eea65d54..9496681a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ These types can be shared by client and server code for all Matrix APIs. ## Minimum Rust version -ruma-api requires Rust 1.34.2 or later. +ruma-api requires Rust 1.36.0 or later. ## Documentation diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 8b9c3240..051e1d09 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -9,7 +9,7 @@ // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released. )] #![allow(clippy::cognitive_complexity)] -// Since we support Rust 1.34.2, we can't apply this suggestion yet +// Since we support Rust 1.36.0, we can't apply this suggestion yet #![allow(clippy::use_self)] #![recursion_limit = "256"] diff --git a/src/lib.rs b/src/lib.rs index 66e7cfbb..355e4dee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ #![warn(rust_2018_idioms)] #![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)] -// Since we support Rust 1.34.2, we can't apply this suggestion yet +// Since we support Rust 1.36.0, we can't apply this suggestion yet #![allow(clippy::use_self)] use std::{ From f2addacccb4b312748209181b24f9693984ce177 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 22 Nov 2019 11:05:16 +0100 Subject: [PATCH 216/295] Fixup for previous commit --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 04c81b29..4579af1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ jobs: before_script: - rustup component add rustfmt - | - if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ]; then + if [ "$TRAVIS_RUST_VERSION" != "1.36.0" ]; then rustup component add clippy || true fi - | @@ -28,7 +28,7 @@ script: fi - cargo fmt --all -- --check - | - if [ "$TRAVIS_RUST_VERSION" != "1.34.2" ] && ( rustup component list | grep -q clippy ); then + if [ "$TRAVIS_RUST_VERSION" != "1.36.0" ] && ( rustup component list | grep -q clippy ); then cargo clippy --all --all-targets --all-features -- -D warnings fi - cargo build --all --verbose From 0f803e2acf8059e7403576fcb97257b162017d23 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 25 Nov 2019 22:41:16 +0100 Subject: [PATCH 217/295] Move documentation of ruma_api! from ruma-api-macros to ruma-api --- ruma-api-macros/src/lib.rs | 175 +------------------------------------ src/lib.rs | 170 +++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 174 deletions(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 051e1d09..af90cd9b 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -3,11 +3,7 @@ //! //! See the documentation for the `ruma_api!` macro for usage details. -#![deny( - missing_copy_implementations, - missing_debug_implementations, - // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released. -)] +#![deny(missing_copy_implementations, missing_debug_implementations)] #![allow(clippy::cognitive_complexity)] // Since we support Rust 1.36.0, we can't apply this suggestion yet #![allow(clippy::use_self)] @@ -24,175 +20,6 @@ use crate::api::{Api, RawApi}; mod api; -/// Generates a `ruma_api::Endpoint` from a concise definition. -/// -/// The macro expects the following structure as input: -/// -/// ```ignore -/// ruma_api! { -/// metadata { -/// description: &'static str -/// method: http::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 `http::Request` and to create a response from a -/// `http::Response` and vice versa. -/// -/// 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 `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". -/// 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 -/// `http::Request`: -/// -/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP -/// headers on the request. -/// 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 -/// component of the request URL. -/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query -/// string. -/// * `#[ruma_api(query_map)]`: Instead of individual query fields, one query_map field, of any -/// type that implements `IntoIterator` (e.g. -/// `HashMap`, can be used for cases where an endpoint supports arbitrary query -/// parameters. -/// -/// 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 -/// `http::Request`: -/// -/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP -/// headers on the response. -/// 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`. -/// -/// 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,ignore -/// pub mod some_endpoint { -/// use ruma_api_macros::ruma_api; -/// -/// ruma_api! { -/// metadata { -/// description: "Does something.", -/// method: GET, -/// name: "some_endpoint", -/// path: "/_matrix/some/endpoint/:baz", -/// rate_limited: false, -/// requires_authentication: false, -/// } -/// -/// request { -/// pub foo: String, -/// -/// #[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 = CONTENT_TYPE)] -/// pub content_type: String, -/// -/// pub value: String, -/// } -/// } -/// } -/// -/// pub mod newtype_body_endpoint { -/// use ruma_api_macros::ruma_api; -/// -/// #[derive(Clone, Debug, Deserialize, Serialize)] -/// pub struct MyCustomType { -/// pub foo: String, -/// } -/// -/// ruma_api! { -/// metadata { -/// description: "Does something.", -/// 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 raw_api = syn::parse_macro_input!(input as RawApi); diff --git a/src/lib.rs b/src/lib.rs index 355e4dee..3f6867e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,176 @@ use ruma_identifiers; use serde_json; use serde_urlencoded; +/// 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: http::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 `http::Request` and to create a response from a +/// `http::Response` and vice versa. +/// +/// 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 `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". +/// 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 +/// `http::Request`: +/// +/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP +/// headers on the request. +/// 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 +/// component of the request URL. +/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query +/// string. +/// * `#[ruma_api(query_map)]`: Instead of individual query fields, one query_map field, of any +/// type that implements `IntoIterator` (e.g. +/// `HashMap`, can be used for cases where an endpoint supports arbitrary query +/// parameters. +/// +/// 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 +/// `http::Request`: +/// +/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP +/// headers on the response. +/// 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`. +/// +/// 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 +/// +/// ``` +/// pub mod some_endpoint { +/// use ruma_api_macros::ruma_api; +/// +/// ruma_api! { +/// metadata { +/// description: "Does something.", +/// method: POST, +/// name: "some_endpoint", +/// path: "/_matrix/some/endpoint/:baz", +/// rate_limited: false, +/// requires_authentication: false, +/// } +/// +/// request { +/// pub foo: String, +/// +/// #[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 = CONTENT_TYPE)] +/// pub content_type: String, +/// +/// pub value: String, +/// } +/// } +/// } +/// +/// pub mod newtype_body_endpoint { +/// use ruma_api_macros::ruma_api; +/// use serde::{Deserialize, Serialize}; +/// +/// #[derive(Clone, Debug, Deserialize, Serialize)] +/// pub struct MyCustomType { +/// pub foo: String, +/// } +/// +/// ruma_api! { +/// metadata { +/// description: "Does something.", +/// method: PUT, +/// 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, +/// } +/// } +/// } +/// ``` #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; From 02a9a6494d4c5fe49e4bc70e5e89d7595ca3b405 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 26 Nov 2019 00:05:36 +0100 Subject: [PATCH 218/295] Update ruma-api-macro's crate docs --- ruma-api-macros/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index af90cd9b..94b90380 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -1,7 +1,9 @@ -//! Crate `ruma-api-macros` provides a procedural macro for easily generating +//! 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. +//! This crate should never be used directly; instead, use it through the +//! re-exports in ruma-api. Also note that for technical reasons, the +//! `ruma_api!` macro is only documented in ruma-api, not here. #![deny(missing_copy_implementations, missing_debug_implementations)] #![allow(clippy::cognitive_complexity)] From 14fa10af6b35a6363b8bf5c67adcd5f2ae99d83c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 26 Nov 2019 00:08:39 +0100 Subject: [PATCH 219/295] Update ruma-api-macros/README.md --- ruma-api-macros/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ruma-api-macros/README.md b/ruma-api-macros/README.md index 5fe82430..e9eb115e 100644 --- a/ruma-api-macros/README.md +++ b/ruma-api-macros/README.md @@ -7,11 +7,13 @@ You define the endpoint's metadata, request fields, and response fields, and the ## Usage -Here is an example that shows most of the macro's functionality. +This crate is not meant to be used directly; instead, you can use it through the re-exports in ruma-api. -``` rust +Here is an example that shows most of the macro's functionality: + +```rust pub mod some_endpoint { - use ruma_api_macros::ruma_api; + use ruma_api::ruma_api; ruma_api! { metadata { @@ -55,7 +57,9 @@ pub mod some_endpoint { ## Documentation -ruma-api-macros has [comprehensive documentation](https://docs.rs/ruma-api-macros) available on docs.rs. +Please refer to the documentation of the `ruma_api!` re-export in [ruma-api][]. + +[ruma-api]: https://docs.rs/ruma-api ## License From 5729aa1344360da147c27526e8b64f820acd86b1 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 20:41:54 +0100 Subject: [PATCH 220/295] Revert "Remove percent-encoding dependency (not currently used)" This reverts commit 540a69a8e45ff117f0a1bb0272bbc40c526ed12e. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c39191b..7c656ed8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ edition = "2018" [dependencies] http = "0.1.19" +percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" serde = { version = "1.0.102", features = ["derive"], optional = true } @@ -23,7 +24,7 @@ url = { version = "2.1.0", optional = true } [features] default = ["with-ruma-api-macros"] -with-ruma-api-macros = ["ruma-api-macros", "serde", "url"] +with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] [workspace] members = [ From e383ae98eae8a3477110b94d718fca8e1583d96f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 15 Nov 2019 20:42:01 +0100 Subject: [PATCH 221/295] Revert "Remove server-side functionality" This reverts commit 958a0a01c47c051eebf493234c314bc101609f63. --- ruma-api-macros/src/api.rs | 155 +++++++++++++++++++++++++++- ruma-api-macros/src/api/request.rs | 54 +++++++++- ruma-api-macros/src/api/response.rs | 66 +++++++++++- src/lib.rs | 45 +++++++- 4 files changed, 306 insertions(+), 14 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index e854d332..9fbe21aa 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -90,7 +90,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.value(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); @@ -116,7 +124,7 @@ impl ToTokens for Api { } }); - quote! { + let set_tokens = quote! { let request_path = RequestPath { #request_path_init_fields }; @@ -126,11 +134,43 @@ impl ToTokens for Api { // the case for our placeholder url. let mut path_segments = url.path_segments_mut().unwrap(); #(#path_segment_push)* - } + }; + + 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()); + let path_field = self + .request + .path_field(path_var) + .expect("expected request to have path field"); + let ty = &path_field.ty; + + quote! { + #path_var_ident: { + let segment = path_segments.get(#i).unwrap().as_bytes(); + let decoded = + ruma_api::exports::percent_encoding::percent_decode(segment) + .decode_utf8_lossy(); + #ty::deserialize(decoded.into_deserializer()) + .map_err(|e: ruma_api::exports::serde_json::error::Error| e)? + } + } + }); + + let parse_tokens = quote! { + #(#path_fields,)* + }; + + (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 let Some(field) = self.request.query_map_field() { @@ -183,6 +223,21 @@ impl ToTokens for Api { TokenStream::new() }; + let extract_request_query = if self.request.has_query_fields() { + quote! { + let request_query: RequestQuery = + ruma_api::exports::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 add_headers = self.request.add_headers_to_request(); quote! { @@ -193,6 +248,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"); @@ -221,6 +290,36 @@ 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 = + ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; + } + } else if self.request.has_body_fields() { + quote! { + let request_body: RequestBody = + ruma_api::exports::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 try_deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { let field_type = &field.ty; @@ -256,6 +355,19 @@ impl ToTokens for Api { TokenStream::new() }; + let serialize_response_headers = self.response.apply_header_fields(); + + let try_serialize_response_body = if self.response.has_body() { + let body = self.response.to_body(); + quote! { + ruma_api::exports::serde_json::to_vec(&#body)? + } + } else { + quote! { + "{}".as_bytes().to_vec() + } + }; + let request_doc = format!( "Data for a request to the `{}` API endpoint.\n\n{}", name, @@ -273,6 +385,25 @@ impl ToTokens for Api { #[doc = #request_doc] #request_types + impl std::convert::TryFrom>> for Request { + type Error = ruma_api::Error; + + #[allow(unused_variables)] + fn try_from(request: ruma_api::exports::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 std::convert::TryFrom for ruma_api::exports::http::Request> { type Error = ruma_api::Error; @@ -304,6 +435,20 @@ impl ToTokens for Api { #[doc = #response_doc] #response_types + impl std::convert::TryFrom for ruma_api::exports::http::Response> { + type Error = ruma_api::Error; + + #[allow(unused_variables)] + fn try_from(response: Response) -> Result { + let response = ruma_api::exports::http::Response::builder() + .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") + #serialize_response_headers + .body(#try_serialize_response_body) + .unwrap(); + Ok(response) + } + } + impl std::convert::TryFrom>> for Response { type Error = ruma_api::Error; diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 3a741289..1e8ff1d9 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -42,6 +42,30 @@ 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) = 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_string = header_name.to_string(); + + quote! { + #field_name: headers.get(ruma_api::exports::http::header::#header_name) + .and_then(|v| v.to_str().ok()) + .ok_or(ruma_api::exports::serde_json::Error::missing_field(#header_name_string))? + .to_owned() + } + }); + + quote! { + #(#fields,)* + } + } + /// 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()) @@ -51,7 +75,6 @@ impl Request { 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()) @@ -77,6 +100,20 @@ impl Request { 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_of_kind(RequestFieldKind::Path)) + .find(|field| { + field + .ident + .as_ref() + .expect("expected field to have an identifier") + == name + }) + } + /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { self.fields.iter().find_map(RequestField::as_newtype_body_field) @@ -102,6 +139,17 @@ impl Request { 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( @@ -270,7 +318,7 @@ impl ToTokens for Request { quote_spanned! {span=> /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestBody(#ty); } } else if self.has_body_fields() { @@ -278,7 +326,7 @@ impl ToTokens for Request { quote! { /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Serialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct RequestBody { #(#fields),* } diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index d1881297..c413d6c2 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -33,6 +33,11 @@ impl Response { 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 response struct initializer. pub fn init_fields(&self) -> TokenStream { let fields = self.fields.iter().map(|response_field| match response_field { @@ -74,6 +79,63 @@ 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_name) = *response_field { + let field_name = field + .ident + .as_ref() + .expect("expected field to have an identifier"); + let span = field.span(); + + Some(quote_spanned! {span=> + .header(ruma_api::exports::http::header::#header_name, response.#field_name) + }) + } else { + None + } + }); + + quote! { + #(#header_calls)* + } + } + + /// Produces code to initialize the struct that will be used to create the response body. + pub fn to_body(&self) -> TokenStream { + if let Some(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),* + } + } + } + } + /// Gets the newtype body field, if this response has one. pub fn newtype_body_field(&self) -> Option<&Field> { self.fields.iter().find_map(ResponseField::as_newtype_body_field) @@ -195,7 +257,7 @@ impl ToTokens for Response { quote_spanned! {span=> /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct ResponseBody(#ty); } } else if self.has_body_fields() { @@ -203,7 +265,7 @@ impl ToTokens for Response { quote! { /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize)] + #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] struct ResponseBody { #(#fields),* } diff --git a/src/lib.rs b/src/lib.rs index 3f6867e1..b43ac198 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -206,6 +206,7 @@ pub use ruma_api_macros::ruma_api; /// It is not considered part of ruma-api's public API. pub mod exports { pub use http; + pub use percent_encoding; pub use serde; pub use serde_json; pub use serde_urlencoded; @@ -215,9 +216,12 @@ pub mod exports { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: TryInto>, Error = Error> { +pub trait Endpoint: + TryFrom>, Error = Error> + TryInto>, Error = Error> +{ /// Data returned in a successful response from the endpoint. - type Response: TryFrom>, Error = Error>; + type Response: TryFrom>, Error = Error> + + TryInto>, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -346,9 +350,10 @@ mod tests { pub mod create { use std::convert::TryFrom; - use http::{self, method::Method}; + use http::{self, header::CONTENT_TYPE, method::Method}; + use percent_encoding; use ruma_identifiers::{RoomAliasId, RoomId}; - use serde::{Deserialize, Serialize}; + use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; use crate::{Endpoint, Error, Metadata}; @@ -395,6 +400,25 @@ mod tests { } } + impl TryFrom>> for Request { + type Error = Error; + + fn try_from(request: http::Request>) -> Result { + let request_body: RequestBody = + ::serde_json::from_slice(request.body().as_slice())?; + let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); + Ok(Request { + room_id: request_body.room_id, + room_alias: { + let segment = path_segments.get(5).unwrap().as_bytes(); + let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); + RoomAliasId::deserialize(decoded.into_deserializer()) + .map_err(|e: serde_json::error::Error| e)? + }, + }) + } + } + #[derive(Debug, Serialize, Deserialize)] struct RequestBody { room_id: RoomId, @@ -415,5 +439,18 @@ mod tests { } } } + + impl TryFrom for http::Response> { + type Error = Error; + + fn try_from(_: Response) -> Result>, Self::Error> { + let response = http::Response::builder() + .header(CONTENT_TYPE, "application/json") + .body(b"{}".to_vec()) + .unwrap(); + + Ok(response) + } + } } } From f558b5569260ece47d14f8ffc367dbfdfc3b6077 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 19 Nov 2019 21:11:17 +0100 Subject: [PATCH 222/295] Add SendRecv trait + derive macro to allow receiving requests, sending responses --- Cargo.toml | 3 + ruma-api-macros/Cargo.toml | 2 +- ruma-api-macros/src/api.rs | 89 ++++---- ruma-api-macros/src/api/request.rs | 98 ++++++--- ruma-api-macros/src/api/response.rs | 102 ++++++--- ruma-api-macros/src/lib.rs | 15 +- ruma-api-macros/src/send_recv.rs | 195 ++++++++++++++++++ .../src/send_recv/wrap_incoming.rs | 58 ++++++ src/lib.rs | 31 ++- tests/ruma_api_macros.rs | 5 + 10 files changed, 487 insertions(+), 111 deletions(-) create mode 100644 ruma-api-macros/src/send_recv.rs create mode 100644 ruma-api-macros/src/send_recv/wrap_incoming.rs diff --git a/Cargo.toml b/Cargo.toml index 7c656ed8..b77b7745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ serde_json = "1.0.41" serde_urlencoded = "0.6.1" url = { version = "2.1.0", optional = true } +[dev-dependencies] +ruma-events = "0.15.1" + [features] default = ["with-ruma-api-macros"] with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 88b85b2d..3cfddf13 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" [dependencies] proc-macro2 = "1.0.6" quote = "1.0.2" -syn = { version = "1.0.8", features = ["full"] } +syn = { version = "1.0.8", features = ["full", "extra-traits"] } [lib] proc-macro = true diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 9fbe21aa..326576dd 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -85,10 +85,20 @@ impl ToTokens for Api { let rate_limited = &self.metadata.rate_limited; let requires_authentication = &self.metadata.requires_authentication; - let request = &self.request; - let request_types = quote! { #request }; - let response = &self.response; - let response_types = quote! { #response }; + let request_type = &self.request; + let response_type = &self.response; + + let request_try_from_type = if self.request.uses_wrap_incoming() { + quote!(IncomingRequest) + } else { + quote!(Request) + }; + + let response_try_from_type = if self.response.uses_wrap_incoming() { + quote!(IncomingResponse) + } else { + quote!(Response) + }; let extract_request_path = if self.request.has_path_fields() { quote! { @@ -110,7 +120,7 @@ impl ToTokens for Api { let request_path_init_fields = self.request.request_path_init_fields(); let path_segments = path_str[1..].split('/'); - let path_segment_push = path_segments.map(|segment| { + 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()); @@ -136,10 +146,8 @@ impl ToTokens for Api { #(#path_segment_push)* }; - let path_fields = path_segments - .enumerate() - .filter(|(_, s)| s.starts_with(':')) - .map(|(i, segment)| { + 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()); let path_field = self @@ -158,7 +166,8 @@ impl ToTokens for Api { .map_err(|e: ruma_api::exports::serde_json::error::Error| e)? } } - }); + }, + ); let parse_tokens = quote! { #(#path_fields,)* @@ -223,7 +232,12 @@ impl ToTokens for Api { TokenStream::new() }; - let extract_request_query = if self.request.has_query_fields() { + let extract_request_query = if self.request.query_map_field().is_some() { + quote! { + let request_query = + ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; + } + } else if self.request.has_query_fields() { quote! { let request_query: RequestQuery = ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; @@ -232,7 +246,13 @@ impl ToTokens for Api { TokenStream::new() }; - let parse_request_query = if self.request.has_query_fields() { + let parse_request_query = if let Some(field) = self.request.query_map_field() { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + + quote! { + #field_name: request_query + } + } else if self.request.has_query_fields() { self.request.request_init_query_fields() } else { TokenStream::new() @@ -290,15 +310,14 @@ impl ToTokens for Api { } }; - let extract_request_body = if let Some(field) = self.request.newtype_body_field() { - let ty = &field.ty; + let extract_request_body = if self.request.newtype_body_field().is_some() { quote! { - let request_body: #ty = + let request_body = ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; } } else if self.request.has_body_fields() { quote! { - let request_body: RequestBody = + let request_body: ::Incoming = ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; } } else { @@ -306,10 +325,7 @@ 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, @@ -320,18 +336,15 @@ impl ToTokens for Api { TokenStream::new() }; - let try_deserialize_response_body = if let Some(field) = self.response.newtype_body_field() - { - let field_type = &field.ty; + let response_body_type_annotation = if self.response.has_body_fields() { + quote!(: ::Incoming) + } else { + TokenStream::new() + }; + let try_deserialize_response_body = if self.response.has_body() { quote! { - ruma_api::exports::serde_json::from_slice::<#field_type>( - http_response.into_body().as_slice(), - )? - } - } else if self.response.has_body_fields() { - quote! { - ruma_api::exports::serde_json::from_slice::( + ruma_api::exports::serde_json::from_slice( http_response.into_body().as_slice(), )? } @@ -383,9 +396,9 @@ impl ToTokens for Api { use std::convert::TryInto as _; #[doc = #request_doc] - #request_types + #request_type - impl std::convert::TryFrom>> for Request { + impl std::convert::TryFrom>> for #request_try_from_type { type Error = ruma_api::Error; #[allow(unused_variables)] @@ -395,7 +408,7 @@ impl ToTokens for Api { #extract_request_headers #extract_request_body - Ok(Request { + Ok(Self { #parse_request_path #parse_request_query #parse_request_headers @@ -433,7 +446,7 @@ impl ToTokens for Api { } #[doc = #response_doc] - #response_types + #response_type impl std::convert::TryFrom for ruma_api::exports::http::Response> { type Error = ruma_api::Error; @@ -449,7 +462,7 @@ impl ToTokens for Api { } } - impl std::convert::TryFrom>> for Response { + impl std::convert::TryFrom>> for #response_try_from_type { type Error = ruma_api::Error; #[allow(unused_variables)] @@ -459,8 +472,10 @@ impl ToTokens for Api { if http_response.status().is_success() { #extract_response_headers - let response_body = #try_deserialize_response_body; - Ok(Response { + let response_body #response_body_type_annotation = + #try_deserialize_response_body; + + Ok(Self { #response_init_fields }) } else { diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 1e8ff1d9..e6261780 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -90,6 +90,11 @@ impl Request { self.fields.iter().filter_map(|field| field.as_body_field()) } + /// Whether any field has a #[wrap_incoming] attribute. + pub fn uses_wrap_incoming(&self) -> bool { + self.fields.iter().any(|f| f.has_wrap_incoming_attr()) + } + /// Produces an iterator over all the header fields. pub fn header_fields(&self) -> impl Iterator { self.fields.iter().filter(|field| field.is_header()) @@ -102,16 +107,9 @@ impl Request { /// 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_of_kind(RequestFieldKind::Path)) - .find(|field| { - field - .ident - .as_ref() - .expect("expected field to have an identifier") - == name - }) + self.fields.iter().flat_map(|f| f.field_of_kind(RequestFieldKind::Path)).find(|field| { + field.ident.as_ref().expect("expected field to have an identifier") == name + }) } /// Returns the body field. @@ -273,8 +271,8 @@ impl TryFrom for Request { .collect::>>()?; if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) { + // TODO: highlight conflicting fields, return Err(syn::Error::new_spanned( - // TODO: raw, raw.request_kw, "Can't have both a newtype body field and regular body fields", )); @@ -295,7 +293,8 @@ impl TryFrom for Request { impl ToTokens for Request { fn to_tokens(&self, tokens: &mut TokenStream) { let request_struct_header = quote! { - #[derive(Debug, Clone)] + #[derive(Debug, Clone, ruma_api::SendRecv)] + #[incoming_no_deserialize] pub struct Request }; @@ -312,28 +311,51 @@ impl ToTokens for Request { } }; - let request_body_struct = if let Some(field) = self.newtype_body_field() { - let ty = &field.ty; - let span = field.span(); + let request_body_struct = + if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { + let field = body_field.field(); + let ty = &field.ty; + let span = field.span(); + let derive_deserialize = if body_field.has_wrap_incoming_attr() { + TokenStream::new() + } else { + quote!(ruma_api::exports::serde::Deserialize) + }; - quote_spanned! {span=> - /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] - struct RequestBody(#ty); - } - } else if self.has_body_fields() { - let fields = self.fields.iter().filter_map(RequestField::as_body_field); - - quote! { - /// Data in the request body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] - struct RequestBody { - #(#fields),* + quote_spanned! {span=> + /// Data in the request body. + #[derive( + Debug, + ruma_api::SendRecv, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct RequestBody(#ty); } - } - } else { - TokenStream::new() - }; + } else if self.has_body_fields() { + let fields = self.fields.iter().filter(|f| f.is_body()); + let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { + TokenStream::new() + } else { + quote!(ruma_api::exports::serde::Deserialize) + }; + let fields = fields.map(RequestField::field); + + quote! { + /// Data in the request body. + #[derive( + Debug, + ruma_api::SendRecv, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct RequestBody { + #(#fields),* + } + } + } else { + TokenStream::new() + }; let request_path_struct = if self.has_path_fields() { let fields = self.fields.iter().filter_map(RequestField::as_path_field); @@ -449,6 +471,11 @@ impl RequestField { self.kind() == RequestFieldKind::Header } + /// Whether or not this request field is a newtype body kind. + fn is_newtype_body(&self) -> bool { + self.kind() == RequestFieldKind::NewtypeBody + } + /// Whether or not this request field is a path kind. fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path @@ -504,6 +531,13 @@ impl RequestField { None } } + + /// Whether or not the request field has a #[wrap_incoming] attribute. + fn has_wrap_incoming_attr(&self) -> bool { + self.field().attrs.iter().any(|attr| { + attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming" + }) + } } /// The types of fields that a request can have, without their values. diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index c413d6c2..eb0abe8b 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -38,6 +38,11 @@ impl Response { self.fields.iter().any(|field| !field.is_header()) } + /// Whether any field has a #[wrap_incoming] attribute. + pub fn uses_wrap_incoming(&self) -> bool { + self.fields.iter().any(|f| f.has_wrap_incoming_attr()) + } + /// Produces code for a response struct initializer. pub fn init_fields(&self) -> TokenStream { let fields = self.fields.iter().map(|response_field| match response_field { @@ -83,10 +88,8 @@ 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_name) = *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=> @@ -105,19 +108,14 @@ 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(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=> @@ -220,8 +218,8 @@ impl TryFrom for Response { .collect::>>()?; if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) { + // TODO: highlight conflicting fields, return Err(syn::Error::new_spanned( - // TODO: raw, raw.response_kw, "Can't have both a newtype body field and regular body fields", )); @@ -234,7 +232,8 @@ impl TryFrom for Response { impl ToTokens for Response { fn to_tokens(&self, tokens: &mut TokenStream) { let response_struct_header = quote! { - #[derive(Debug, Clone)] + #[derive(Debug, Clone, ruma_api::SendRecv)] + #[incoming_no_deserialize] pub struct Response }; @@ -251,28 +250,51 @@ impl ToTokens for Response { } }; - let response_body_struct = if let Some(field) = self.newtype_body_field() { - let ty = &field.ty; - let span = field.span(); + let response_body_struct = + if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { + let field = body_field.field(); + let ty = &field.ty; + let span = field.span(); + let derive_deserialize = if body_field.has_wrap_incoming_attr() { + TokenStream::new() + } else { + quote!(ruma_api::exports::serde::Deserialize) + }; - quote_spanned! {span=> - /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] - struct ResponseBody(#ty); - } - } else if self.has_body_fields() { - let fields = self.fields.iter().filter_map(ResponseField::as_body_field); - - quote! { - /// Data in the response body. - #[derive(Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize)] - struct ResponseBody { - #(#fields),* + quote_spanned! {span=> + /// Data in the response body. + #[derive( + Debug, + ruma_api::SendRecv, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct ResponseBody(#ty); } - } - } else { - TokenStream::new() - }; + } else if self.has_body_fields() { + let fields = self.fields.iter().filter(|f| f.is_body()); + let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { + TokenStream::new() + } else { + quote!(ruma_api::exports::serde::Deserialize) + }; + let fields = fields.map(ResponseField::field); + + quote! { + /// Data in the response body. + #[derive( + Debug, + ruma_api::SendRecv, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct ResponseBody { + #(#fields),* + } + } + } else { + TokenStream::new() + }; let response = quote! { #response_struct_header @@ -317,6 +339,11 @@ impl ResponseField { } } + /// Whether or not this response field is a newtype body kind. + fn is_newtype_body(&self) -> bool { + self.as_newtype_body_field().is_some() + } + /// Return the contained field if this response field is a body kind. fn as_body_field(&self) -> Option<&Field> { match self { @@ -332,6 +359,13 @@ impl ResponseField { _ => None, } } + + /// Whether or not the reponse field has a #[wrap_incoming] attribute. + fn has_wrap_incoming_attr(&self) -> bool { + self.field().attrs.iter().any(|attr| { + attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming" + }) + } } /// The types of fields that a response can have, without their values. diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 94b90380..0e5d4fb5 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -17,16 +17,27 @@ use std::convert::TryFrom as _; use proc_macro::TokenStream; use quote::ToTokens; +use syn::{parse_macro_input, DeriveInput}; -use crate::api::{Api, RawApi}; +use self::{ + api::{Api, RawApi}, + send_recv::expand_send_recv, +}; mod api; +mod send_recv; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let raw_api = syn::parse_macro_input!(input as RawApi); + let raw_api = parse_macro_input!(input as RawApi); match Api::try_from(raw_api) { Ok(api) => api.into_token_stream().into(), Err(err) => err.to_compile_error().into(), } } + +#[proc_macro_derive(SendRecv, attributes(wrap_incoming, incoming_no_deserialize))] +pub fn derive_send_recv(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand_send_recv(input).unwrap_or_else(|err| err.to_compile_error()).into() +} diff --git a/ruma-api-macros/src/send_recv.rs b/ruma-api-macros/src/send_recv.rs new file mode 100644 index 00000000..448e14e1 --- /dev/null +++ b/ruma-api-macros/src/send_recv.rs @@ -0,0 +1,195 @@ +use std::mem; + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + parse_quote, punctuated::Pair, spanned::Spanned, Attribute, Data, DeriveInput, Fields, + GenericArgument, Path, PathArguments, Type, TypePath, +}; + +mod wrap_incoming; + +use wrap_incoming::Meta; + +pub fn expand_send_recv(input: DeriveInput) -> syn::Result { + let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) { + TokenStream::new() + } else { + quote!(#[derive(ruma_api::exports::serde::Deserialize)]) + }; + + let mut fields: Vec<_> = match input.data { + Data::Enum(_) | Data::Union(_) => { + panic!("#[derive(SendRecv)] is only supported for structs") + } + Data::Struct(s) => match s.fields { + Fields::Named(fs) => fs.named.into_pairs().map(Pair::into_value).collect(), + Fields::Unnamed(fs) => fs.unnamed.into_pairs().map(Pair::into_value).collect(), + Fields::Unit => return Ok(impl_send_recv_incoming_self(input.ident)), + }, + }; + + let mut any_attribute = false; + + for field in &mut fields { + let mut field_meta = None; + + let mut remaining_attrs = Vec::new(); + for attr in mem::replace(&mut field.attrs, Vec::new()) { + if let Some(meta) = Meta::from_attribute(&attr)? { + if field_meta.is_some() { + return Err(syn::Error::new_spanned( + attr, + "duplicate #[wrap_incoming] attribute", + )); + } + field_meta = Some(meta); + any_attribute = true; + } else { + remaining_attrs.push(attr); + } + } + field.attrs = remaining_attrs; + + if let Some(attr) = field_meta { + if let Some(type_to_wrap) = attr.type_to_wrap { + wrap_generic_arg(&type_to_wrap, &mut field.ty, attr.wrapper_type.as_ref())?; + } else { + wrap_ty(&mut field.ty, attr.wrapper_type)?; + } + } + } + + if !any_attribute { + return Ok(impl_send_recv_incoming_self(input.ident)); + } + + let vis = input.vis; + let doc = format!("\"Incoming\" variant of [{ty}](struct.{ty}.html).", ty = input.ident); + let original_ident = input.ident; + let incoming_ident = Ident::new(&format!("Incoming{}", original_ident), Span::call_site()); + + Ok(quote! { + #[doc = #doc] + #derive_deserialize + #vis struct #incoming_ident { + #(#fields,)* + } + + impl ruma_api::SendRecv for #original_ident { + type Incoming = #incoming_ident; + } + }) +} + +fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool { + for attr in attrs { + match &attr.path { + Path { leading_colon: None, segments } + if segments.len() == 1 && segments[0].ident == "incoming_no_deserialize" => + { + return true + } + _ => {} + } + } + + false +} + +fn impl_send_recv_incoming_self(ident: Ident) -> TokenStream { + quote! { + impl ruma_api::SendRecv for #ident { + type Incoming = Self; + } + } +} + +fn wrap_ty(ty: &mut Type, path: Option) -> syn::Result<()> { + if let Some(wrap_ty) = path { + *ty = parse_quote!(#wrap_ty<#ty>); + } else { + match ty { + Type::Path(TypePath { path, .. }) => { + let ty_ident = &mut path.segments.last_mut().unwrap().ident; + let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site()); + *ty_ident = parse_quote!(#ident); + } + _ => return Err(syn::Error::new_spanned(ty, "Can't wrap this type")), + } + } + + Ok(()) +} + +fn wrap_generic_arg(type_to_wrap: &Type, of: &mut Type, with: Option<&Path>) -> syn::Result<()> { + let mut span = None; + wrap_generic_arg_impl(type_to_wrap, of, with, &mut span)?; + + if span.is_some() { + Ok(()) + } else { + Err(syn::Error::new_spanned( + of, + format!( + "Couldn't find generic argument `{}` in this type", + type_to_wrap.to_token_stream() + ), + )) + } +} + +fn wrap_generic_arg_impl( + type_to_wrap: &Type, + of: &mut Type, + with: Option<&Path>, + span: &mut Option, +) -> syn::Result<()> { + // TODO: Support things like array types? + let ty_path = match of { + Type::Path(TypePath { path, .. }) => path, + _ => return Ok(()), + }; + + let args = match &mut ty_path.segments.last_mut().unwrap().arguments { + PathArguments::AngleBracketed(ab) => &mut ab.args, + _ => return Ok(()), + }; + + for arg in args.iter_mut() { + let ty = match arg { + GenericArgument::Type(ty) => ty, + _ => continue, + }; + + if ty == type_to_wrap { + if let Some(s) = span { + let mut error = syn::Error::new( + *s, + format!( + "`{}` found multiple times, this is not currently supported", + type_to_wrap.to_token_stream() + ), + ); + error.combine(syn::Error::new_spanned(ty, "second occurrence")); + return Err(error); + } + + *span = Some(ty.span()); + + if let Some(wrapper_type) = with { + *ty = parse_quote!(#wrapper_type<#ty>); + } else if let Type::Path(TypePath { path, .. }) = ty { + let ty_ident = &mut path.segments.last_mut().unwrap().ident; + let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site()); + *ty_ident = parse_quote!(#ident); + } else { + return Err(syn::Error::new_spanned(ty, "Can't wrap this type")); + } + } else { + wrap_generic_arg_impl(type_to_wrap, ty, with, span)?; + } + } + + Ok(()) +} diff --git a/ruma-api-macros/src/send_recv/wrap_incoming.rs b/ruma-api-macros/src/send_recv/wrap_incoming.rs new file mode 100644 index 00000000..f13c6f3c --- /dev/null +++ b/ruma-api-macros/src/send_recv/wrap_incoming.rs @@ -0,0 +1,58 @@ +use syn::{ + parse::{Parse, ParseStream}, + Ident, Path, Type, +}; + +mod kw { + use syn::custom_keyword; + custom_keyword!(with); +} + +/// The inside of a `#[wrap_incoming]` attribute +#[derive(Default)] +pub struct Meta { + pub type_to_wrap: Option, + pub wrapper_type: Option, +} + +impl Meta { + /// Check if the given attribute is a wrap_incoming attribute. If it is, parse it. + pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { + if attr.path.is_ident("wrap_incoming") { + if attr.tokens.is_empty() { + Ok(Some(Self::default())) + } else { + attr.parse_args().map(Some) + } + } else { + Ok(None) + } + } +} + +impl Parse for Meta { + fn parse(input: ParseStream) -> syn::Result { + let mut type_to_wrap = None; + let mut wrapper_type = try_parse_wrapper_type(input)?; + + if wrapper_type.is_none() && input.peek(Ident) { + type_to_wrap = Some(input.parse()?); + wrapper_type = try_parse_wrapper_type(input)?; + } + + if input.is_empty() { + Ok(Self { type_to_wrap, wrapper_type }) + } else { + Err(input.error("expected end of attribute args")) + } + } +} + +fn try_parse_wrapper_type(input: ParseStream) -> syn::Result> { + if input.peek(kw::with) { + input.parse::()?; + Ok(Some(input.parse()?)) + } else { + Ok(None) + } +} diff --git a/src/lib.rs b/src/lib.rs index b43ac198..47f2bfc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,6 +200,9 @@ use serde_urlencoded; #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; +#[cfg(feature = "with-ruma-api-macros")] +pub use ruma_api_macros::SendRecv; + #[cfg(feature = "with-ruma-api-macros")] #[doc(hidden)] /// This module is used to support the generated code from ruma-api-macros. @@ -213,15 +216,25 @@ pub mod exports { pub use url; } +/// A type that can be sent as well as received. Types that implement this trait have a +/// corresponding 'Incoming' type, which is either just `Self`, or another type that has the same +/// fields with some types exchanged by ones that allow fallible deserialization, e.g. `EventResult` +/// from ruma_events. +pub trait SendRecv { + /// The 'Incoming' variant of `Self`. + type Incoming; +} + /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: - TryFrom>, Error = Error> + TryInto>, Error = Error> +pub trait Endpoint: SendRecv + TryInto>, Error = Error> +where + ::Incoming: TryFrom>, Error = Error>, + ::Incoming: TryFrom>, Error = Error>, { /// Data returned in a successful response from the endpoint. - type Response: TryFrom>, Error = Error> - + TryInto>, Error = Error>; + type Response: SendRecv + TryInto>, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -356,7 +369,7 @@ mod tests { use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; - use crate::{Endpoint, Error, Metadata}; + use crate::{Endpoint, Error, Metadata, SendRecv}; /// A request to create a new room alias. #[derive(Debug)] @@ -365,6 +378,10 @@ mod tests { pub room_alias: RoomAliasId, // path } + impl SendRecv for Request { + type Incoming = Self; + } + impl Endpoint for Request { type Response = Response; @@ -428,6 +445,10 @@ mod tests { #[derive(Clone, Copy, Debug)] pub struct Response; + impl SendRecv for Response { + type Incoming = Self; + } + impl TryFrom>> for Response { type Error = Error; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index a99d24dd..d43c9269 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,5 +1,6 @@ pub mod some_endpoint { use ruma_api::ruma_api; + use ruma_events::{tag::TagEventContent, EventResult}; ruma_api! { metadata { @@ -40,6 +41,10 @@ pub mod some_endpoint { // You can use serde attributes on any kind of field #[serde(skip_serializing_if = "Option::is_none")] pub optional_flag: Option, + + /// The user's tags for the room. + #[wrap_incoming(with EventResult)] + pub tags: TagEventContent, } } } From 4c1d6b4b5835f8926bb80544bebbc3d00a38dc77 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 25 Nov 2019 23:09:49 +0100 Subject: [PATCH 223/295] Add documentation for SendRecv --- ruma-api-macros/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 8 ++++++++ 2 files changed, 45 insertions(+) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 0e5d4fb5..f20378d3 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -36,6 +36,43 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { } } +/// Derive the `SendRecv` trait, possibly generating an 'Incoming' version of the struct this +/// derive macro is used on. Specifically, if no `#[wrap_incoming]` attribute is used on any of the +/// fields of the struct, this simple implementation will be generated: +/// +/// ```ignore +/// impl SendRecv for MyType { +/// type Incoming = Self; +/// } +/// ``` +/// +/// If, however, `#[wrap_incoming]` is used (which is the only reason you should ever use this +/// derive macro manually), a new struct `IncomingT` (where `T` is the type this derive is used on) +/// is generated, with all of the fields with `#[wrap_incoming]` replaced: +/// +/// ```ignore +/// #[derive(SendRecv)] +/// struct MyType { +/// pub foo: Foo, +/// #[wrap_incoming] +/// pub bar: Bar, +/// #[wrap_incoming(Baz)] +/// pub baz: Option, +/// #[wrap_incoming(with EventResult)] +/// pub x: XEvent, +/// #[wrap_incoming(YEvent with EventResult)] +/// pub ys: Vec, +/// } +/// +/// // generated +/// struct IncomingMyType { +/// pub foo: Foo, +/// pub bar: IncomingBar, +/// pub baz: Option, +/// pub x: EventResult, +/// pub ys: Vec>, +/// } +/// ``` #[proc_macro_derive(SendRecv, attributes(wrap_incoming, incoming_no_deserialize))] pub fn derive_send_recv(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/src/lib.rs b/src/lib.rs index 47f2bfc4..b81410c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,6 +197,14 @@ use serde_urlencoded; /// } /// } /// ``` +/// +/// ## Fallible deserialization +/// +/// All request and response types also derive `ruma_api::SendRecv`. As such, to allow fallible +/// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the +/// documentation for [`SendRecv`][]. +/// +/// [`SendRecv`]: derive.SendRecv.html #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; From d94e15e38c88baeb7c63c7540b2f607879936844 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 25 Nov 2019 23:28:18 +0100 Subject: [PATCH 224/295] Rename SendRecv to Outgoing --- ruma-api-macros/src/api.rs | 4 +-- ruma-api-macros/src/api/request.rs | 6 ++-- ruma-api-macros/src/api/response.rs | 6 ++-- .../src/{send_recv.rs => derive_outgoing.rs} | 10 +++--- .../wrap_incoming.rs | 0 ruma-api-macros/src/lib.rs | 14 ++++---- src/lib.rs | 34 ++++++++++--------- 7 files changed, 38 insertions(+), 36 deletions(-) rename ruma-api-macros/src/{send_recv.rs => derive_outgoing.rs} (94%) rename ruma-api-macros/src/{send_recv => derive_outgoing}/wrap_incoming.rs (100%) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 326576dd..627ea442 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -317,7 +317,7 @@ impl ToTokens for Api { } } else if self.request.has_body_fields() { quote! { - let request_body: ::Incoming = + let request_body: ::Incoming = ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; } } else { @@ -337,7 +337,7 @@ impl ToTokens for Api { }; let response_body_type_annotation = if self.response.has_body_fields() { - quote!(: ::Incoming) + quote!(: ::Incoming) } else { TokenStream::new() }; diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index e6261780..a6efc456 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -293,7 +293,7 @@ impl TryFrom for Request { impl ToTokens for Request { fn to_tokens(&self, tokens: &mut TokenStream) { let request_struct_header = quote! { - #[derive(Debug, Clone, ruma_api::SendRecv)] + #[derive(Debug, Clone, ruma_api::Outgoing)] #[incoming_no_deserialize] pub struct Request }; @@ -326,7 +326,7 @@ impl ToTokens for Request { /// Data in the request body. #[derive( Debug, - ruma_api::SendRecv, + ruma_api::Outgoing, ruma_api::exports::serde::Serialize, #derive_deserialize )] @@ -345,7 +345,7 @@ impl ToTokens for Request { /// Data in the request body. #[derive( Debug, - ruma_api::SendRecv, + ruma_api::Outgoing, ruma_api::exports::serde::Serialize, #derive_deserialize )] diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index eb0abe8b..29f2b4ae 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -232,7 +232,7 @@ impl TryFrom for Response { impl ToTokens for Response { fn to_tokens(&self, tokens: &mut TokenStream) { let response_struct_header = quote! { - #[derive(Debug, Clone, ruma_api::SendRecv)] + #[derive(Debug, Clone, ruma_api::Outgoing)] #[incoming_no_deserialize] pub struct Response }; @@ -265,7 +265,7 @@ impl ToTokens for Response { /// Data in the response body. #[derive( Debug, - ruma_api::SendRecv, + ruma_api::Outgoing, ruma_api::exports::serde::Serialize, #derive_deserialize )] @@ -284,7 +284,7 @@ impl ToTokens for Response { /// Data in the response body. #[derive( Debug, - ruma_api::SendRecv, + ruma_api::Outgoing, ruma_api::exports::serde::Serialize, #derive_deserialize )] diff --git a/ruma-api-macros/src/send_recv.rs b/ruma-api-macros/src/derive_outgoing.rs similarity index 94% rename from ruma-api-macros/src/send_recv.rs rename to ruma-api-macros/src/derive_outgoing.rs index 448e14e1..8d8e866b 100644 --- a/ruma-api-macros/src/send_recv.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -11,7 +11,7 @@ mod wrap_incoming; use wrap_incoming::Meta; -pub fn expand_send_recv(input: DeriveInput) -> syn::Result { +pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) { TokenStream::new() } else { @@ -20,7 +20,7 @@ pub fn expand_send_recv(input: DeriveInput) -> syn::Result { let mut fields: Vec<_> = match input.data { Data::Enum(_) | Data::Union(_) => { - panic!("#[derive(SendRecv)] is only supported for structs") + panic!("#[derive(Outgoing)] is only supported for structs") } Data::Struct(s) => match s.fields { Fields::Named(fs) => fs.named.into_pairs().map(Pair::into_value).collect(), @@ -65,7 +65,7 @@ pub fn expand_send_recv(input: DeriveInput) -> syn::Result { } let vis = input.vis; - let doc = format!("\"Incoming\" variant of [{ty}](struct.{ty}.html).", ty = input.ident); + let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = input.ident); let original_ident = input.ident; let incoming_ident = Ident::new(&format!("Incoming{}", original_ident), Span::call_site()); @@ -76,7 +76,7 @@ pub fn expand_send_recv(input: DeriveInput) -> syn::Result { #(#fields,)* } - impl ruma_api::SendRecv for #original_ident { + impl ruma_api::Outgoing for #original_ident { type Incoming = #incoming_ident; } }) @@ -99,7 +99,7 @@ fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool { fn impl_send_recv_incoming_self(ident: Ident) -> TokenStream { quote! { - impl ruma_api::SendRecv for #ident { + impl ruma_api::Outgoing for #ident { type Incoming = Self; } } diff --git a/ruma-api-macros/src/send_recv/wrap_incoming.rs b/ruma-api-macros/src/derive_outgoing/wrap_incoming.rs similarity index 100% rename from ruma-api-macros/src/send_recv/wrap_incoming.rs rename to ruma-api-macros/src/derive_outgoing/wrap_incoming.rs diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index f20378d3..d2024306 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -21,11 +21,11 @@ use syn::{parse_macro_input, DeriveInput}; use self::{ api::{Api, RawApi}, - send_recv::expand_send_recv, + derive_outgoing::expand_derive_outgoing, }; mod api; -mod send_recv; +mod derive_outgoing; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { @@ -36,12 +36,12 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { } } -/// Derive the `SendRecv` trait, possibly generating an 'Incoming' version of the struct this +/// Derive the `Outgoing` trait, possibly generating an 'Incoming' version of the struct this /// derive macro is used on. Specifically, if no `#[wrap_incoming]` attribute is used on any of the /// fields of the struct, this simple implementation will be generated: /// /// ```ignore -/// impl SendRecv for MyType { +/// impl Outgoing for MyType { /// type Incoming = Self; /// } /// ``` @@ -51,7 +51,7 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { /// is generated, with all of the fields with `#[wrap_incoming]` replaced: /// /// ```ignore -/// #[derive(SendRecv)] +/// #[derive(Outgoing)] /// struct MyType { /// pub foo: Foo, /// #[wrap_incoming] @@ -73,8 +73,8 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { /// pub ys: Vec>, /// } /// ``` -#[proc_macro_derive(SendRecv, attributes(wrap_incoming, incoming_no_deserialize))] +#[proc_macro_derive(Outgoing, attributes(wrap_incoming, incoming_no_deserialize))] pub fn derive_send_recv(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - expand_send_recv(input).unwrap_or_else(|err| err.to_compile_error()).into() + expand_derive_outgoing(input).unwrap_or_else(|err| err.to_compile_error()).into() } diff --git a/src/lib.rs b/src/lib.rs index b81410c3..94a29b54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,16 +200,16 @@ use serde_urlencoded; /// /// ## Fallible deserialization /// -/// All request and response types also derive `ruma_api::SendRecv`. As such, to allow fallible +/// All request and response types also derive `ruma_api::Outgoing`. As such, to allow fallible /// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the -/// documentation for [`SendRecv`][]. +/// documentation for [`Outgoing`][]. /// -/// [`SendRecv`]: derive.SendRecv.html +/// [`Outgoing`]: derive.Outgoing.html #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; #[cfg(feature = "with-ruma-api-macros")] -pub use ruma_api_macros::SendRecv; +pub use ruma_api_macros::Outgoing; #[cfg(feature = "with-ruma-api-macros")] #[doc(hidden)] @@ -224,11 +224,13 @@ pub mod exports { pub use url; } -/// A type that can be sent as well as received. Types that implement this trait have a -/// corresponding 'Incoming' type, which is either just `Self`, or another type that has the same -/// fields with some types exchanged by ones that allow fallible deserialization, e.g. `EventResult` -/// from ruma_events. -pub trait SendRecv { +/// A type that can be sent to another party that understands the matrix protocol. If any of the +/// fields of `Self` don't implement serde's `Deserialize`, you can derive this trait to generate a +/// corresponding 'Incoming' type that supports deserialization. This is useful for things like +/// ruma_events' `EventResult` type. For more details, see the [derive macro's documentation][doc]. +/// +/// [doc]: derive.Outgoing.html +pub trait Outgoing { /// The 'Incoming' variant of `Self`. type Incoming; } @@ -236,13 +238,13 @@ pub trait SendRecv { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: SendRecv + TryInto>, Error = Error> +pub trait Endpoint: Outgoing + TryInto>, Error = Error> where - ::Incoming: TryFrom>, Error = Error>, - ::Incoming: TryFrom>, Error = Error>, + ::Incoming: TryFrom>, Error = Error>, + ::Incoming: TryFrom>, Error = Error>, { /// Data returned in a successful response from the endpoint. - type Response: SendRecv + TryInto>, Error = Error>; + type Response: Outgoing + TryInto>, Error = Error>; /// Metadata about the endpoint. const METADATA: Metadata; @@ -377,7 +379,7 @@ mod tests { use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; - use crate::{Endpoint, Error, Metadata, SendRecv}; + use crate::{Endpoint, Error, Metadata, Outgoing}; /// A request to create a new room alias. #[derive(Debug)] @@ -386,7 +388,7 @@ mod tests { pub room_alias: RoomAliasId, // path } - impl SendRecv for Request { + impl Outgoing for Request { type Incoming = Self; } @@ -453,7 +455,7 @@ mod tests { #[derive(Clone, Copy, Debug)] pub struct Response; - impl SendRecv for Response { + impl Outgoing for Response { type Incoming = Self; } From 6f7eb126f6763943c3fb8ce05d9b37ddeca9d480 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 25 Nov 2019 23:36:04 +0100 Subject: [PATCH 225/295] Improve tests --- tests/ruma_api_macros.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index d43c9269..e18c33ff 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,6 +1,7 @@ pub mod some_endpoint { - use ruma_api::ruma_api; - use ruma_events::{tag::TagEventContent, EventResult}; + use ruma_api::{ruma_api, Outgoing}; + use ruma_events::{collections::all, sticker::StickerEvent, tag::TagEvent, EventResult}; + use serde::Serialize; ruma_api! { metadata { @@ -42,11 +43,27 @@ pub mod some_endpoint { #[serde(skip_serializing_if = "Option::is_none")] pub optional_flag: Option, - /// The user's tags for the room. + // This is how you usually use `#[wrap_incoming]` with event types #[wrap_incoming(with EventResult)] - pub tags: TagEventContent, + pub event: TagEvent, + + // Same for lists of events + #[wrap_incoming(all::RoomEvent with EventResult)] + pub list_of_events: Vec, + + // This is how `#[wrap_incoming]` is used with nested `EventResult`s + #[wrap_incoming] + pub object: ObjectContainingEvents, } } + + #[derive(Clone, Debug, Serialize, Outgoing)] + pub struct ObjectContainingEvents { + #[wrap_incoming(TagEvent with EventResult)] + pub event_list_1: Vec, + #[wrap_incoming(StickerEvent with EventResult)] + pub event_list_2: Vec, + } } pub mod newtype_body_endpoint { From 7d340942a0c77ab4c59575273bfa555642a6b5e2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 26 Nov 2019 22:36:58 +0100 Subject: [PATCH 226/295] Error when derive(Outgoing) is used on a type with generics --- ruma-api-macros/src/derive_outgoing.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs index 8d8e866b..aec49740 100644 --- a/ruma-api-macros/src/derive_outgoing.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -12,6 +12,13 @@ mod wrap_incoming; use wrap_incoming::Meta; pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { + if !input.generics.params.is_empty() { + return Err(syn::Error::new_spanned( + input.generics, + "derive(Outgoing) doesn't currently support types with generics!", + )); + } + let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) { TokenStream::new() } else { From 9fa1305e6520e9a2aa526afbbb5dba97095f5ee4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 27 Nov 2019 19:27:47 +0100 Subject: [PATCH 227/295] Incorporate feedback by lu-fennell, Nauxuron --- ruma-api-macros/src/api/request.rs | 1 + ruma-api-macros/src/derive_outgoing.rs | 6 +++--- ruma-api-macros/src/lib.rs | 4 +++- src/lib.rs | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index a6efc456..58e8363e 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -75,6 +75,7 @@ impl Request { 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()) diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs index aec49740..d1c051a7 100644 --- a/ruma-api-macros/src/derive_outgoing.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -32,7 +32,7 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { Data::Struct(s) => match s.fields { Fields::Named(fs) => fs.named.into_pairs().map(Pair::into_value).collect(), Fields::Unnamed(fs) => fs.unnamed.into_pairs().map(Pair::into_value).collect(), - Fields::Unit => return Ok(impl_send_recv_incoming_self(input.ident)), + Fields::Unit => return Ok(impl_outgoing_with_incoming_self(input.ident)), }, }; @@ -68,7 +68,7 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { } if !any_attribute { - return Ok(impl_send_recv_incoming_self(input.ident)); + return Ok(impl_outgoing_with_incoming_self(input.ident)); } let vis = input.vis; @@ -104,7 +104,7 @@ fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool { false } -fn impl_send_recv_incoming_self(ident: Ident) -> TokenStream { +fn impl_outgoing_with_incoming_self(ident: Ident) -> TokenStream { quote! { impl ruma_api::Outgoing for #ident { type Incoming = Self; diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index d2024306..868abb84 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -73,8 +73,10 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { /// pub ys: Vec>, /// } /// ``` +// TODO: Make it clear that `#[wrap_incoming]` and `#[wrap_incoming(Type)]` without the "with" part +// are (only) useful for fallible deserialization of nested structures. #[proc_macro_derive(Outgoing, attributes(wrap_incoming, incoming_no_deserialize))] -pub fn derive_send_recv(input: TokenStream) -> TokenStream { +pub fn derive_outgoing(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand_derive_outgoing(input).unwrap_or_else(|err| err.to_compile_error()).into() } diff --git a/src/lib.rs b/src/lib.rs index 94a29b54..0bc3559a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,11 +200,12 @@ use serde_urlencoded; /// /// ## Fallible deserialization /// -/// All request and response types also derive `ruma_api::Outgoing`. As such, to allow fallible +/// All request and response types also derive [`Outgoing`][]. As such, to allow fallible /// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the /// documentation for [`Outgoing`][]. /// /// [`Outgoing`]: derive.Outgoing.html +// TODO: Explain the concept of fallible deserialization before jumping to `ruma_api::Outgoing` #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; @@ -230,6 +231,7 @@ pub mod exports { /// ruma_events' `EventResult` type. For more details, see the [derive macro's documentation][doc]. /// /// [doc]: derive.Outgoing.html +// TODO: Better explain how this trait relates to serde's traits pub trait Outgoing { /// The 'Incoming' variant of `Self`. type Incoming; From 3696516679459795a3d90f334df43f323ad56905 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 28 Nov 2019 21:43:50 +0100 Subject: [PATCH 228/295] Fix support for tuple structs in Outgoing derive --- ruma-api-macros/src/derive_outgoing.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs index d1c051a7..f10e92a2 100644 --- a/ruma-api-macros/src/derive_outgoing.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -11,6 +11,11 @@ mod wrap_incoming; use wrap_incoming::Meta; +enum StructKind { + Struct, + Tuple, +} + pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { if !input.generics.params.is_empty() { return Err(syn::Error::new_spanned( @@ -25,13 +30,17 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { quote!(#[derive(ruma_api::exports::serde::Deserialize)]) }; - let mut fields: Vec<_> = match input.data { + let (mut fields, struct_kind): (Vec<_>, _) = match input.data { Data::Enum(_) | Data::Union(_) => { panic!("#[derive(Outgoing)] is only supported for structs") } Data::Struct(s) => match s.fields { - Fields::Named(fs) => fs.named.into_pairs().map(Pair::into_value).collect(), - Fields::Unnamed(fs) => fs.unnamed.into_pairs().map(Pair::into_value).collect(), + Fields::Named(fs) => { + (fs.named.into_pairs().map(Pair::into_value).collect(), StructKind::Struct) + } + Fields::Unnamed(fs) => { + (fs.unnamed.into_pairs().map(Pair::into_value).collect(), StructKind::Tuple) + } Fields::Unit => return Ok(impl_outgoing_with_incoming_self(input.ident)), }, }; @@ -76,12 +85,15 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { let original_ident = input.ident; let incoming_ident = Ident::new(&format!("Incoming{}", original_ident), Span::call_site()); + let struct_def = match struct_kind { + StructKind::Struct => quote! { { #(#fields,)* } }, + StructKind::Tuple => quote! { ( #(#fields,)* ); }, + }; + Ok(quote! { #[doc = #doc] #derive_deserialize - #vis struct #incoming_ident { - #(#fields,)* - } + #vis struct #incoming_ident #struct_def impl ruma_api::Outgoing for #original_ident { type Incoming = #incoming_ident; From 81207890b6e1203b628ae6a2b6e53f27eba83798 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 28 Nov 2019 21:52:48 +0100 Subject: [PATCH 229/295] Simplify ruma-api-macros codegen --- ruma-api-macros/src/api.rs | 113 +++++++--------------------- ruma-api-macros/src/api/request.rs | 76 ++++++------------- ruma-api-macros/src/api/response.rs | 81 ++++++-------------- 3 files changed, 73 insertions(+), 197 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 627ea442..49196e73 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -108,7 +108,7 @@ impl ToTokens for Api { TokenStream::new() }; - let (set_request_path, parse_request_path) = if self.request.has_path_fields() { + let (url_set_path, parse_request_path) = if self.request.has_path_fields() { let path_str = path.value(); assert!(path_str.starts_with('/'), "path needs to start with '/'"); @@ -182,7 +182,7 @@ impl ToTokens for Api { (set_tokens, parse_tokens) }; - let set_request_query = if let Some(field) = self.request.query_map_field() { + let url_set_querystring = if let Some(field) = self.request.query_map_field() { let field_name = field.ident.as_ref().expect("expected field to have identifier"); let field_type = &field.ty; @@ -252,10 +252,8 @@ impl ToTokens for Api { quote! { #field_name: request_query } - } else if self.request.has_query_fields() { - self.request.request_init_query_fields() } else { - TokenStream::new() + self.request.request_init_query_fields() }; let add_headers_to_request = if self.request.has_header_fields() { @@ -282,76 +280,22 @@ impl ToTokens for Api { TokenStream::new() }; - let create_http_request = if let Some(field) = self.request.newtype_body_field() { + let request_body_initializers = if let Some(field) = self.request.newtype_body_field() { let field_name = field.ident.as_ref().expect("expected field to have an identifier"); - - quote! { - let request_body = RequestBody(request.#field_name); - - let mut http_request = ruma_api::exports::http::Request::new( - ruma_api::exports::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 - }; - - let mut http_request = ruma_api::exports::http::Request::new( - ruma_api::exports::serde_json::to_vec(&request_body)?, - ); - } + quote! { (request.#field_name) } } else { - quote! { - let mut http_request = ruma_api::exports::http::Request::new(Vec::new()); - } - }; - - let extract_request_body = if self.request.newtype_body_field().is_some() { - quote! { - let request_body = - ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; - } - } else if self.request.has_body_fields() { - quote! { - let request_body: ::Incoming = - ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; - } - } else { - TokenStream::new() + let initializers = self.request.request_body_init_fields(); + quote! { { #initializers } } }; 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, + #field_name: request_body.0, } - } else if self.request.has_body_fields() { + } else { self.request.request_init_body_fields() - } else { - TokenStream::new() - }; - - let response_body_type_annotation = if self.response.has_body_fields() { - quote!(: ::Incoming) - } else { - TokenStream::new() - }; - - let try_deserialize_response_body = if self.response.has_body() { - quote! { - ruma_api::exports::serde_json::from_slice( - http_response.into_body().as_slice(), - )? - } - } else { - quote! { - () - } }; let extract_response_headers = if self.response.has_header_fields() { @@ -362,24 +306,11 @@ impl ToTokens for Api { TokenStream::new() }; - let response_init_fields = if self.response.has_fields() { - self.response.init_fields() - } else { - TokenStream::new() - }; + let response_init_fields = self.response.init_fields(); let serialize_response_headers = self.response.apply_header_fields(); - let try_serialize_response_body = if self.response.has_body() { - let body = self.response.to_body(); - quote! { - ruma_api::exports::serde_json::to_vec(&#body)? - } - } else { - quote! { - "{}".as_bytes().to_vec() - } - }; + let body = self.response.to_body(); let request_doc = format!( "Data for a request to the `{}` API endpoint.\n\n{}", @@ -406,7 +337,9 @@ impl ToTokens for Api { #extract_request_path #extract_request_query #extract_request_headers - #extract_request_body + + let request_body: ::Incoming = + ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; Ok(Self { #parse_request_path @@ -431,10 +364,14 @@ impl ToTokens for Api { ruma_api::exports::url::Url::parse("http://invalid-host-please-change/") .unwrap(); - { #set_request_path } - { #set_request_query } + { #url_set_path } + { #url_set_querystring } - #create_http_request + let request_body = RequestBody #request_body_initializers; + + let mut http_request = ruma_api::exports::http::Request::new( + ruma_api::exports::serde_json::to_vec(&request_body)?, + ); *http_request.method_mut() = ruma_api::exports::http::Method::#method; *http_request.uri_mut() = url.into_string().parse().unwrap(); @@ -456,7 +393,7 @@ impl ToTokens for Api { let response = ruma_api::exports::http::Response::builder() .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") #serialize_response_headers - .body(#try_serialize_response_body) + .body(ruma_api::exports::serde_json::to_vec(&#body)?) .unwrap(); Ok(response) } @@ -472,8 +409,10 @@ impl ToTokens for Api { if http_response.status().is_success() { #extract_response_headers - let response_body #response_body_type_annotation = - #try_deserialize_response_body; + let response_body: ::Incoming = + ruma_api::exports::serde_json::from_slice( + http_response.into_body().as_slice(), + )?; Ok(Self { #response_init_fields diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 58e8363e..ad741f89 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -168,9 +168,7 @@ impl Request { }) }); - quote! { - #(#fields,)* - } + quote! { #(#fields,)* } } } @@ -293,46 +291,24 @@ impl TryFrom for Request { impl ToTokens for Request { fn to_tokens(&self, tokens: &mut TokenStream) { - let request_struct_header = quote! { - #[derive(Debug, Clone, ruma_api::Outgoing)] - #[incoming_no_deserialize] - pub struct Request - }; - - let request_struct_body = if self.fields.is_empty() { + let request_def = if self.fields.is_empty() { quote!(;) } else { let fields = self.fields.iter().map(|request_field| strip_serde_attrs(request_field.field())); - - quote! { - { - #(#fields),* - } - } + quote! { { #(#fields),* } } }; - let request_body_struct = + let (derive_deserialize, request_body_def) = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { - let field = body_field.field(); - let ty = &field.ty; - let span = field.span(); + let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let derive_deserialize = if body_field.has_wrap_incoming_attr() { TokenStream::new() } else { quote!(ruma_api::exports::serde::Deserialize) }; - quote_spanned! {span=> - /// Data in the request body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct RequestBody(#ty); - } + (derive_deserialize, quote! { (#field); }) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { @@ -342,20 +318,9 @@ impl ToTokens for Request { }; let fields = fields.map(RequestField::field); - quote! { - /// Data in the request body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct RequestBody { - #(#fields),* - } - } + (derive_deserialize, quote! { { #(#fields),* } }) } else { - TokenStream::new() + (quote!(ruma_api::exports::serde::Deserialize), quote!(;)) }; let request_path_struct = if self.has_path_fields() { @@ -376,18 +341,17 @@ impl ToTokens for Request { TokenStream::new() }; - let request_query_struct = if let Some(field) = self.query_map_field() { - let ty = &field.ty; - let span = field.span(); + let request_query_struct = if let Some(f) = self.query_map_field() { + let field = Field { ident: None, colon_token: None, ..f.clone() }; - quote_spanned! {span=> + quote! { /// Data in the request's query string. #[derive( Debug, ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize, )] - struct RequestQuery(#ty); + struct RequestQuery(#field); } } else if self.has_query_fields() { let fields = self.fields.iter().filter_map(RequestField::as_query_field); @@ -408,9 +372,19 @@ impl ToTokens for Request { }; let request = quote! { - #request_struct_header - #request_struct_body - #request_body_struct + #[derive(Debug, Clone, ruma_api::Outgoing)] + #[incoming_no_deserialize] + pub struct Request #request_def + + /// Data in the request body. + #[derive( + Debug, + ruma_api::Outgoing, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct RequestBody #request_body_def + #request_path_struct #request_query_struct }; diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 29f2b4ae..5f5f4016 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -23,21 +23,11 @@ impl Response { 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()) - } - /// Whether any field has a #[wrap_incoming] attribute. pub fn uses_wrap_incoming(&self) -> bool { self.fields.iter().any(|f| f.has_wrap_incoming_attr()) @@ -74,7 +64,7 @@ impl Response { let span = field.span(); quote_spanned! {span=> - #field_name: response_body + #field_name: response_body.0 } } }); @@ -100,9 +90,7 @@ impl Response { } }); - quote! { - #(#header_calls)* - } + quote! { #(#header_calls)* } } /// Produces code to initialize the struct that will be used to create the response body. @@ -127,9 +115,7 @@ impl Response { }); quote! { - ResponseBody { - #(#fields),* - } + ResponseBody { #(#fields),* } } } } @@ -231,46 +217,25 @@ impl TryFrom for Response { impl ToTokens for Response { fn to_tokens(&self, tokens: &mut TokenStream) { - let response_struct_header = quote! { - #[derive(Debug, Clone, ruma_api::Outgoing)] - #[incoming_no_deserialize] - pub struct Response - }; - - let response_struct_body = if self.fields.is_empty() { + let response_def = if self.fields.is_empty() { quote!(;) } else { let fields = self.fields.iter().map(|response_field| strip_serde_attrs(response_field.field())); - quote! { - { - #(#fields),* - } - } + quote! { { #(#fields),* } } }; - let response_body_struct = + let (derive_deserialize, response_body_def) = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { - let field = body_field.field(); - let ty = &field.ty; - let span = field.span(); + let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let derive_deserialize = if body_field.has_wrap_incoming_attr() { TokenStream::new() } else { quote!(ruma_api::exports::serde::Deserialize) }; - quote_spanned! {span=> - /// Data in the response body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct ResponseBody(#ty); - } + (derive_deserialize, quote! { (#field); }) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { @@ -280,26 +245,24 @@ impl ToTokens for Response { }; let fields = fields.map(ResponseField::field); - quote! { - /// Data in the response body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct ResponseBody { - #(#fields),* - } - } + (derive_deserialize, quote!({ #(#fields),* })) } else { - TokenStream::new() + (quote!(ruma_api::exports::serde::Deserialize), quote!(;)) }; let response = quote! { - #response_struct_header - #response_struct_body - #response_body_struct + #[derive(Debug, Clone, ruma_api::Outgoing)] + #[incoming_no_deserialize] + pub struct Response #response_def + + /// Data in the response body. + #[derive( + Debug, + ruma_api::Outgoing, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct ResponseBody #response_body_def }; response.to_tokens(tokens); From f7c6bd7d4edd00c870e40b8cd53fdc4969a0c0f0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 28 Nov 2019 21:58:07 +0100 Subject: [PATCH 230/295] Update deps, make alpha releases --- Cargo.toml | 10 +++++----- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b77b7745..be55c1df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,16 +9,16 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.11.2" +version = "0.12.0-alpha.1" edition = "2018" [dependencies] -http = "0.1.19" +http = "0.1.20" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "0.9.0-alpha.1", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" -serde = { version = "1.0.102", features = ["derive"], optional = true } -serde_json = "1.0.41" +serde = { version = "1.0.103", features = ["derive"], optional = true } +serde_json = "1.0.42" serde_urlencoded = "0.6.1" url = { version = "2.1.0", optional = true } diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 3cfddf13..5bbb68c3 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.8.2" +version = "0.9.0-alpha.1" edition = "2018" [dependencies] From e8462be7f21b405be76dba634f1158bc211255af Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 3 Dec 2019 12:09:00 +0100 Subject: [PATCH 231/295] Use exact version requirement for ruma-api-macros dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index be55c1df..1d9f7334 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" [dependencies] http = "0.1.20" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "0.9.0-alpha.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.9.0-alpha.1", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" serde = { version = "1.0.103", features = ["derive"], optional = true } serde_json = "1.0.42" From 634eb5f13eca3e25ca3ecb32c2ceb5a1080718d0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 3 Dec 2019 12:14:43 +0100 Subject: [PATCH 232/295] Update http to 0.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d9f7334..282700a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ version = "0.12.0-alpha.1" edition = "2018" [dependencies] -http = "0.1.20" +http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.9.0-alpha.1", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" From d68c59974d32b50bb5e9f2309dee54899a6d3909 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 3 Dec 2019 22:11:17 +0100 Subject: [PATCH 233/295] Bump MSRV --- .travis.yml | 9 +++------ README.md | 2 +- ruma-api-macros/src/lib.rs | 2 -- src/lib.rs | 2 -- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4579af1e..47bfc526 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: "rust" cache: "cargo" rust: - - 1.36.0 + - 1.39.0 - stable - beta - nightly @@ -12,10 +12,7 @@ jobs: before_script: - rustup component add rustfmt - - | - if [ "$TRAVIS_RUST_VERSION" != "1.36.0" ]; then - rustup component add clippy || true - fi + - rustup component add clippy || true - | if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then cargo install --force cargo-audit @@ -28,7 +25,7 @@ script: fi - cargo fmt --all -- --check - | - if [ "$TRAVIS_RUST_VERSION" != "1.36.0" ] && ( rustup component list | grep -q clippy ); then + if ( rustup component list | grep -q clippy ); then cargo clippy --all --all-targets --all-features -- -D warnings fi - cargo build --all --verbose diff --git a/README.md b/README.md index 9496681a..a99907dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ These types can be shared by client and server code for all Matrix APIs. ## Minimum Rust version -ruma-api requires Rust 1.36.0 or later. +ruma-api requires Rust 1.39.0 or later. ## Documentation diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 868abb84..e580e286 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -7,8 +7,6 @@ #![deny(missing_copy_implementations, missing_debug_implementations)] #![allow(clippy::cognitive_complexity)] -// Since we support Rust 1.36.0, we can't apply this suggestion yet -#![allow(clippy::use_self)] #![recursion_limit = "256"] extern crate proc_macro; diff --git a/src/lib.rs b/src/lib.rs index 0bc3559a..a669e55c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,6 @@ #![warn(rust_2018_idioms)] #![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)] -// Since we support Rust 1.36.0, we can't apply this suggestion yet -#![allow(clippy::use_self)] use std::{ convert::{TryFrom, TryInto}, From 08a3d820be66342234ea2806ff6c83e6f9588770 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 12 Dec 2019 20:48:40 +0100 Subject: [PATCH 234/295] Release ruma-api-macros 0.9.0, ruma-api 0.12.0 --- Cargo.toml | 4 ++-- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 282700a7..2160a053 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.12.0-alpha.1" +version = "0.12.0" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.9.0-alpha.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.9.0", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" serde = { version = "1.0.103", features = ["derive"], optional = true } serde_json = "1.0.42" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 5bbb68c3..4ebffbab 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.9.0-alpha.1" +version = "0.9.0" edition = "2018" [dependencies] From 9063f4c5264af34e1d371f5e4a0bf3dd461fcd6c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 13 Dec 2019 12:08:35 +0100 Subject: [PATCH 235/295] Update change logs --- CHANGELOG.md | 6 +++++- ruma-api-macros/CHANGELOG.md | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6229d21..fbb6ed8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # [unreleased] +# 0.12.0 + Breaking changes: -* Our Minimum Supported Rust Version is now 1.36.0 +* Our Minimum Supported Rust Version is now 1.39.0 +* Support for the server-side use case has been restored. For details, see the documentation for + `ruma_api!`, the new `Outgoing` trait and its derive macro # 0.11.2 diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 67e46644..1c4780a6 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,15 @@ # [unreleased] +# 0.9.0 + +Breaking changes: + +* Updated code generation to match the changes in ruma-api 0.12.0 + +New features: + +* Added a derive macro for the new `Outgoing` trait from ruma-api + # 0.8.2 Bug fixes: From af7333203a1c1efbd5d82930de764bc4e0505a28 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 13 Dec 2019 20:30:12 +0100 Subject: [PATCH 236/295] Use quote::format_ident! --- ruma-api-macros/src/derive_outgoing.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs index f10e92a2..a2411f43 100644 --- a/ruma-api-macros/src/derive_outgoing.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -1,7 +1,7 @@ use std::mem; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse_quote, punctuated::Pair, spanned::Spanned, Attribute, Data, DeriveInput, Fields, GenericArgument, Path, PathArguments, Type, TypePath, @@ -83,7 +83,7 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { let vis = input.vis; let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = input.ident); let original_ident = input.ident; - let incoming_ident = Ident::new(&format!("Incoming{}", original_ident), Span::call_site()); + let incoming_ident = format_ident!("Incoming{}", original_ident, span = Span::call_site()); let struct_def = match struct_kind { StructKind::Struct => quote! { { #(#fields,)* } }, @@ -131,7 +131,7 @@ fn wrap_ty(ty: &mut Type, path: Option) -> syn::Result<()> { match ty { Type::Path(TypePath { path, .. }) => { let ty_ident = &mut path.segments.last_mut().unwrap().ident; - let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site()); + let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site()); *ty_ident = parse_quote!(#ident); } _ => return Err(syn::Error::new_spanned(ty, "Can't wrap this type")), @@ -200,7 +200,7 @@ fn wrap_generic_arg_impl( *ty = parse_quote!(#wrapper_type<#ty>); } else if let Type::Path(TypePath { path, .. }) = ty { let ty_ident = &mut path.segments.last_mut().unwrap().ident; - let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site()); + let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site()); *ty_ident = parse_quote!(#ident); } else { return Err(syn::Error::new_spanned(ty, "Can't wrap this type")); From b6394a32b76355ba9ca0dabe2969dc582c35d672 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 6 Jan 2020 12:20:48 +0100 Subject: [PATCH 237/295] ruma_client_api: Simplify Response::init_fields --- ruma-api-macros/src/api/response.rs | 48 +++++++++++++---------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 5f5f4016..7753594a 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -35,36 +35,30 @@ impl Response { /// Produces code for a response struct initializer. pub fn init_fields(&self) -> TokenStream { - let fields = self.fields.iter().map(|response_field| match response_field { - ResponseField::Body(field) => { - let field_name = - field.ident.as_ref().expect("expected field to have an identifier"); - let span = field.span(); + let fields = self.fields.iter().map(|response_field| { + let field = response_field.field(); + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); - quote_spanned! {span=> - #field_name: response_body.#field_name + match response_field { + ResponseField::Body(_) => { + quote_spanned! {span=> + #field_name: response_body.#field_name + } } - } - ResponseField::Header(field, header_name) => { - let field_name = - field.ident.as_ref().expect("expected field to have an identifier"); - let span = field.span(); - - quote_spanned! {span=> - #field_name: headers.remove(ruma_api::exports::http::header::#header_name) - .expect("response missing expected header") - .to_str() - .expect("failed to convert HeaderValue to str") - .to_owned() + ResponseField::Header(_, header_name) => { + quote_spanned! {span=> + #field_name: headers.remove(ruma_api::exports::http::header::#header_name) + .expect("response missing expected header") + .to_str() + .expect("failed to convert HeaderValue to str") + .to_owned() + } } - } - ResponseField::NewtypeBody(field) => { - let field_name = - field.ident.as_ref().expect("expected field to have an identifier"); - let span = field.span(); - - quote_spanned! {span=> - #field_name: response_body.0 + ResponseField::NewtypeBody(_) => { + quote_spanned! {span=> + #field_name: response_body.0 + } } } }); From 86aa04bc595c1d104fcd670435122947aced474d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 6 Jan 2020 23:26:04 +0100 Subject: [PATCH 238/295] Implement #[ruma_api(raw_body)] --- ruma-api-macros/src/api.rs | 67 +++++++++++----- ruma-api-macros/src/api/request.rs | 57 ++++++++++---- ruma-api-macros/src/api/response.rs | 114 +++++++++++++++++++--------- src/lib.rs | 6 +- tests/ruma_api_macros.rs | 34 ++++++++- 5 files changed, 204 insertions(+), 74 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 49196e73..80eb34c1 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -274,26 +274,55 @@ impl ToTokens for Api { TokenStream::new() }; + let extract_request_body = + if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { + quote! { + let request_body: ::Incoming = + ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; + } + } else { + TokenStream::new() + }; + let parse_request_headers = if self.request.has_header_fields() { self.request.parse_headers_from_request() } else { TokenStream::new() }; - let request_body_initializers = if let Some(field) = self.request.newtype_body_field() { + let request_body = if let Some(field) = self.request.newtype_raw_body_field() { let field_name = field.ident.as_ref().expect("expected field to have an identifier"); - quote! { (request.#field_name) } + quote!(request.#field_name) + } else if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { + let request_body_initializers = if let Some(field) = self.request.newtype_body_field() { + let field_name = + field.ident.as_ref().expect("expected field to have an identifier"); + quote! { (request.#field_name) } + } else { + let initializers = self.request.request_body_init_fields(); + quote! { { #initializers } } + }; + + quote! { + { + let request_body = RequestBody #request_body_initializers; + ruma_api::exports::serde_json::to_vec(&request_body)? + } + } } else { - let initializers = self.request.request_body_init_fields(); - quote! { { #initializers } } + quote!(Vec::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.0, } + } else if let Some(field) = self.request.newtype_raw_body_field() { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + quote! { + #field_name: request.into_body(), + } } else { self.request.request_init_body_fields() }; @@ -306,6 +335,16 @@ impl ToTokens for Api { TokenStream::new() }; + let typed_response_body_decl = + if self.response.has_body_fields() || self.response.newtype_body_field().is_some() { + quote! { + let response_body: ::Incoming = + ruma_api::exports::serde_json::from_slice(response_body.as_slice())?; + } + } else { + TokenStream::new() + }; + let response_init_fields = self.response.init_fields(); let serialize_response_headers = self.response.apply_header_fields(); @@ -337,9 +376,7 @@ impl ToTokens for Api { #extract_request_path #extract_request_query #extract_request_headers - - let request_body: ::Incoming = - ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; + #extract_request_body Ok(Self { #parse_request_path @@ -367,11 +404,7 @@ impl ToTokens for Api { { #url_set_path } { #url_set_querystring } - let request_body = RequestBody #request_body_initializers; - - let mut http_request = ruma_api::exports::http::Request::new( - ruma_api::exports::serde_json::to_vec(&request_body)?, - ); + let mut http_request = ruma_api::exports::http::Request::new(#request_body); *http_request.method_mut() = ruma_api::exports::http::Method::#method; *http_request.uri_mut() = url.into_string().parse().unwrap(); @@ -393,7 +426,7 @@ impl ToTokens for Api { let response = ruma_api::exports::http::Response::builder() .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") #serialize_response_headers - .body(ruma_api::exports::serde_json::to_vec(&#body)?) + .body(#body) .unwrap(); Ok(response) } @@ -409,10 +442,8 @@ impl ToTokens for Api { if http_response.status().is_success() { #extract_response_headers - let response_body: ::Incoming = - ruma_api::exports::serde_json::from_slice( - http_response.into_body().as_slice(), - )?; + let response_body = http_response.into_body(); + #typed_response_body_decl Ok(Self { #response_init_fields diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index ad741f89..d4c02d06 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -118,6 +118,11 @@ impl Request { self.fields.iter().find_map(RequestField::as_newtype_body_field) } + /// Returns the body field. + pub fn newtype_raw_body_field(&self) -> Option<&Field> { + self.fields.iter().find_map(RequestField::as_newtype_raw_body_field) + } + /// Returns the query map field. pub fn query_map_field(&self) -> Option<&Field> { self.fields.iter().find_map(RequestField::as_query_map_field) @@ -205,7 +210,7 @@ impl TryFrom for Request { field_kind = Some(match meta { Meta::Word(ident) => { match &ident.to_string()[..] { - "body" => { + s @ "body" | s @ "raw_body" => { if let Some(f) = &newtype_body_field { let mut error = syn::Error::new_spanned( field, @@ -219,7 +224,11 @@ impl TryFrom for Request { } newtype_body_field = Some(field.clone()); - RequestFieldKind::NewtypeBody + match s { + "body" => RequestFieldKind::NewtypeBody, + "raw_body" => RequestFieldKind::NewtypeRawBody, + _ => unreachable!(), + } } "path" => RequestFieldKind::Path, "query" => RequestFieldKind::Query, @@ -299,7 +308,7 @@ impl ToTokens for Request { quote! { { #(#fields),* } } }; - let (derive_deserialize, request_body_def) = + let request_body_struct = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let derive_deserialize = if body_field.has_wrap_incoming_attr() { @@ -308,7 +317,7 @@ impl ToTokens for Request { quote!(ruma_api::exports::serde::Deserialize) }; - (derive_deserialize, quote! { (#field); }) + Some((derive_deserialize, quote! { (#field); })) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { @@ -318,10 +327,22 @@ impl ToTokens for Request { }; let fields = fields.map(RequestField::field); - (derive_deserialize, quote! { { #(#fields),* } }) + Some((derive_deserialize, quote! { { #(#fields),* } })) } else { - (quote!(ruma_api::exports::serde::Deserialize), quote!(;)) - }; + None + } + .map(|(derive_deserialize, def)| { + quote! { + /// Data in the request body. + #[derive( + Debug, + ruma_api::Outgoing, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct RequestBody #def + } + }); let request_path_struct = if self.has_path_fields() { let fields = self.fields.iter().filter_map(RequestField::as_path_field); @@ -376,15 +397,7 @@ impl ToTokens for Request { #[incoming_no_deserialize] pub struct Request #request_def - /// Data in the request body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct RequestBody #request_body_def - + #request_body_struct #request_path_struct #request_query_struct }; @@ -401,6 +414,8 @@ pub enum RequestField { Header(Field, Ident), /// A specific data type in the body of the request. NewtypeBody(Field), + /// Arbitrary bytes in the body of the request. + NewtypeRawBody(Field), /// Data that appears in the URL path. Path(Field), /// Data that appears in the query string. @@ -418,6 +433,7 @@ impl RequestField { RequestField::Header(field, header.expect("missing header name")) } RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), + RequestFieldKind::NewtypeRawBody => RequestField::NewtypeRawBody(field), RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), RequestFieldKind::QueryMap => RequestField::QueryMap(field), @@ -430,6 +446,7 @@ impl RequestField { RequestField::Body(..) => RequestFieldKind::Body, RequestField::Header(..) => RequestFieldKind::Header, RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, + RequestField::NewtypeRawBody(..) => RequestFieldKind::NewtypeRawBody, RequestField::Path(..) => RequestFieldKind::Path, RequestField::Query(..) => RequestFieldKind::Query, RequestField::QueryMap(..) => RequestFieldKind::QueryMap, @@ -471,6 +488,11 @@ impl RequestField { self.field_of_kind(RequestFieldKind::NewtypeBody) } + /// Return the contained field if this request field is a raw body kind. + fn as_newtype_raw_body_field(&self) -> Option<&Field> { + self.field_of_kind(RequestFieldKind::NewtypeRawBody) + } + /// Return the contained field if this request field is a path kind. fn as_path_field(&self) -> Option<&Field> { self.field_of_kind(RequestFieldKind::Path) @@ -492,6 +514,7 @@ impl RequestField { RequestField::Body(field) | RequestField::Header(field, _) | RequestField::NewtypeBody(field) + | RequestField::NewtypeRawBody(field) | RequestField::Path(field) | RequestField::Query(field) | RequestField::QueryMap(field) => field, @@ -525,6 +548,8 @@ enum RequestFieldKind { /// See the similarly named variant of `RequestField`. NewtypeBody, /// See the similarly named variant of `RequestField`. + NewtypeRawBody, + /// See the similarly named variant of `RequestField`. Path, /// See the similarly named variant of `RequestField`. Query, diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 7753594a..c289b2a9 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -60,6 +60,11 @@ impl Response { #field_name: response_body.0 } } + ResponseField::NewtypeRawBody(_) => { + quote_spanned! {span=> + #field_name: response_body + } + } } }); @@ -89,7 +94,17 @@ 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(field) = self.newtype_body_field() { + if let Some(field) = self.newtype_raw_body_field() { + let field_name = field.ident.as_ref().expect("expected field to have an identifier"); + let span = field.span(); + return quote_spanned!(span=> response.#field_name); + } + + if !self.has_body_fields() && self.newtype_body_field().is_none() { + return quote!(Vec::new()); + } + + let body = if let Some(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) @@ -111,13 +126,20 @@ impl Response { quote! { ResponseBody { #(#fields),* } } - } + }; + + quote!(ruma_api::exports::serde_json::to_vec(&#body)?) } /// Gets the newtype body field, if this response has one. pub fn newtype_body_field(&self) -> Option<&Field> { self.fields.iter().find_map(ResponseField::as_newtype_body_field) } + + /// Gets the newtype raw body field, if this response has one. + pub fn newtype_raw_body_field(&self) -> Option<&Field> { + self.fields.iter().find_map(ResponseField::as_newtype_raw_body_field) + } } impl TryFrom for Response { @@ -150,29 +172,34 @@ impl TryFrom for Response { } field_kind = Some(match meta { - Meta::Word(ident) => { - if ident != "body" { + Meta::Word(ident) => match &ident.to_string()[..] { + s @ "body" | s @ "raw_body" => { + if let Some(f) = &newtype_body_field { + let mut error = syn::Error::new_spanned( + field, + "There can only be one newtype body field", + ); + error.combine(syn::Error::new_spanned( + f, + "Previous newtype body field", + )); + return Err(error); + } + + newtype_body_field = Some(field.clone()); + match s { + "body" => ResponseFieldKind::NewtypeBody, + "raw_body" => ResponseFieldKind::NewtypeRawBody, + _ => unreachable!(), + } + } + _ => { return Err(syn::Error::new_spanned( ident, "Invalid #[ruma_api] argument with value, expected `body`", )); } - - if let Some(f) = &newtype_body_field { - let mut error = syn::Error::new_spanned( - field, - "There can only be one newtype body field", - ); - error.combine(syn::Error::new_spanned( - f, - "Previous newtype body field", - )); - return Err(error); - } - - newtype_body_field = Some(field.clone()); - ResponseFieldKind::NewtypeBody - } + }, Meta::NameValue(MetaNameValue { name, value }) => { if name != "header" { return Err(syn::Error::new_spanned( @@ -193,6 +220,7 @@ impl TryFrom for Response { ResponseField::Header(field, header.expect("missing header name")) } ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), + ResponseFieldKind::NewtypeRawBody => ResponseField::NewtypeRawBody(field), }) }) .collect::>>()?; @@ -220,7 +248,7 @@ impl ToTokens for Response { quote! { { #(#fields),* } } }; - let (derive_deserialize, response_body_def) = + let response_body_struct = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let derive_deserialize = if body_field.has_wrap_incoming_attr() { @@ -229,7 +257,7 @@ impl ToTokens for Response { quote!(ruma_api::exports::serde::Deserialize) }; - (derive_deserialize, quote! { (#field); }) + Some((derive_deserialize, quote! { (#field); })) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { @@ -239,24 +267,29 @@ impl ToTokens for Response { }; let fields = fields.map(ResponseField::field); - (derive_deserialize, quote!({ #(#fields),* })) + Some((derive_deserialize, quote!({ #(#fields),* }))) } else { - (quote!(ruma_api::exports::serde::Deserialize), quote!(;)) - }; + None + } + .map(|(derive_deserialize, def)| { + quote! { + /// Data in the response body. + #[derive( + Debug, + ruma_api::Outgoing, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct ResponseBody #def + } + }); let response = quote! { #[derive(Debug, Clone, ruma_api::Outgoing)] #[incoming_no_deserialize] pub struct Response #response_def - /// Data in the response body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct ResponseBody #response_body_def + #response_body_struct }; response.to_tokens(tokens); @@ -271,6 +304,8 @@ pub enum ResponseField { Header(Field, Ident), /// A specific data type in the body of the response. NewtypeBody(Field), + /// Arbitrary bytes in the body of the response. + NewtypeRawBody(Field), } impl ResponseField { @@ -279,7 +314,8 @@ impl ResponseField { match self { ResponseField::Body(field) | ResponseField::Header(field, _) - | ResponseField::NewtypeBody(field) => field, + | ResponseField::NewtypeBody(field) + | ResponseField::NewtypeRawBody(field) => field, } } @@ -317,6 +353,14 @@ impl ResponseField { } } + /// Return the contained field if this response field is a newtype raw body kind. + fn as_newtype_raw_body_field(&self) -> Option<&Field> { + match self { + ResponseField::NewtypeRawBody(field) => Some(field), + _ => None, + } + } + /// Whether or not the reponse field has a #[wrap_incoming] attribute. fn has_wrap_incoming_attr(&self) -> bool { self.field().attrs.iter().any(|attr| { @@ -333,4 +377,6 @@ enum ResponseFieldKind { Header, /// See the similarly named variant of `ResponseField`. NewtypeBody, + /// See the similarly named variant of `ResponseField`. + NewtypeRawBody, } diff --git a/src/lib.rs b/src/lib.rs index a669e55c..b387b826 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,11 +198,9 @@ use serde_urlencoded; /// /// ## Fallible deserialization /// -/// All request and response types also derive [`Outgoing`][]. As such, to allow fallible +/// All request and response types also derive [`Outgoing`][Outgoing]. As such, to allow fallible /// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the -/// documentation for [`Outgoing`][]. -/// -/// [`Outgoing`]: derive.Outgoing.html +/// documentation for [the derive macro](derive.Outgoing.html). // TODO: Explain the concept of fallible deserialization before jumping to `ruma_api::Outgoing` #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index e18c33ff..96d983f3 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -86,12 +86,42 @@ pub mod newtype_body_endpoint { request { #[ruma_api(body)] - pub file: Vec, + pub list_of_custom_things: Vec, } response { #[ruma_api(body)] - pub my_custom_type: MyCustomType, + pub my_custom_thing: MyCustomType, + } + } +} + +pub mod newtype_raw_body_endpoint { + use ruma_api_macros::ruma_api; + + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + pub struct MyCustomType { + pub foo: String, + } + + ruma_api! { + metadata { + description: "Does something.", + method: PUT, + name: "newtype_body_endpoint", + path: "/_matrix/some/newtype/body/endpoint", + rate_limited: false, + requires_authentication: false, + } + + request { + #[ruma_api(raw_body)] + pub file: Vec, + } + + response { + #[ruma_api(raw_body)] + pub file: Vec, } } } From 3b294baf07ee7d4432e76c574d130e9d4a55b3de Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Jan 2020 20:04:15 +0100 Subject: [PATCH 239/295] Update change logs, bump versions --- CHANGELOG.md | 6 ++++++ Cargo.toml | 4 ++-- ruma-api-macros/CHANGELOG.md | 8 ++++++++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb6ed8c..6f5c6451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.12.1 + +Improvements: + +* Update ruma-api-macros to 0.9.1 to support `#[ruma_api(raw_body)]` + # 0.12.0 Breaking changes: diff --git a/Cargo.toml b/Cargo.toml index 2160a053..84bae69a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.12.0" +version = "0.12.1" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.9.0", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.9.1", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.14.0" serde = { version = "1.0.103", features = ["derive"], optional = true } serde_json = "1.0.42" diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 1c4780a6..b1d65f13 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,13 @@ # [unreleased] +# 0.9.1 + +Improvements: + +* Add `#[ruma_api(raw_body)]` attribute to `ruma_api!`'s grammar + * This attribute is used to bypass (de)serialization for endpoints where the HTTP request / + response is arbitrary data rather than some JSON structure + # 0.9.0 Breaking changes: diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 4ebffbab..70b246a9 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.9.0" +version = "0.9.1" edition = "2018" [dependencies] From 8a7e533feda8d4103e91bec890c8d10322142275 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Jan 2020 20:13:08 +0100 Subject: [PATCH 240/295] Update ruma_api! documentation --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b387b826..25cdaae9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,10 @@ use serde_urlencoded; /// 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. /// +/// There is another kind of newtype body that is enabled with `#[ruma_api(raw_body)]`. It is used +/// for endpoints in which the request or response body can be arbitrary bytes instead of a JSON +/// objects. A field with `#[ruma_api(raw_body)]` needs to have the type `Vec`. +/// /// # Examples /// /// ``` From 542bef8c4ac338aae51fbb0f625f2ae254fb71b5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 7 Feb 2020 01:00:07 +0100 Subject: [PATCH 241/295] Split the `Error` type in two --- .travis.yml | 2 +- CHANGELOG.md | 7 ++ Cargo.toml | 10 ++- ruma-api-macros/src/api.rs | 10 +-- src/lib.rs | 159 ++++++++++++++++--------------------- 5 files changed, 89 insertions(+), 99 deletions(-) diff --git a/.travis.yml b/.travis.yml index 47bfc526..3569d047 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: "rust" cache: "cargo" rust: - - 1.39.0 + - 1.40.0 - stable - beta - nightly diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f5c6451..87e1fe9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # [unreleased] +Breaking changes: + +* Instead of one `Error` type, there is now two: `FromHttpError` and `IntoHttpError` + * In constrast to the old `Error` types, these can be pattern matched and generally allow getting + much more context for why things failed +* Out Minimum Supported Rust Version is now 1.40.0 + # 0.12.1 Improvements: diff --git a/Cargo.toml b/Cargo.toml index 84bae69a..fdcd7ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.9.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = "0.14.0" +ruma-identifiers = { version = "0.14.0", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_json = "1.0.42" serde_urlencoded = "0.6.1" @@ -27,7 +27,13 @@ ruma-events = "0.15.1" [features] default = ["with-ruma-api-macros"] -with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] +with-ruma-api-macros = [ + "percent-encoding", + "ruma-api-macros", + "ruma-identifiers", + "serde", + "url", +] [workspace] members = [ diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 80eb34c1..29180673 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -369,7 +369,7 @@ impl ToTokens for Api { #request_type impl std::convert::TryFrom>> for #request_try_from_type { - type Error = ruma_api::Error; + type Error = ruma_api::FromHttpError; #[allow(unused_variables)] fn try_from(request: ruma_api::exports::http::Request>) -> Result { @@ -388,7 +388,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom for ruma_api::exports::http::Request> { - type Error = ruma_api::Error; + type Error = ruma_api::IntoHttpError; #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { @@ -419,7 +419,7 @@ impl ToTokens for Api { #response_type impl std::convert::TryFrom for ruma_api::exports::http::Response> { - type Error = ruma_api::Error; + type Error = ruma_api::IntoHttpError; #[allow(unused_variables)] fn try_from(response: Response) -> Result { @@ -433,7 +433,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom>> for #response_try_from_type { - type Error = ruma_api::Error; + type Error = ruma_api::FromHttpError; #[allow(unused_variables)] fn try_from( @@ -449,7 +449,7 @@ impl ToTokens for Api { #response_init_fields }) } else { - Err(http_response.status().clone().into()) + Err(ruma_api::FromHttpError::Http(http_response)) } } } diff --git a/src/lib.rs b/src/lib.rs index 25cdaae9..6a85e6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,9 @@ use std::{ convert::{TryFrom, TryInto}, error::Error as StdError, fmt::{Display, Formatter, Result as FmtResult}, - io, }; -use http::{self, Method, StatusCode}; -use ruma_identifiers; +use http::{self, Method}; use serde_json; use serde_urlencoded; @@ -240,111 +238,89 @@ pub trait Outgoing { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: Outgoing + TryInto>, Error = Error> +pub trait Endpoint: Outgoing + TryInto>, Error = IntoHttpError> where - ::Incoming: TryFrom>, Error = Error>, - ::Incoming: TryFrom>, Error = Error>, + ::Incoming: TryFrom>, Error = FromHttpError>, + ::Incoming: TryFrom>, Error = FromHttpError>, { /// Data returned in a successful response from the endpoint. - type Response: Outgoing + TryInto>, Error = Error>; + type Response: Outgoing + TryInto>, Error = IntoHttpError>; /// Metadata about the endpoint. const METADATA: Metadata; } -/// An error when converting an `Endpoint` request or response to the corresponding type from the -/// `http` crate. +/// An error when converting an `http` request or response to the corresponding #[derive(Debug)] -pub struct Error(pub(crate) InnerError); +#[non_exhaustive] +pub enum FromHttpError { + /// The server returned a non-success status + Http(http::Response>), + /// JSON deserialization failed + Json(serde_json::Error), + /// Deserialization of query parameters failed + Query(serde_urlencoded::de::Error), +} -impl Display for Error { +/// An error when converting a request or response to the corresponding http type. +#[derive(Debug)] +#[non_exhaustive] +pub enum IntoHttpError { + /// JSON serialization failed + Json(serde_json::Error), + /// Serialization of query parameters failed + Query(serde_urlencoded::ser::Error), +} + +impl From for FromHttpError { + fn from(err: serde_json::Error) -> Self { + Self::Json(err) + } +} + +impl From for FromHttpError { + fn from(err: serde_urlencoded::de::Error) -> Self { + Self::Query(err) + } +} + +impl Display for FromHttpError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let message = match self.0 { - InnerError::Http(_) => "An error converting to or from `http` types occurred.".into(), - InnerError::Io(_) => "An I/O error occurred.".into(), - InnerError::SerdeJson(_) => "A JSON error occurred.".into(), - InnerError::SerdeUrlEncodedDe(_) => { - "A URL encoding deserialization error occurred.".into() - } - InnerError::SerdeUrlEncodedSer(_) => { - "A URL encoding serialization error occurred.".into() - } - InnerError::RumaIdentifiers(_) => "A ruma-identifiers error occurred.".into(), - InnerError::StatusCode(code) => format!("A HTTP {} error occurred.", code), - }; - - write!(f, "{}", message) + match self { + Self::Http(res) => match res.status().canonical_reason() { + Some(reason) => write!(f, "HTTP status {} {}", res.status().as_str(), reason), + None => write!(f, "HTTP status {}", res.status().as_str()), + }, + Self::Json(err) => write!(f, "JSON deserialization failed: {}", err), + Self::Query(err) => write!(f, "Query parameter deserialization failed: {}", err), + } } } -impl StdError for Error {} +impl StdError for FromHttpError {} -/// Internal representation of errors. -#[derive(Debug)] -pub(crate) enum InnerError { - /// An HTTP error. - Http(http::Error), - - /// A I/O error. - Io(io::Error), - - /// A Serde JSON error. - SerdeJson(serde_json::Error), - - /// A Serde URL decoding error. - SerdeUrlEncodedDe(serde_urlencoded::de::Error), - - /// A Serde URL encoding error. - SerdeUrlEncodedSer(serde_urlencoded::ser::Error), - - /// A Ruma Identitifiers error. - RumaIdentifiers(ruma_identifiers::Error), - - /// An HTTP status code indicating error. - StatusCode(StatusCode), -} - -impl From for Error { - fn from(error: http::Error) -> Self { - Self(InnerError::Http(error)) +impl From for IntoHttpError { + fn from(err: serde_json::Error) -> Self { + Self::Json(err) } } -impl From for Error { - fn from(error: io::Error) -> Self { - Self(InnerError::Io(error)) +impl From for IntoHttpError { + fn from(err: serde_urlencoded::ser::Error) -> Self { + Self::Query(err) } } -impl From for Error { - fn from(error: serde_json::Error) -> Self { - Self(InnerError::SerdeJson(error)) +impl Display for IntoHttpError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + Self::Json(err) => write!(f, "JSON serialization failed: {}", err), + Self::Query(err) => write!(f, "Query parameter serialization failed: {}", err), + } } } -impl From for Error { - fn from(error: serde_urlencoded::de::Error) -> Self { - Self(InnerError::SerdeUrlEncodedDe(error)) - } -} - -impl From for Error { - fn from(error: serde_urlencoded::ser::Error) -> Self { - Self(InnerError::SerdeUrlEncodedSer(error)) - } -} - -impl From for Error { - fn from(error: ruma_identifiers::Error) -> Self { - Self(InnerError::RumaIdentifiers(error)) - } -} - -impl From for Error { - fn from(error: StatusCode) -> Self { - Self(InnerError::StatusCode(error)) - } -} +impl StdError for IntoHttpError {} /// Metadata about an API endpoint. #[derive(Clone, Debug)] @@ -381,7 +357,7 @@ mod tests { use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; - use crate::{Endpoint, Error, Metadata, Outgoing}; + use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing}; /// A request to create a new room alias. #[derive(Debug)] @@ -408,7 +384,7 @@ mod tests { } impl TryFrom for http::Request> { - type Error = Error; + type Error = IntoHttpError; fn try_from(request: Request) -> Result>, Self::Error> { let metadata = Request::METADATA; @@ -423,14 +399,15 @@ mod tests { let http_request = http::Request::builder() .method(metadata.method) .uri(path) - .body(serde_json::to_vec(&request_body).map_err(Error::from)?)?; + .body(serde_json::to_vec(&request_body)?) + .expect("http request building to succeed"); Ok(http_request) } } impl TryFrom>> for Request { - type Error = Error; + type Error = FromHttpError; fn try_from(request: http::Request>) -> Result { let request_body: RequestBody = @@ -462,19 +439,19 @@ mod tests { } impl TryFrom>> for Response { - type Error = Error; + type Error = FromHttpError; fn try_from(http_response: http::Response>) -> Result { if http_response.status().is_success() { Ok(Response) } else { - Err(http_response.status().into()) + Err(FromHttpError::Http(http_response)) } } } impl TryFrom for http::Response> { - type Error = Error; + type Error = IntoHttpError; fn try_from(_: Response) -> Result>, Self::Error> { let response = http::Response::builder() From 5a8da77ac15d47086ccddc2e2abd047c9156f6e3 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 7 Feb 2020 01:01:23 +0100 Subject: [PATCH 242/295] Add myself to the Cargo.toml authors arrays --- Cargo.toml | 5 ++++- ruma-api-macros/Cargo.toml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fdcd7ebe..a2668cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,8 @@ [package] -authors = ["Jimmy Cuadra "] +authors = [ + "Jimmy Cuadra ", + "Jonas Platte ", +] categories = ["api-bindings", "web-programming"] description = "An abstraction for Matrix API endpoints." documentation = "https://docs.rs/ruma-api" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 70b246a9..d45b86ae 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -1,5 +1,8 @@ [package] -authors = ["Jimmy Cuadra "] +authors = [ + "Jimmy Cuadra ", + "Jonas Platte ", +] categories = ["api-bindings", "web-programming"] description = "A procedural macro for generating ruma-api Endpoints." documentation = "https://docs.rs/ruma-api-macros" From df0e07654956bf492d15748b1e1f28ad0704a104 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 7 Feb 2020 01:02:15 +0100 Subject: [PATCH 243/295] Update dependencies --- Cargo.toml | 8 ++++---- ruma-api-macros/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2668cff..ad864469 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,11 @@ edition = "2018" http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.9.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = { version = "0.14.0", optional = true } -serde = { version = "1.0.103", features = ["derive"], optional = true } -serde_json = "1.0.42" +ruma-identifiers = { version = "0.14.1", optional = true } +serde = { version = "1.0.104", features = ["derive"], optional = true } +serde_json = "1.0.46" serde_urlencoded = "0.6.1" -url = { version = "2.1.0", optional = true } +url = { version = "2.1.1", optional = true } [dev-dependencies] ruma-events = "0.15.1" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index d45b86ae..499145be 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -16,9 +16,9 @@ version = "0.9.1" edition = "2018" [dependencies] -proc-macro2 = "1.0.6" +proc-macro2 = "1.0.8" quote = "1.0.2" -syn = { version = "1.0.8", features = ["full", "extra-traits"] } +syn = { version = "1.0.14", features = ["full", "extra-traits"] } [lib] proc-macro = true From eb7c85325a0685f6e1dba180a58b73656d567467 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 7 Feb 2020 11:23:52 +0100 Subject: [PATCH 244/295] Remove redundant imports --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6a85e6ac..9c447afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,7 @@ use std::{ fmt::{Display, Formatter, Result as FmtResult}, }; -use http::{self, Method}; -use serde_json; -use serde_urlencoded; +use http::Method; /// Generates a `ruma_api::Endpoint` from a concise definition. /// From c40735c3e8b90c8c5f6965c89990f74599c77a07 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 7 Feb 2020 11:47:34 +0100 Subject: [PATCH 245/295] Remove more reduntant imports --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9c447afd..8a4c33d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -349,11 +349,9 @@ mod tests { pub mod create { use std::convert::TryFrom; - use http::{self, header::CONTENT_TYPE, method::Method}; - use percent_encoding; + use http::{header::CONTENT_TYPE, method::Method}; use ruma_identifiers::{RoomAliasId, RoomId}; use serde::{de::IntoDeserializer, Deserialize, Serialize}; - use serde_json; use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing}; From b72bb254961aed9efb9b617b04ab01e5f6607dcc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Feb 2020 20:01:08 +0100 Subject: [PATCH 246/295] Move attributes after doc comment --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a4c33d5..132581cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,10 +208,10 @@ pub use ruma_api_macros::ruma_api; #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::Outgoing; -#[cfg(feature = "with-ruma-api-macros")] -#[doc(hidden)] /// This module is used to support the generated code from ruma-api-macros. /// It is not considered part of ruma-api's public API. +#[cfg(feature = "with-ruma-api-macros")] +#[doc(hidden)] pub mod exports { pub use http; pub use percent_encoding; From abb278c85a31ae1aead3915fcdfb616e15c44f81 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Feb 2020 20:48:50 +0100 Subject: [PATCH 247/295] Simplify path segment deserialization code --- ruma-api-macros/src/api.rs | 12 ++++-------- ruma-api-macros/src/api/request.rs | 7 ------- src/lib.rs | 7 +++---- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 29180673..bbef4690 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -150,20 +150,16 @@ impl ToTokens for Api { |(i, segment)| { let path_var = &segment[1..]; let path_var_ident = Ident::new(path_var, Span::call_site()); - let path_field = self - .request - .path_field(path_var) - .expect("expected request to have path field"); - let ty = &path_field.ty; quote! { #path_var_ident: { + use std::ops::Deref as _; + let segment = path_segments.get(#i).unwrap().as_bytes(); let decoded = ruma_api::exports::percent_encoding::percent_decode(segment) .decode_utf8_lossy(); - #ty::deserialize(decoded.into_deserializer()) - .map_err(|e: ruma_api::exports::serde_json::error::Error| e)? + ruma_api::exports::serde_json::from_str(decoded.deref())? } } }, @@ -359,7 +355,7 @@ impl ToTokens for Api { let response_doc = format!("Data in the response from the `{}` API endpoint.", name); let api = quote! { - use ruma_api::exports::serde::de::{Error as _, IntoDeserializer as _}; + use ruma_api::exports::serde::de::Error as _; use ruma_api::exports::serde::Deserialize as _; use ruma_api::Endpoint as _; diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index d4c02d06..0f4e4f19 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -106,13 +106,6 @@ impl Request { 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_of_kind(RequestFieldKind::Path)).find(|field| { - field.ident.as_ref().expect("expected field to have an identifier") == name - }) - } - /// Returns the body field. pub fn newtype_body_field(&self) -> Option<&Field> { self.fields.iter().find_map(RequestField::as_newtype_body_field) diff --git a/src/lib.rs b/src/lib.rs index 132581cd..e2089c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -347,11 +347,11 @@ pub struct Metadata { mod tests { /// PUT /_matrix/client/r0/directory/room/:room_alias pub mod create { - use std::convert::TryFrom; + use std::{convert::TryFrom, ops::Deref}; use http::{header::CONTENT_TYPE, method::Method}; use ruma_identifiers::{RoomAliasId, RoomId}; - use serde::{de::IntoDeserializer, Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing}; @@ -414,8 +414,7 @@ mod tests { room_alias: { let segment = path_segments.get(5).unwrap().as_bytes(); let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); - RoomAliasId::deserialize(decoded.into_deserializer()) - .map_err(|e: serde_json::error::Error| e)? + serde_json::from_str(decoded.deref())? }, }) } From 89f8d0f656a1fd02598055de740b7aba11777007 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Feb 2020 21:44:02 +0100 Subject: [PATCH 248/295] Further split the error types --- CHANGELOG.md | 6 +- ruma-api-macros/src/api.rs | 88 ++++++++--- ruma-api-macros/src/api/request.rs | 19 ++- ruma-api-macros/src/api/response.rs | 2 +- src/error.rs | 219 ++++++++++++++++++++++++++++ src/lib.rs | 116 ++++----------- 6 files changed, 333 insertions(+), 117 deletions(-) create mode 100644 src/error.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e1fe9f..6088473e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ Breaking changes: -* Instead of one `Error` type, there is now two: `FromHttpError` and `IntoHttpError` - * In constrast to the old `Error` types, these can be pattern matched and generally allow getting - much more context for why things failed +* Instead of one `Error` type, there is now many + * The new types live in their own `error` module + * They provide access to details that were previously hidden * Out Minimum Supported Rust Version is now 1.40.0 # 0.12.1 diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index bbef4690..a7a42c88 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -154,12 +154,20 @@ impl ToTokens for Api { quote! { #path_var_ident: { use std::ops::Deref as _; + use ruma_api::error::RequestDeserializationError; let segment = path_segments.get(#i).unwrap().as_bytes(); let decoded = ruma_api::exports::percent_encoding::percent_decode(segment) .decode_utf8_lossy(); - ruma_api::exports::serde_json::from_str(decoded.deref())? + match ruma_api::exports::serde_json::from_str(decoded.deref()) { + Ok(val) => val, + Err(err) => { + return Err( + RequestDeserializationError::new(err, request).into() + ); + } + } } } }, @@ -230,13 +238,31 @@ impl ToTokens for Api { let extract_request_query = if self.request.query_map_field().is_some() { quote! { - let request_query = - ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; + let request_query = match ruma_api::exports::serde_urlencoded::from_str( + &request.uri().query().unwrap_or("") + ) { + Ok(query) => query, + Err(err) => { + return Err( + ruma_api::error::RequestDeserializationError::new(err, request).into() + ); + } + }; } } else if self.request.has_query_fields() { quote! { let request_query: RequestQuery = - ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?; + match ruma_api::exports::serde_urlencoded::from_str( + &request.uri().query().unwrap_or("") + ) { + Ok(query) => query, + Err(err) => { + return Err( + ruma_api::error::RequestDeserializationError::new(err, request) + .into() + ); + } + }; } } else { TokenStream::new() @@ -274,7 +300,15 @@ impl ToTokens for Api { if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { quote! { let request_body: ::Incoming = - ruma_api::exports::serde_json::from_slice(request.body().as_slice())?; + match ruma_api::exports::serde_json::from_slice(request.body().as_slice()) { + Ok(body) => body, + Err(err) => { + return Err( + ruma_api::error::RequestDeserializationError::new(err, request) + .into() + ); + } + }; } } else { TokenStream::new() @@ -325,21 +359,30 @@ impl ToTokens for Api { let extract_response_headers = if self.response.has_header_fields() { quote! { - let mut headers = http_response.headers().clone(); + let mut headers = response.headers().clone(); } } else { TokenStream::new() }; - let typed_response_body_decl = - if self.response.has_body_fields() || self.response.newtype_body_field().is_some() { - quote! { - let response_body: ::Incoming = - ruma_api::exports::serde_json::from_slice(response_body.as_slice())?; - } - } else { - TokenStream::new() - }; + let typed_response_body_decl = if self.response.has_body_fields() + || self.response.newtype_body_field().is_some() + { + quote! { + let response_body: ::Incoming = + match ruma_api::exports::serde_json::from_slice(response.body().as_slice()) { + Ok(body) => body, + Err(err) => { + return Err( + ruma_api::error::ResponseDeserializationError::new(err, response) + .into() + ); + } + }; + } + } else { + TokenStream::new() + }; let response_init_fields = self.response.init_fields(); @@ -365,7 +408,7 @@ impl ToTokens for Api { #request_type impl std::convert::TryFrom>> for #request_try_from_type { - type Error = ruma_api::FromHttpError; + type Error = ruma_api::error::FromHttpRequestError; #[allow(unused_variables)] fn try_from(request: ruma_api::exports::http::Request>) -> Result { @@ -384,7 +427,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom for ruma_api::exports::http::Request> { - type Error = ruma_api::IntoHttpError; + type Error = ruma_api::error::IntoHttpError; #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { @@ -415,7 +458,7 @@ impl ToTokens for Api { #response_type impl std::convert::TryFrom for ruma_api::exports::http::Response> { - type Error = ruma_api::IntoHttpError; + type Error = ruma_api::error::IntoHttpError; #[allow(unused_variables)] fn try_from(response: Response) -> Result { @@ -429,23 +472,22 @@ impl ToTokens for Api { } impl std::convert::TryFrom>> for #response_try_from_type { - type Error = ruma_api::FromHttpError; + type Error = ruma_api::error::FromHttpResponseError; #[allow(unused_variables)] fn try_from( - http_response: ruma_api::exports::http::Response>, + response: ruma_api::exports::http::Response>, ) -> Result { - if http_response.status().is_success() { + if response.status().as_u16() < 400 { #extract_response_headers - let response_body = http_response.into_body(); #typed_response_body_decl Ok(Self { #response_init_fields }) } else { - Err(ruma_api::FromHttpError::Http(http_response)) + Err(ruma_api::error::ServerError::new(response).into()) } } } diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index 0f4e4f19..cb5e57f0 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -54,10 +54,21 @@ impl Request { let header_name_string = header_name.to_string(); quote! { - #field_name: headers.get(ruma_api::exports::http::header::#header_name) - .and_then(|v| v.to_str().ok()) - .ok_or(ruma_api::exports::serde_json::Error::missing_field(#header_name_string))? - .to_owned() + #field_name: match headers.get(ruma_api::exports::http::header::#header_name) + .and_then(|v| v.to_str().ok()) { + Some(header) => header.to_owned(), + None => { + return Err( + ruma_api::error::RequestDeserializationError::new( + ruma_api::exports::serde_json::Error::missing_field( + #header_name_string + ), + request, + ) + .into() + ); + } + } } }); diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index c289b2a9..4b2cf89d 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -62,7 +62,7 @@ impl Response { } ResponseField::NewtypeRawBody(_) => { quote_spanned! {span=> - #field_name: response_body + #field_name: response.into_body() } } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..364f36a0 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,219 @@ +//! This module contains types for all kinds of errors that can occur when +//! converting between http requests / responses and ruma's representation of +//! matrix API requests / responses. + +use std::fmt::{self, Display, Formatter}; + +/// An error when converting one of ruma's endpoint-specific request or response +/// types to the corresponding http type. +#[derive(Debug)] +pub struct IntoHttpError(SerializationError); + +#[doc(hidden)] +impl From for IntoHttpError { + fn from(err: serde_json::Error) -> Self { + Self(SerializationError::Json(err)) + } +} + +#[doc(hidden)] +impl From for IntoHttpError { + fn from(err: serde_urlencoded::ser::Error) -> Self { + Self(SerializationError::Query(err)) + } +} + +impl Display for IntoHttpError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.0 { + SerializationError::Json(err) => write!(f, "JSON serialization failed: {}", err), + SerializationError::Query(err) => { + write!(f, "Query parameter serialization failed: {}", err) + } + } + } +} + +impl std::error::Error for IntoHttpError {} + +/// An error when converting a http request to one of ruma's endpoint-specific +/// request types. +#[derive(Debug)] +#[non_exhaustive] +pub enum FromHttpRequestError { + /// Deserialization failed + Deserialization(RequestDeserializationError), +} + +impl Display for FromHttpRequestError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Deserialization(err) => write!(f, "deserialization failed: {}", err), + } + } +} + +impl From for FromHttpRequestError { + fn from(err: RequestDeserializationError) -> Self { + Self::Deserialization(err) + } +} + +impl std::error::Error for FromHttpRequestError {} + +/// An error that occurred when trying to deserialize a request. +#[derive(Debug)] +pub struct RequestDeserializationError { + inner: DeserializationError, + http_request: http::Request>, +} + +impl RequestDeserializationError { + /// This method is public so it is accessible from `ruma_api!` generated + /// code. It is not considered part of ruma-api's public API. + #[doc(hidden)] + pub fn new( + inner: impl Into, + http_request: http::Request>, + ) -> Self { + Self { inner: inner.into(), http_request } + } +} + +impl Display for RequestDeserializationError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl std::error::Error for RequestDeserializationError {} + +/// An error when converting a http response to one of ruma's endpoint-specific +/// response types. +#[derive(Debug)] +#[non_exhaustive] +pub enum FromHttpResponseError { + /// Deserialization failed + Deserialization(ResponseDeserializationError), + /// The server returned a non-success status + Http(ServerError), +} + +impl Display for FromHttpResponseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Deserialization(err) => write!(f, "deserialization failed: {}", err), + Self::Http(err) => write!(f, "the server returned an error: {}", err), + } + } +} + +impl From for FromHttpResponseError { + fn from(err: ServerError) -> Self { + Self::Http(err) + } +} + +impl From for FromHttpResponseError { + fn from(err: ResponseDeserializationError) -> Self { + Self::Deserialization(err) + } +} + +/// An error that occurred when trying to deserialize a response. +#[derive(Debug)] +pub struct ResponseDeserializationError { + inner: DeserializationError, + http_response: http::Response>, +} + +impl ResponseDeserializationError { + /// This method is public so it is accessible from `ruma_api!` generated + /// code. It is not considered part of ruma-api's public API. + #[doc(hidden)] + pub fn new( + inner: impl Into, + http_response: http::Response>, + ) -> Self { + Self { inner: inner.into(), http_response } + } +} + +impl Display for ResponseDeserializationError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl std::error::Error for ResponseDeserializationError {} + +/// An error was reported by the server (HTTP status code 4xx or 5xx) +#[derive(Debug)] +pub struct ServerError { + http_response: http::Response>, +} + +impl ServerError { + /// This method is public so it is accessible from `ruma_api!` generated + /// code. It is not considered part of ruma-api's public API. + #[doc(hidden)] + pub fn new(http_response: http::Response>) -> Self { + Self { http_response } + } + + /// Get the HTTP response without parsing its contents. + pub fn into_raw_reponse(self) -> http::Response> { + self.http_response + } +} + +impl Display for ServerError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.http_response.status().canonical_reason() { + Some(reason) => { + write!(f, "HTTP status {} {}", self.http_response.status().as_str(), reason) + } + None => write!(f, "HTTP status {}", self.http_response.status().as_str()), + } + } +} + +impl std::error::Error for ServerError {} + +#[derive(Debug)] +enum SerializationError { + Json(serde_json::Error), + Query(serde_urlencoded::ser::Error), +} + +/// This type is public so it is accessible from `ruma_api!` generated code. +/// It is not considered part of ruma-api's public API. +#[doc(hidden)] +#[derive(Debug)] +pub enum DeserializationError { + Json(serde_json::Error), + Query(serde_urlencoded::de::Error), +} + +impl Display for DeserializationError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + DeserializationError::Json(err) => Display::fmt(err, f), + DeserializationError::Query(err) => Display::fmt(err, f), + } + } +} + +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: serde_json::Error) -> Self { + Self::Json(err) + } +} + +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: serde_urlencoded::de::Error) -> Self { + Self::Query(err) + } +} diff --git a/src/lib.rs b/src/lib.rs index e2089c27..48f68eb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,11 +13,7 @@ #![warn(rust_2018_idioms)] #![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)] -use std::{ - convert::{TryFrom, TryInto}, - error::Error as StdError, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::convert::{TryFrom, TryInto}; use http::Method; @@ -208,6 +204,7 @@ pub use ruma_api_macros::ruma_api; #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::Outgoing; +pub mod error; /// This module is used to support the generated code from ruma-api-macros. /// It is not considered part of ruma-api's public API. #[cfg(feature = "with-ruma-api-macros")] @@ -221,6 +218,8 @@ pub mod exports { pub use url; } +use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; + /// A type that can be sent to another party that understands the matrix protocol. If any of the /// fields of `Self` don't implement serde's `Deserialize`, you can derive this trait to generate a /// corresponding 'Incoming' type that supports deserialization. This is useful for things like @@ -238,8 +237,9 @@ pub trait Outgoing { /// The type implementing this trait contains any data needed to make a request to the endpoint. pub trait Endpoint: Outgoing + TryInto>, Error = IntoHttpError> where - ::Incoming: TryFrom>, Error = FromHttpError>, - ::Incoming: TryFrom>, Error = FromHttpError>, + ::Incoming: TryFrom>, Error = FromHttpRequestError>, + ::Incoming: + TryFrom>, Error = FromHttpResponseError>, { /// Data returned in a successful response from the endpoint. type Response: Outgoing + TryInto>, Error = IntoHttpError>; @@ -248,78 +248,6 @@ where const METADATA: Metadata; } -/// An error when converting an `http` request or response to the corresponding -#[derive(Debug)] -#[non_exhaustive] -pub enum FromHttpError { - /// The server returned a non-success status - Http(http::Response>), - /// JSON deserialization failed - Json(serde_json::Error), - /// Deserialization of query parameters failed - Query(serde_urlencoded::de::Error), -} - -/// An error when converting a request or response to the corresponding http type. -#[derive(Debug)] -#[non_exhaustive] -pub enum IntoHttpError { - /// JSON serialization failed - Json(serde_json::Error), - /// Serialization of query parameters failed - Query(serde_urlencoded::ser::Error), -} - -impl From for FromHttpError { - fn from(err: serde_json::Error) -> Self { - Self::Json(err) - } -} - -impl From for FromHttpError { - fn from(err: serde_urlencoded::de::Error) -> Self { - Self::Query(err) - } -} - -impl Display for FromHttpError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self { - Self::Http(res) => match res.status().canonical_reason() { - Some(reason) => write!(f, "HTTP status {} {}", res.status().as_str(), reason), - None => write!(f, "HTTP status {}", res.status().as_str()), - }, - Self::Json(err) => write!(f, "JSON deserialization failed: {}", err), - Self::Query(err) => write!(f, "Query parameter deserialization failed: {}", err), - } - } -} - -impl StdError for FromHttpError {} - -impl From for IntoHttpError { - fn from(err: serde_json::Error) -> Self { - Self::Json(err) - } -} - -impl From for IntoHttpError { - fn from(err: serde_urlencoded::ser::Error) -> Self { - Self::Query(err) - } -} - -impl Display for IntoHttpError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - match self { - Self::Json(err) => write!(f, "JSON serialization failed: {}", err), - Self::Query(err) => write!(f, "Query parameter serialization failed: {}", err), - } - } -} - -impl StdError for IntoHttpError {} - /// Metadata about an API endpoint. #[derive(Clone, Debug)] pub struct Metadata { @@ -353,7 +281,13 @@ mod tests { use ruma_identifiers::{RoomAliasId, RoomId}; use serde::{Deserialize, Serialize}; - use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing}; + use crate::{ + error::{ + FromHttpRequestError, FromHttpResponseError, IntoHttpError, + RequestDeserializationError, ServerError, + }, + Endpoint, Metadata, Outgoing, + }; /// A request to create a new room alias. #[derive(Debug)] @@ -403,18 +337,28 @@ mod tests { } impl TryFrom>> for Request { - type Error = FromHttpError; + type Error = FromHttpRequestError; fn try_from(request: http::Request>) -> Result { let request_body: RequestBody = - ::serde_json::from_slice(request.body().as_slice())?; + match serde_json::from_slice(request.body().as_slice()) { + Ok(body) => body, + Err(err) => { + return Err(RequestDeserializationError::new(err, request).into()); + } + }; let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); Ok(Request { room_id: request_body.room_id, room_alias: { let segment = path_segments.get(5).unwrap().as_bytes(); let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); - serde_json::from_str(decoded.deref())? + match serde_json::from_str(decoded.deref()) { + Ok(id) => id, + Err(err) => { + return Err(RequestDeserializationError::new(err, request).into()) + } + } }, }) } @@ -434,13 +378,13 @@ mod tests { } impl TryFrom>> for Response { - type Error = FromHttpError; + type Error = FromHttpResponseError; fn try_from(http_response: http::Response>) -> Result { - if http_response.status().is_success() { + if http_response.status().as_u16() < 400 { Ok(Response) } else { - Err(FromHttpError::Http(http_response)) + Err(FromHttpResponseError::Http(ServerError::new(http_response))) } } } From 8be1a4d168a89394ba0e83d61aaab03d84d7a817 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Feb 2020 22:02:27 +0100 Subject: [PATCH 249/295] Bump versions --- CHANGELOG.md | 2 ++ Cargo.toml | 6 +++--- ruma-api-macros/CHANGELOG.md | 6 ++++++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6088473e..8a454cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.13.0 + Breaking changes: * Instead of one `Error` type, there is now many diff --git a/Cargo.toml b/Cargo.toml index ad864469..76947227 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,16 +12,16 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.12.1" +version = "0.13.0" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.9.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.10.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.104", features = ["derive"], optional = true } -serde_json = "1.0.46" +serde_json = "1.0.47" serde_urlencoded = "0.6.1" url = { version = "2.1.1", optional = true } diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index b1d65f13..12786e7a 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.10.0 + +Breaking changes: + +* Updated code generation to match the changes in ruma-api 0.13.0 + # 0.9.1 Improvements: diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 499145be..b46ea7be 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.9.1" +version = "0.10.0" edition = "2018" [dependencies] From a9abdbc5e9690ad24b70723b3ae6b1b4bb848fc6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Feb 2020 22:38:34 +0100 Subject: [PATCH 250/295] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a454cbf..cbe5c559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Breaking changes: * Instead of one `Error` type, there is now many * The new types live in their own `error` module * They provide access to details that were previously hidden -* Out Minimum Supported Rust Version is now 1.40.0 +* Our Minimum Supported Rust Version is now 1.40.0 # 0.12.1 From a811a709ec2219470b99bc002b5d28700ddffd6a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 10 Feb 2020 10:49:47 +0100 Subject: [PATCH 251/295] Implement Debug for Incoming types --- CHANGELOG.md | 7 +++++++ Cargo.toml | 4 ++-- ruma-api-macros/CHANGELOG.md | 6 ++++++ ruma-api-macros/Cargo.toml | 2 +- ruma-api-macros/src/derive_outgoing.rs | 4 ++-- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbe5c559..4db88f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # [unreleased] +# 0.13.1 + +Improvements: + +* Update ruma-api-macros to 0.10.1 + * `Incoming` types will now implement `Debug` + # 0.13.0 Breaking changes: diff --git a/Cargo.toml b/Cargo.toml index 76947227..ac18a4a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.13.0" +version = "0.13.1" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.10.0", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.10.1", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.104", features = ["derive"], optional = true } serde_json = "1.0.47" diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 12786e7a..a4c67b66 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.10.1 + +Improvements: + +* Derive `Debug` for `Incoming` types + # 0.10.0 Breaking changes: diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index b46ea7be..78defcf4 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.10.0" +version = "0.10.1" edition = "2018" [dependencies] diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs index a2411f43..0b1b0f51 100644 --- a/ruma-api-macros/src/derive_outgoing.rs +++ b/ruma-api-macros/src/derive_outgoing.rs @@ -27,7 +27,7 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) { TokenStream::new() } else { - quote!(#[derive(ruma_api::exports::serde::Deserialize)]) + quote!(ruma_api::exports::serde::Deserialize) }; let (mut fields, struct_kind): (Vec<_>, _) = match input.data { @@ -92,7 +92,7 @@ pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { Ok(quote! { #[doc = #doc] - #derive_deserialize + #[derive(Debug, #derive_deserialize)] #vis struct #incoming_ident #struct_def impl ruma_api::Outgoing for #original_ident { From 9861aa20c8a0415b045682fda0a961f1ee192f35 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 17 Feb 2020 22:12:18 +0100 Subject: [PATCH 252/295] Fix path segments being parsed as JSON --- ruma-api-macros/src/api.rs | 2 +- src/error.rs | 16 ++++++++++ tests/conversions.rs | 61 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/conversions.rs diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index a7a42c88..b2024c98 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -160,7 +160,7 @@ impl ToTokens for Api { let decoded = ruma_api::exports::percent_encoding::percent_decode(segment) .decode_utf8_lossy(); - match ruma_api::exports::serde_json::from_str(decoded.deref()) { + match std::convert::TryFrom::try_from(decoded.deref()) { Ok(val) => val, Err(err) => { return Err( diff --git a/src/error.rs b/src/error.rs index 364f36a0..f90f37f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -193,6 +193,7 @@ enum SerializationError { pub enum DeserializationError { Json(serde_json::Error), Query(serde_urlencoded::de::Error), + Ident(ruma_identifiers::Error), } impl Display for DeserializationError { @@ -200,6 +201,7 @@ impl Display for DeserializationError { match self { DeserializationError::Json(err) => Display::fmt(err, f), DeserializationError::Query(err) => Display::fmt(err, f), + DeserializationError::Ident(err) => Display::fmt(err, f), } } } @@ -217,3 +219,17 @@ impl From for DeserializationError { Self::Query(err) } } + +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: ruma_identifiers::Error) -> Self { + Self::Ident(err) + } +} + +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: std::convert::Infallible) -> Self { + match err {} + } +} diff --git a/tests/conversions.rs b/tests/conversions.rs new file mode 100644 index 00000000..55bdc578 --- /dev/null +++ b/tests/conversions.rs @@ -0,0 +1,61 @@ +use ruma_api::ruma_api; +use ruma_identifiers::UserId; + +ruma_api! { + metadata { + description: "Does something.", + method: POST, + name: "my_endpoint", + path: "/_matrix/foo/:bar/:baz", + rate_limited: false, + requires_authentication: false, + } + + request { + pub hello: String, + #[ruma_api(header = CONTENT_TYPE)] + pub world: String, + #[ruma_api(query)] + pub q1: String, + #[ruma_api(query)] + pub q2: u32, + #[ruma_api(path)] + pub bar: String, + #[ruma_api(path)] + pub baz: UserId, + } + + response { + pub hello: String, + #[ruma_api(header = CONTENT_TYPE)] + pub world: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub optional_flag: Option, + } +} + +#[test] +fn request_serde() -> Result<(), Box> { + use std::convert::TryFrom; + + let req = Request { + hello: "hi".to_owned(), + world: "test".to_owned(), + q1: "query_param_special_chars %/&@!".to_owned(), + q2: 55, + bar: "bar".to_owned(), + baz: UserId::try_from("@bazme:ruma.io")?, + }; + + let http_req = http::Request::>::try_from(req.clone())?; + let req2 = Request::try_from(http_req)?; + + assert_eq!(req.hello, req2.hello); + assert_eq!(req.world, req2.world); + assert_eq!(req.q1, req2.q1); + assert_eq!(req.q2, req2.q2); + assert_eq!(req.bar, req2.bar); + assert_eq!(req.baz, req2.baz); + + Ok(()) +} From 810bf56874b31750d8cec3550e1fd181346ab382 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 18 Feb 2020 20:58:29 +0100 Subject: [PATCH 253/295] Add strum::ParseError variant to DeserializationError --- Cargo.toml | 1 + ruma-api-macros/src/api/request.rs | 6 +----- src/error.rs | 11 +++++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac18a4a8..054a1913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.104", features = ["derive"], optional = true } serde_json = "1.0.47" serde_urlencoded = "0.6.1" +strum = "0.17.1" url = { version = "2.1.1", optional = true } [dev-dependencies] diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index cb5e57f0..ed14f42c 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -353,11 +353,7 @@ impl ToTokens for Request { quote! { /// Data in the request path. - #[derive( - Debug, - ruma_api::exports::serde::Deserialize, - ruma_api::exports::serde::Serialize, - )] + #[derive(Debug)] struct RequestPath { #(#fields),* } diff --git a/src/error.rs b/src/error.rs index f90f37f8..1047c58a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -194,6 +194,9 @@ pub enum DeserializationError { Json(serde_json::Error), Query(serde_urlencoded::de::Error), Ident(ruma_identifiers::Error), + // String <> Enum conversion failed. This can currently only happen in path + // segment deserialization + Strum(strum::ParseError), } impl Display for DeserializationError { @@ -202,6 +205,7 @@ impl Display for DeserializationError { DeserializationError::Json(err) => Display::fmt(err, f), DeserializationError::Query(err) => Display::fmt(err, f), DeserializationError::Ident(err) => Display::fmt(err, f), + DeserializationError::Strum(err) => Display::fmt(err, f), } } } @@ -227,6 +231,13 @@ impl From for DeserializationError { } } +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: strum::ParseError) -> Self { + Self::Strum(err) + } +} + #[doc(hidden)] impl From for DeserializationError { fn from(err: std::convert::Infallible) -> Self { From 9e076858af2120a63c44aee44dd0cd322e8aeed9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 18 Feb 2020 21:02:33 +0100 Subject: [PATCH 254/295] Update change logs --- CHANGELOG.md | 6 ++++++ ruma-api-macros/CHANGELOG.md | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4db88f01..5219746d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +Breaking changes: + +* Update ruma-api-macros to 0.11.0 + * This includes a fix that uses `TryFrom<&str>` instead of serde_json for path segment + deserialization + # 0.13.1 Improvements: diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index a4c67b66..1bef00e3 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,9 @@ # [unreleased] +Breaking changes: + +* Use `TryFrom<&str>` instead of serde_json for path segment deserialization + # 0.10.1 Improvements: From 8e7325cdcc84f54fa84ac38cbe82438949c65269 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 18 Feb 2020 21:05:21 +0100 Subject: [PATCH 255/295] Simplify request path deserialization by removing RequestPath struct --- ruma-api-macros/src/api.rs | 8 +------- ruma-api-macros/src/api/request.rs | 25 ------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index b2024c98..348093ce 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -117,14 +117,12 @@ impl ToTokens for Api { "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 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..]; let path_var_ident = Ident::new(path_var, Span::call_site()); - quote!(&request_path.#path_var_ident.to_string()) + quote!(&request.#path_var_ident.to_string()) } else { quote!(#segment) }; @@ -135,10 +133,6 @@ impl ToTokens for Api { }); let set_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. diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index ed14f42c..c95bf4fc 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -137,11 +137,6 @@ impl Request { 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)) @@ -348,20 +343,6 @@ impl ToTokens for Request { } }); - let request_path_struct = if self.has_path_fields() { - let fields = self.fields.iter().filter_map(RequestField::as_path_field); - - quote! { - /// Data in the request path. - #[derive(Debug)] - struct RequestPath { - #(#fields),* - } - } - } else { - TokenStream::new() - }; - let request_query_struct = if let Some(f) = self.query_map_field() { let field = Field { ident: None, colon_token: None, ..f.clone() }; @@ -398,7 +379,6 @@ impl ToTokens for Request { pub struct Request #request_def #request_body_struct - #request_path_struct #request_query_struct }; @@ -493,11 +473,6 @@ impl RequestField { self.field_of_kind(RequestFieldKind::NewtypeRawBody) } - /// Return the contained field if this request field is a path kind. - fn as_path_field(&self) -> Option<&Field> { - self.field_of_kind(RequestFieldKind::Path) - } - /// Return the contained field if this request field is a query kind. fn as_query_field(&self) -> Option<&Field> { self.field_of_kind(RequestFieldKind::Query) From 557be94f3ac89b2415dc5f5e7debe3214fdb346a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 18 Feb 2020 21:17:18 +0100 Subject: [PATCH 256/295] Bump versions --- CHANGELOG.md | 2 ++ Cargo.toml | 4 ++-- ruma-api-macros/CHANGELOG.md | 2 ++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5219746d..835c4f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.14.0 + Breaking changes: * Update ruma-api-macros to 0.11.0 diff --git a/Cargo.toml b/Cargo.toml index 054a1913..4ed9d304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.13.1" +version = "0.14.0" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.10.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.11.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.104", features = ["derive"], optional = true } serde_json = "1.0.47" diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 1bef00e3..caa30504 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.11.0 + Breaking changes: * Use `TryFrom<&str>` instead of serde_json for path segment deserialization diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 78defcf4..8f3716db 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.10.1" +version = "0.11.0" edition = "2018" [dependencies] From 1c23df55e4934db76010ba1be77eeb960072e4dc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 19 Feb 2020 00:46:16 +0100 Subject: [PATCH 257/295] Update ruma-events to 0.16.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4ed9d304..e42db324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ strum = "0.17.1" url = { version = "2.1.1", optional = true } [dev-dependencies] -ruma-events = "0.15.1" +ruma-events = "0.16.0" [features] default = ["with-ruma-api-macros"] From e5e89b8929859a58c2c426184d8ec0b745ba9dfe Mon Sep 17 00:00:00 2001 From: iinuwa Date: Fri, 21 Feb 2020 06:53:07 -0600 Subject: [PATCH 258/295] Use `raw_body` for bytes payload in doc examples. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 48f68eb7..2dfcb7f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ use http::Method; /// } /// /// request { -/// #[ruma_api(body)] +/// #[ruma_api(raw_body)] /// pub file: Vec, /// } /// From c346b4f681b85f76e60adb919000ba31c4274d00 Mon Sep 17 00:00:00 2001 From: Donough Liu Date: Sun, 8 Mar 2020 00:06:24 +0800 Subject: [PATCH 259/295] Emit error on non-UTF8 characters in path segments --- ruma-api-macros/src/api.rs | 13 ++++++++++--- src/error.rs | 9 +++++++++ src/lib.rs | 8 +++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 348093ce..d7741437 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -151,9 +151,16 @@ impl ToTokens for Api { use ruma_api::error::RequestDeserializationError; let segment = path_segments.get(#i).unwrap().as_bytes(); - let decoded = - ruma_api::exports::percent_encoding::percent_decode(segment) - .decode_utf8_lossy(); + let decoded = match ruma_api::exports::percent_encoding::percent_decode( + segment + ).decode_utf8() { + Ok(x) => x, + Err(err) => { + return Err( + RequestDeserializationError::new(err, request).into() + ); + } + }; match std::convert::TryFrom::try_from(decoded.deref()) { Ok(val) => val, Err(err) => { diff --git a/src/error.rs b/src/error.rs index 1047c58a..b3d17c9d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -191,6 +191,7 @@ enum SerializationError { #[doc(hidden)] #[derive(Debug)] pub enum DeserializationError { + Utf8(std::str::Utf8Error), Json(serde_json::Error), Query(serde_urlencoded::de::Error), Ident(ruma_identifiers::Error), @@ -202,6 +203,7 @@ pub enum DeserializationError { impl Display for DeserializationError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { + DeserializationError::Utf8(err) => Display::fmt(err, f), DeserializationError::Json(err) => Display::fmt(err, f), DeserializationError::Query(err) => Display::fmt(err, f), DeserializationError::Ident(err) => Display::fmt(err, f), @@ -210,6 +212,13 @@ impl Display for DeserializationError { } } +#[doc(hidden)] +impl From for DeserializationError { + fn from(err: std::str::Utf8Error) -> Self { + Self::Utf8(err) + } +} + #[doc(hidden)] impl From for DeserializationError { fn from(err: serde_json::Error) -> Self { diff --git a/src/lib.rs b/src/lib.rs index 2dfcb7f0..fd9abb37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -352,7 +352,13 @@ mod tests { room_id: request_body.room_id, room_alias: { let segment = path_segments.get(5).unwrap().as_bytes(); - let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy(); + let decoded = match percent_encoding::percent_decode(segment).decode_utf8() + { + Ok(x) => x, + Err(err) => { + return Err(RequestDeserializationError::new(err, request).into()) + } + }; match serde_json::from_str(decoded.deref()) { Ok(id) => id, Err(err) => { From 6f5e25cb7d9c05a54df4df615773ba048cd945b4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 12 Mar 2020 21:38:21 +0100 Subject: [PATCH 260/295] Update dependencies --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e42db324..e889d814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,13 @@ percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.11.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.104", features = ["derive"], optional = true } -serde_json = "1.0.47" +serde_json = "1.0.48" serde_urlencoded = "0.6.1" -strum = "0.17.1" +strum = "0.18.0" url = { version = "2.1.1", optional = true } [dev-dependencies] -ruma-events = "0.16.0" +ruma-events = "0.17.0" [features] default = ["with-ruma-api-macros"] From 6a0ab987b546f5e6389a7af4834e71ab515b42fc Mon Sep 17 00:00:00 2001 From: "Ragotzy.devin" Date: Mon, 16 Mar 2020 20:07:12 -0400 Subject: [PATCH 261/295] Enable deserialization of unsuccessful responses Co-authored-by: Jonas Platte --- ruma-api-macros/src/api.rs | 42 +++++++++++++++++++-- src/error.rs | 76 ++++++++++++++++++++++---------------- src/lib.rs | 28 +++++++++++--- 3 files changed, 105 insertions(+), 41 deletions(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index d7741437..ab5cc91b 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -7,7 +7,7 @@ use quote::{quote, ToTokens}; use syn::{ braced, parse::{Parse, ParseStream}, - Field, FieldValue, Ident, Token, + Field, FieldValue, Ident, Token, Type, }; mod attribute; @@ -32,6 +32,8 @@ pub struct Api { request: Request, /// The `response` section of the macro. response: Response, + /// The `error` section of the macro. + error: Type, } impl TryFrom for Api { @@ -42,6 +44,9 @@ impl TryFrom for Api { metadata: raw_api.metadata.try_into()?, request: raw_api.request.try_into()?, response: raw_api.response.try_into()?, + error: raw_api + .error + .map_or(syn::parse_str::("ruma_api::error::Void").unwrap(), |err| err.ty), }; let newtype_body_field = res.request.newtype_body_field(); @@ -398,6 +403,8 @@ impl ToTokens for Api { ); let response_doc = format!("Data in the response from the `{}` API endpoint.", name); + let error = &self.error; + let api = quote! { use ruma_api::exports::serde::de::Error as _; use ruma_api::exports::serde::Deserialize as _; @@ -473,7 +480,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom>> for #response_try_from_type { - type Error = ruma_api::error::FromHttpResponseError; + type Error = ruma_api::error::FromHttpResponseError<#error>; #[allow(unused_variables)] fn try_from( @@ -488,13 +495,17 @@ impl ToTokens for Api { #response_init_fields }) } else { - Err(ruma_api::error::ServerError::new(response).into()) + match <#error as ruma_api::EndpointError>::try_from_response(response) { + Ok(err) => Err(ruma_api::error::ServerError::Known(err).into()), + Err(response_err) => Err(ruma_api::error::ServerError::Unknown(response_err).into()) + } } } } impl ruma_api::Endpoint for Request { type Response = Response; + type ResponseError = #error; /// Metadata for the `#name` endpoint. const METADATA: ruma_api::Metadata = ruma_api::Metadata { @@ -519,6 +530,7 @@ mod kw { custom_keyword!(metadata); custom_keyword!(request); custom_keyword!(response); + custom_keyword!(error); } /// The entire `ruma_api!` macro structure directly as it appears in the source code.. @@ -529,11 +541,18 @@ pub struct RawApi { pub request: RawRequest, /// The `response` section of the macro. pub response: RawResponse, + /// The `error` section of the macro. + pub error: Option, } impl Parse for RawApi { fn parse(input: ParseStream<'_>) -> syn::Result { - Ok(Self { metadata: input.parse()?, request: input.parse()?, response: input.parse()? }) + Ok(Self { + metadata: input.parse()?, + request: input.parse()?, + response: input.parse()?, + error: input.parse().ok(), + }) } } @@ -599,3 +618,18 @@ impl Parse for RawResponse { }) } } + +pub struct RawErrorType { + pub error_kw: kw::error, + pub ty: Type, +} + +impl Parse for RawErrorType { + fn parse(input: ParseStream<'_>) -> syn::Result { + let error_kw = input.parse::()?; + input.parse::()?; + let ty = input.parse()?; + + Ok(Self { error_kw, ty }) + } +} diff --git a/src/error.rs b/src/error.rs index b3d17c9d..69e265f9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,18 @@ use std::fmt::{self, Display, Formatter}; +// FIXME when `!` becomes stable use it +/// Default `ResponseError` for `ruma_api!` macro +#[derive(Clone, Copy, Debug)] +pub struct Void; + +impl crate::EndpointError for Void { + fn try_from_response( + response: http::Response>, + ) -> Result { + Err(ResponseDeserializationError::from_response(response)) + } +} /// An error when converting one of ruma's endpoint-specific request or response /// types to the corresponding http type. #[derive(Debug)] @@ -92,14 +104,14 @@ impl std::error::Error for RequestDeserializationError {} /// response types. #[derive(Debug)] #[non_exhaustive] -pub enum FromHttpResponseError { +pub enum FromHttpResponseError { /// Deserialization failed Deserialization(ResponseDeserializationError), /// The server returned a non-success status - Http(ServerError), + Http(ServerError), } -impl Display for FromHttpResponseError { +impl Display for FromHttpResponseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Deserialization(err) => write!(f, "deserialization failed: {}", err), @@ -108,13 +120,13 @@ impl Display for FromHttpResponseError { } } -impl From for FromHttpResponseError { - fn from(err: ServerError) -> Self { +impl From> for FromHttpResponseError { + fn from(err: ServerError) -> Self { Self::Http(err) } } -impl From for FromHttpResponseError { +impl From for FromHttpResponseError { fn from(err: ResponseDeserializationError) -> Self { Self::Deserialization(err) } @@ -123,7 +135,7 @@ impl From for FromHttpResponseError { /// An error that occurred when trying to deserialize a response. #[derive(Debug)] pub struct ResponseDeserializationError { - inner: DeserializationError, + inner: Option, http_response: http::Response>, } @@ -135,13 +147,25 @@ impl ResponseDeserializationError { inner: impl Into, http_response: http::Response>, ) -> Self { - Self { inner: inner.into(), http_response } + Self { inner: Some(inner.into()), http_response } + } + + /// This method is public so it is accessible from `ruma_api!` generated + /// code. It is not considered part of ruma-api's public API. + /// Creates an Error from a `http::Response`. + #[doc(hidden)] + pub fn from_response(http_response: http::Response>) -> Self { + Self { http_response, inner: None } } } impl Display for ResponseDeserializationError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.inner, f) + if let Some(ref inner) = self.inner { + Display::fmt(inner, f) + } else { + Display::fmt("deserialization error, no error specified", f) + } } } @@ -149,36 +173,24 @@ impl std::error::Error for ResponseDeserializationError {} /// An error was reported by the server (HTTP status code 4xx or 5xx) #[derive(Debug)] -pub struct ServerError { - http_response: http::Response>, +pub enum ServerError { + /// An error that is expected to happen under certain circumstances and + /// that has a well-defined structure + Known(E), + /// An error of unexpected type of structure + Unknown(ResponseDeserializationError), } -impl ServerError { - /// This method is public so it is accessible from `ruma_api!` generated - /// code. It is not considered part of ruma-api's public API. - #[doc(hidden)] - pub fn new(http_response: http::Response>) -> Self { - Self { http_response } - } - - /// Get the HTTP response without parsing its contents. - pub fn into_raw_reponse(self) -> http::Response> { - self.http_response - } -} - -impl Display for ServerError { +impl Display for ServerError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.http_response.status().canonical_reason() { - Some(reason) => { - write!(f, "HTTP status {} {}", self.http_response.status().as_str(), reason) - } - None => write!(f, "HTTP status {}", self.http_response.status().as_str()), + match self { + ServerError::Known(e) => Display::fmt(e, f), + ServerError::Unknown(res_err) => Display::fmt(res_err, f), } } } -impl std::error::Error for ServerError {} +impl std::error::Error for ServerError {} #[derive(Debug)] enum SerializationError { diff --git a/src/lib.rs b/src/lib.rs index fd9abb37..286cde4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -232,17 +232,32 @@ pub trait Outgoing { type Incoming; } +/// Gives users the ability to define their own serializable/deserializable errors. +pub trait EndpointError: Sized { + /// Tries to construct `Self` from an `http::Response`. + /// + /// This will always return `Err` variant when no `error` field is defined in + /// the `ruma_api` macro. + fn try_from_response( + response: http::Response>, + ) -> Result; +} + /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. pub trait Endpoint: Outgoing + TryInto>, Error = IntoHttpError> where ::Incoming: TryFrom>, Error = FromHttpRequestError>, - ::Incoming: - TryFrom>, Error = FromHttpResponseError>, + ::Incoming: TryFrom< + http::Response>, + Error = FromHttpResponseError<::ResponseError>, + >, { /// Data returned in a successful response from the endpoint. type Response: Outgoing + TryInto>, Error = IntoHttpError>; + /// Error type returned when response from endpoint fails. + type ResponseError: EndpointError; /// Metadata about the endpoint. const METADATA: Metadata; @@ -284,7 +299,7 @@ mod tests { use crate::{ error::{ FromHttpRequestError, FromHttpResponseError, IntoHttpError, - RequestDeserializationError, ServerError, + RequestDeserializationError, ServerError, Void, }, Endpoint, Metadata, Outgoing, }; @@ -302,6 +317,7 @@ mod tests { impl Endpoint for Request { type Response = Response; + type ResponseError = Void; const METADATA: Metadata = Metadata { description: "Add an alias to a room.", @@ -384,13 +400,15 @@ mod tests { } impl TryFrom>> for Response { - type Error = FromHttpResponseError; + type Error = FromHttpResponseError; fn try_from(http_response: http::Response>) -> Result { if http_response.status().as_u16() < 400 { Ok(Response) } else { - Err(FromHttpResponseError::Http(ServerError::new(http_response))) + Err(FromHttpResponseError::Http(ServerError::Unknown( + crate::error::ResponseDeserializationError::from_response(http_response), + ))) } } } From 220d733cc1450fa2323c96614bf274ca5f32c03b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 20 Mar 2020 11:50:13 +0100 Subject: [PATCH 262/295] Bump dependencies --- Cargo.toml | 2 +- ruma-api-macros/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e889d814..d7c72e0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.11.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } -serde = { version = "1.0.104", features = ["derive"], optional = true } +serde = { version = "1.0.105", features = ["derive"], optional = true } serde_json = "1.0.48" serde_urlencoded = "0.6.1" strum = "0.18.0" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 8f3716db..77deb4db 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -16,9 +16,9 @@ version = "0.11.0" edition = "2018" [dependencies] -proc-macro2 = "1.0.8" -quote = "1.0.2" -syn = { version = "1.0.14", features = ["full", "extra-traits"] } +proc-macro2 = "1.0.9" +quote = "1.0.3" +syn = { version = "1.0.16", features = ["full", "extra-traits"] } [lib] proc-macro = true From 9be124f826e3285719a31a1d9cc093b1dca2bd44 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 20 Mar 2020 11:51:45 +0100 Subject: [PATCH 263/295] Bump versions --- Cargo.toml | 4 ++-- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d7c72e0d..b64e38e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.14.0" +version = "0.15.0-dev.1" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.11.0", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.12.0-dev.1", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.105", features = ["derive"], optional = true } serde_json = "1.0.48" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 77deb4db..9e5eee83 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.11.0" +version = "0.12.0-dev.1" edition = "2018" [dependencies] From 58b142f3458c1c2866ce936b05a149afef2a703c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 29 Mar 2020 13:55:16 +0200 Subject: [PATCH 264/295] Update change logs --- CHANGELOG.md | 11 +++++++++++ ruma-api-macros/CHANGELOG.md | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 835c4f3c..97d735ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # [unreleased] +Breaking changes: + +* Emit an error on non-UTF8 characters in path segments + * Before, they would be replaced by the unknown character codepoint +* `FromHttpResponseError` now has a generic parameter for the expected type of + error the homeserver could return + +Improvements: + +* Enable deserialization of unsuccessful responses + # 0.14.0 Breaking changes: diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index caa30504..6c6a00ba 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,9 @@ # [unreleased] +Breaking changes: + +* Update code generation to match the changes in ruma-api 0.15.0 + # 0.11.0 Breaking changes: @@ -16,7 +20,7 @@ Improvements: Breaking changes: -* Updated code generation to match the changes in ruma-api 0.13.0 +* Update code generation to match the changes in ruma-api 0.13.0 # 0.9.1 From b389501465fe7229996f08caeecbdcb724a16b38 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 29 Mar 2020 13:55:54 +0200 Subject: [PATCH 265/295] Bump versions --- CHANGELOG.md | 2 ++ Cargo.toml | 4 ++-- ruma-api-macros/CHANGELOG.md | 2 ++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d735ca..818f5558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.15.0 + Breaking changes: * Emit an error on non-UTF8 characters in path segments diff --git a/Cargo.toml b/Cargo.toml index b64e38e3..6abd6731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.15.0-dev.1" +version = "0.15.0" edition = "2018" [dependencies] http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.12.0-dev.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.12.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.105", features = ["derive"], optional = true } serde_json = "1.0.48" diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 6c6a00ba..2e11cd0b 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,7 @@ # [unreleased] +# 0.12.0 + Breaking changes: * Update code generation to match the changes in ruma-api 0.15.0 diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 9e5eee83..0ce01077 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.12.0-dev.1" +version = "0.12.0" edition = "2018" [dependencies] From 111c33c29c1d3ef25f00287e177df3329a282781 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 3 Apr 2020 19:10:30 +0200 Subject: [PATCH 266/295] CI: Disable IRC notifications --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3569d047..0626a95f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,9 +31,3 @@ script: - cargo build --all --verbose - cargo test --all --verbose if: "type != push OR (tag IS blank AND branch = master)" -notifications: - email: false - irc: - channels: - - secure: "Zjx9oQaF5PYNCWHIWFAaESlUedHNdXmgT+QO35ETUNtg19w9GrNDAsOWHawuK5wLr8+NicDC0bopJ7fPIbnLz8cL/aluTl8bGlvBf7U+uqvdaxGT2TR0tntnWBzoqhYJVGM2ZspVMOloaJv7jpVdhLtde6w5KhouinaKagQxmX7Mr5ec5r9Xhwv1a72Bk6teLmEqypBmnuuskAD1MOk52piimNzNQIsqs4X+kg+7ZZ+Umx//PHTgh49HMtn/3IdmAZs3xVjrpkgbbQeDi6ynKvxpzS7obizbFB2uQRZedt/+dYVOb2vmVi9WtuNLosIyuwP6rR+A0AYe3pYDoOhUQ3ARb70kVzq9TaXcFXrxH+/Z2LWddmpVOhqDQhBW5S+b2MVXHf5a5yk6QVha68rywd9UPpD4dXsQIfCHZuQ9xLDhOkPbkiVtqSdsiYBjOF+JjFOTKMG7Dx6kACRo74/pwoMWzDDVC0HiSQdesowmoGltB9kKSyT3to651dqGH97iFLBSxsVNYWuAoO/hmLwskbW5EtqC2Crtz+A89KP3es7zsxMKloSOTzrAaYdMSkfDTI1lsHzpdGFMydDEimqRg60XM89CwxkejWPowKErp9kg+8XEh6J8s6W5c814P7oPYdQ9+DgwUr8qi3rrHqdVSa2maK+MLPK+A5riMiqpWqA=" - use_notice: true From 724f799bdf2729ac2332daab52acacb3746420be Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 10 Apr 2020 13:42:10 +0200 Subject: [PATCH 267/295] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a99907dc..2bfff935 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # ruma-api -[![Build Status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) +[![crates.io page](https://img.shields.io/crates/v/ruma-api.svg)](https://crates.io/crates/ruma-api) +[![docs.rs page](https://docs.rs/ruma-api/badge.svg)](https://docs.rs/ruma-api/) +[![build status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) +![license: MIT](https://img.shields.io/crates/l/ruma-api.svg) **ruma-api** contains core types used to define the requests and responses for each endpoint in the various [Matrix](https://matrix.org/) API specifications. These types can be shared by client and server code for all Matrix APIs. @@ -12,7 +15,3 @@ ruma-api requires Rust 1.39.0 or later. ## Documentation ruma-api has [comprehensive documentation](https://docs.rs/ruma-api) available on docs.rs. - -## License - -[MIT](http://opensource.org/licenses/MIT) From 2bbcfda3fd13399fc3ec38717bb86ba9a15ea2bc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Apr 2020 21:53:27 +0200 Subject: [PATCH 268/295] Don't use ruma_api_macros directly in ruma_api tests --- tests/ruma_api_macros.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 96d983f3..5b4f9948 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -67,7 +67,7 @@ pub mod some_endpoint { } pub mod newtype_body_endpoint { - use ruma_api_macros::ruma_api; + use ruma_api::ruma_api; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct MyCustomType { @@ -97,7 +97,7 @@ pub mod newtype_body_endpoint { } pub mod newtype_raw_body_endpoint { - use ruma_api_macros::ruma_api; + use ruma_api::ruma_api; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct MyCustomType { @@ -127,7 +127,7 @@ pub mod newtype_raw_body_endpoint { } pub mod query_map_endpoint { - use ruma_api_macros::ruma_api; + use ruma_api::ruma_api; ruma_api! { metadata { From 0238a72b590d3ff6542a71948f24e9c111621058 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 15 Apr 2020 22:04:27 +0200 Subject: [PATCH 269/295] Serialize an empty JSON object as body for responses without body fields --- ruma-api-macros/src/api/response.rs | 37 +++++++++++++---------------- tests/no_fields.rs | 33 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 tests/no_fields.rs diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 4b2cf89d..94a38921 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -100,10 +100,6 @@ impl Response { return quote_spanned!(span=> response.#field_name); } - if !self.has_body_fields() && self.newtype_body_field().is_none() { - return quote!(Vec::new()); - } - let body = if let Some(field) = self.newtype_body_field() { let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let span = field.span(); @@ -248,7 +244,7 @@ impl ToTokens for Response { quote! { { #(#fields),* } } }; - let response_body_struct = + let (derive_deserialize, def) = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let derive_deserialize = if body_field.has_wrap_incoming_attr() { @@ -257,7 +253,7 @@ impl ToTokens for Response { quote!(ruma_api::exports::serde::Deserialize) }; - Some((derive_deserialize, quote! { (#field); })) + (derive_deserialize, quote! { (#field); }) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { @@ -267,22 +263,21 @@ impl ToTokens for Response { }; let fields = fields.map(ResponseField::field); - Some((derive_deserialize, quote!({ #(#fields),* }))) + (derive_deserialize, quote!({ #(#fields),* })) } else { - None - } - .map(|(derive_deserialize, def)| { - quote! { - /// Data in the response body. - #[derive( - Debug, - ruma_api::Outgoing, - ruma_api::exports::serde::Serialize, - #derive_deserialize - )] - struct ResponseBody #def - } - }); + (TokenStream::new(), quote!({})) + }; + + let response_body_struct = quote! { + /// Data in the response body. + #[derive( + Debug, + ruma_api::Outgoing, + ruma_api::exports::serde::Serialize, + #derive_deserialize + )] + struct ResponseBody #def + }; let response = quote! { #[derive(Debug, Clone, ruma_api::Outgoing)] diff --git a/tests/no_fields.rs b/tests/no_fields.rs new file mode 100644 index 00000000..50e0227d --- /dev/null +++ b/tests/no_fields.rs @@ -0,0 +1,33 @@ +use std::convert::TryFrom; + +use ruma_api_macros::ruma_api; + +ruma_api! { + metadata { + description: "Does something.", + method: GET, + name: "no_fields", + path: "/_matrix/my/endpoint", + rate_limited: false, + requires_authentication: false, + } + + request {} + response {} +} + +#[test] +fn empty_request_http_repr() { + let req = Request {}; + let http_req = http::Request::>::try_from(req).unwrap(); + + assert!(http_req.body().is_empty()); +} + +#[test] +fn empty_response_http_repr() { + let res = Response {}; + let http_res = http::Response::>::try_from(res).unwrap(); + + assert_eq!(http_res.body(), b"{}"); +} From 5e62369a16c15447ee7bafa7e00189caca91e47b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 16 Apr 2020 01:43:01 +0200 Subject: [PATCH 270/295] Fix MSRV in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bfff935..be592881 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ These types can be shared by client and server code for all Matrix APIs. ## Minimum Rust version -ruma-api requires Rust 1.39.0 or later. +ruma-api requires Rust 1.40.0 or later. ## Documentation From 4c67b1a71d1f2b236f0610e994f03130d2f60274 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 16 Apr 2020 15:09:46 +0200 Subject: [PATCH 271/295] Update ruma-api-macros/README.md --- ruma-api-macros/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/ruma-api-macros/README.md b/ruma-api-macros/README.md index e9eb115e..88747d28 100644 --- a/ruma-api-macros/README.md +++ b/ruma-api-macros/README.md @@ -1,7 +1,5 @@ # ruma-api-macros -[![Build Status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) - **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. From ce1adb28273815fb1edac1106106b079efcae91f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 16 Apr 2020 16:59:48 +0200 Subject: [PATCH 272/295] Update dependencies --- Cargo.toml | 8 ++++---- ruma-api-macros/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6abd6731..6d601a9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,18 +16,18 @@ version = "0.15.0" edition = "2018" [dependencies] -http = "0.2.0" +http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.12.0", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } -serde = { version = "1.0.105", features = ["derive"], optional = true } -serde_json = "1.0.48" +serde = { version = "1.0.106", features = ["derive"], optional = true } +serde_json = "1.0.51" serde_urlencoded = "0.6.1" strum = "0.18.0" url = { version = "2.1.1", optional = true } [dev-dependencies] -ruma-events = "0.17.0" +ruma-events = "0.18.0" [features] default = ["with-ruma-api-macros"] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 0ce01077..51ee4df8 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -16,9 +16,9 @@ version = "0.12.0" edition = "2018" [dependencies] -proc-macro2 = "1.0.9" +proc-macro2 = "1.0.10" quote = "1.0.3" -syn = { version = "1.0.16", features = ["full", "extra-traits"] } +syn = { version = "1.0.17", features = ["full", "extra-traits"] } [lib] proc-macro = true From dc77869de0413bcb047563c28e626cec58b42840 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 16 Apr 2020 17:03:07 +0200 Subject: [PATCH 273/295] Bump versions, update change logs --- CHANGELOG.md | 6 ++++++ Cargo.toml | 4 ++-- ruma-api-macros/CHANGELOG.md | 8 ++++++++ ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 818f5558..76bd5d2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # [unreleased] +# 0.15.1 + +Bug fixes: + +* Write `{}` to the body of responses without body fields (fix from ruma-api-macros) + # 0.15.0 Breaking changes: diff --git a/Cargo.toml b/Cargo.toml index 6d601a9d..7f6887fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.15.0" +version = "0.15.1" edition = "2018" [dependencies] http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.12.0", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.15.1", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.14.1", optional = true } serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 2e11cd0b..37b70f22 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,5 +1,13 @@ # [unreleased] +# 0.15.1 + +*ruma-api-macros is now versioned in lockstep with ruma-api* + +Bug fixes: + +* Write `{}` to the body of responses without body fields + # 0.12.0 Breaking changes: diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 51ee4df8..7b6b270a 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.12.0" +version = "0.15.1" edition = "2018" [dependencies] From 8ba4fe435850e3b052892bfe677ef38ce31f0996 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 18:20:05 +0200 Subject: [PATCH 274/295] Update ruma-identifiers to 0.15.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7f6887fb..bede7502 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ edition = "2018" http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.15.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = { version = "0.14.1", optional = true } +ruma-identifiers = { version = "0.15.1", optional = true } serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" serde_urlencoded = "0.6.1" From cbdbc0c38fee683903d6b8701bc2276baf54a2a6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 19:17:26 +0200 Subject: [PATCH 275/295] Remove url crate dependency --- Cargo.toml | 2 - ruma-api-macros/src/api.rs | 122 ++++++++++++++++--------------------- src/lib.rs | 1 - tests/conversions.rs | 2 +- 4 files changed, 55 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bede7502..85a1355f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" serde_urlencoded = "0.6.1" strum = "0.18.0" -url = { version = "2.1.1", optional = true } [dev-dependencies] ruma-events = "0.18.0" @@ -36,7 +35,6 @@ with-ruma-api-macros = [ "ruma-api-macros", "ruma-identifiers", "serde", - "url", ] [workspace] diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index ab5cc91b..3bcd71ba 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -113,40 +113,52 @@ impl ToTokens for Api { TokenStream::new() }; - let (url_set_path, parse_request_path) = if self.request.has_path_fields() { - let path_str = path.value(); + let (request_path_string, parse_request_path) = if self.request.has_path_fields() { + let path_string = path.value(); - assert!(path_str.starts_with('/'), "path needs to start with '/'"); + assert!(path_string.starts_with('/'), "path needs to start with '/'"); assert!( - path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), + path_string.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_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..]; - let path_var_ident = Ident::new(path_var, Span::call_site()); - quote!(&request.#path_var_ident.to_string()) - } else { - quote!(#segment) - }; + let format_call = { + let mut format_string = path_string.clone(); + let mut format_args = Vec::new(); + + while let Some(start_of_segment) = format_string.find(':') { + // ':' should only ever appear at the start of a segment + assert_eq!(&format_string[start_of_segment - 1..start_of_segment], "/"); + + let end_of_segment = match format_string[start_of_segment..].find('/') { + Some(rel_pos) => start_of_segment + rel_pos, + None => format_string.len(), + }; + + let path_var = Ident::new( + &format_string[start_of_segment + 1..end_of_segment], + Span::call_site(), + ); + format_args.push(quote! { + ruma_api::exports::percent_encoding::utf8_percent_encode( + &request.#path_var.to_string(), + ruma_api::exports::percent_encoding::NON_ALPHANUMERIC, + ) + }); + format_string.replace_range(start_of_segment..end_of_segment, "{}"); + } quote! { - path_segments.push(#arg); + format!(#format_string, #(#format_args),*) } - }); - - let set_tokens = quote! { - // 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(); - #(#path_segment_push)* }; - let path_fields = path_segments.enumerate().filter(|(_, s)| s.starts_with(':')).map( - |(i, segment)| { + let path_fields = path_string[1..] + .split('/') + .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()); @@ -176,27 +188,18 @@ impl ToTokens for Api { } } } - }, - ); + }); - let parse_tokens = quote! { - #(#path_fields,)* - }; - - (set_tokens, parse_tokens) + (format_call, quote! { #(#path_fields,)* }) } else { - let set_tokens = quote! { - url.set_path(metadata.path); - }; - let parse_tokens = TokenStream::new(); - (set_tokens, parse_tokens) + (quote! { metadata.path.to_owned() }, TokenStream::new()) }; - let url_set_querystring = if let Some(field) = self.request.query_map_field() { + let request_query_string = if let Some(field) = self.request.query_map_field() { let field_name = field.ident.as_ref().expect("expected field to have identifier"); let field_type = &field.ty; - quote! { + quote!({ // This function exists so that the compiler will throw an // error when the type of the field with the query_map // attribute doesn't implement IntoIterator @@ -214,32 +217,22 @@ impl ToTokens for Api { assert_trait_impl::<#field_type>(); let request_query = RequestQuery(request.#field_name); - let query_str = ruma_api::exports::serde_urlencoded::to_string( - request_query, - )?; - - let query_opt: Option<&str> = if query_str.is_empty() { - None - } else { - Some(&query_str) - }; - - url.set_query(query_opt); - } + format!("?{}", ruma_api::exports::serde_urlencoded::to_string(request_query)?) + }) } else if self.request.has_query_fields() { let request_query_init_fields = self.request.request_query_init_fields(); - quote! { + quote!({ let request_query = RequestQuery { #request_query_init_fields }; - url.set_query(Some(&ruma_api::exports::serde_urlencoded::to_string( - request_query, - )?)); - } + format!("?{}", ruma_api::exports::serde_urlencoded::to_string(request_query)?) + }) } else { - TokenStream::new() + quote! { + String::new() + } }; let extract_request_query = if self.request.query_map_field().is_some() { @@ -440,21 +433,14 @@ impl ToTokens for Api { #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { let metadata = Request::METADATA; - - // Use dummy homeserver url which has to be overwritten in - // the calling code. Previously (with http::Uri) this was - // not required, but Url::parse only accepts absolute urls. - let mut url = - ruma_api::exports::url::Url::parse("http://invalid-host-please-change/") - .unwrap(); - - { #url_set_path } - { #url_set_querystring } - + let path_and_query = #request_path_string + &#request_query_string; let mut http_request = ruma_api::exports::http::Request::new(#request_body); *http_request.method_mut() = ruma_api::exports::http::Method::#method; - *http_request.uri_mut() = url.into_string().parse().unwrap(); + *http_request.uri_mut() = ruma_api::exports::http::uri::Builder::new() + .path_and_query(path_and_query.as_str()) + .build() + .unwrap(); { #add_headers_to_request } diff --git a/src/lib.rs b/src/lib.rs index 286cde4a..0c8e8ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -215,7 +215,6 @@ pub mod exports { pub use serde; pub use serde_json; pub use serde_urlencoded; - pub use url; } use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; diff --git a/tests/conversions.rs b/tests/conversions.rs index 55bdc578..3aa11675 100644 --- a/tests/conversions.rs +++ b/tests/conversions.rs @@ -43,7 +43,7 @@ fn request_serde() -> Result<(), Box> { world: "test".to_owned(), q1: "query_param_special_chars %/&@!".to_owned(), q2: 55, - bar: "bar".to_owned(), + bar: "barVal".to_owned(), baz: UserId::try_from("@bazme:ruma.io")?, }; From 28d836ec50ab7d656ba3eff175e01c1b16c6b6bb Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 19:22:06 +0200 Subject: [PATCH 276/295] Update change log, bump versions --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- ruma-api-macros/CHANGELOG.md | 12 +++--------- ruma-api-macros/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76bd5d2c..7072530f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # [unreleased] +# 0.16.0 + +Breaking changes: + +* Update ruma-identifiers to 0.15.1 + +Improvements: + +* Remove dependency on the `url` crate + # 0.15.1 Bug fixes: diff --git a/Cargo.toml b/Cargo.toml index 85a1355f..38102b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.15.1" +version = "0.16.0-rc.1" edition = "2018" [dependencies] diff --git a/ruma-api-macros/CHANGELOG.md b/ruma-api-macros/CHANGELOG.md index 37b70f22..6d0e534f 100644 --- a/ruma-api-macros/CHANGELOG.md +++ b/ruma-api-macros/CHANGELOG.md @@ -1,12 +1,6 @@ -# [unreleased] - -# 0.15.1 - -*ruma-api-macros is now versioned in lockstep with ruma-api* - -Bug fixes: - -* Write `{}` to the body of responses without body fields +Since version 0.15.1 of ruma-api, ruma-api-macros is versioned in lockstep with ruma-api. Since +ruma-api-macros cannot be used independently anyway, it no longer maintains a separate change log or +its own version. Instead, refer to ruma-api's change log for changes in versions 0.15.1 and above. # 0.12.0 diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 7b6b270a..e4f5cb30 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.15.1" +version = "0.16.0-rc.1" edition = "2018" [dependencies] From ef4edeb20d1f0f252e4902c59c228354e7c03f90 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 19:22:55 +0200 Subject: [PATCH 277/295] Fixup --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 38102b98..3fb740be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ edition = "2018" [dependencies] http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.15.1", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.16.0-rc.1", path = "ruma-api-macros", optional = true } ruma-identifiers = { version = "0.15.1", optional = true } serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" From 399b7ace57eb95169887b5da7841f8b75350252e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 17 Apr 2020 19:30:08 +0200 Subject: [PATCH 278/295] Fix ruma-identifiers being declared as an optional dependency --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fb740be..8d7b0db3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ edition = "2018" http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.16.0-rc.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = { version = "0.15.1", optional = true } +ruma-identifiers = "0.15.1" serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" serde_urlencoded = "0.6.1" @@ -33,7 +33,6 @@ default = ["with-ruma-api-macros"] with-ruma-api-macros = [ "percent-encoding", "ruma-api-macros", - "ruma-identifiers", "serde", ] From 1686afc8bd7c3fda6d4696a6fb3edccf0a30261a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 19 Apr 2020 20:07:36 +0200 Subject: [PATCH 279/295] Update ruma-events (dev-dep) to 0.19.0-rc.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8d7b0db3..ad836473 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ serde_urlencoded = "0.6.1" strum = "0.18.0" [dev-dependencies] -ruma-events = "0.18.0" +ruma-events = "0.19.0-rc.2" [features] default = ["with-ruma-api-macros"] From 21d6c890a7aefe347b96d76c426ba867dbe70525 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 19 Apr 2020 23:17:36 +0200 Subject: [PATCH 280/295] Update ruma-events to 0.19.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ad836473..12c12090 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ serde_urlencoded = "0.6.1" strum = "0.18.0" [dev-dependencies] -ruma-events = "0.19.0-rc.2" +ruma-events = "0.19.0" [features] default = ["with-ruma-api-macros"] From 3fb21b333074fac6c38fecbd43557d20120b6785 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 20 Apr 2020 15:56:08 +0200 Subject: [PATCH 281/295] Update ruma-identifiers, bump versions --- Cargo.toml | 6 +++--- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12c12090..cd9e86af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,14 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.1" +version = "0.16.0-rc.2" edition = "2018" [dependencies] http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.16.0-rc.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = "0.15.1" +ruma-api-macros = { version = "=0.16.0-rc.2", path = "ruma-api-macros", optional = true } +ruma-identifiers = "0.16.0" serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" serde_urlencoded = "0.6.1" diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index e4f5cb30..509d17f8 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.1" +version = "0.16.0-rc.2" edition = "2018" [dependencies] From 620ca0ebcfe7e045714e0da4b89a4c32fdc7ac69 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 22 Apr 2020 12:08:02 +0200 Subject: [PATCH 282/295] Don't require trait implementations in macro code --- ruma-api-macros/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index e580e286..570578e7 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -5,7 +5,6 @@ //! re-exports in ruma-api. Also note that for technical reasons, the //! `ruma_api!` macro is only documented in ruma-api, not here. -#![deny(missing_copy_implementations, missing_debug_implementations)] #![allow(clippy::cognitive_complexity)] #![recursion_limit = "256"] From df7914de1a6bbbdf6786a9fa85408a73a97faf61 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 22 Apr 2020 12:54:47 +0200 Subject: [PATCH 283/295] Remove Outgoing trait we won't need it anymore once `EventResult` is replaced with `EventJson` --- Cargo.toml | 2 +- ruma-api-macros/src/api.rs | 4 +- ruma-api-macros/src/api/request.rs | 24 +- ruma-api-macros/src/api/response.rs | 38 +--- ruma-api-macros/src/derive_outgoing.rs | 214 ------------------ .../src/derive_outgoing/wrap_incoming.rs | 58 ----- ruma-api-macros/src/lib.rs | 53 +---- src/lib.rs | 46 +--- tests/ruma_api_macros.rs | 27 +-- 9 files changed, 34 insertions(+), 432 deletions(-) delete mode 100644 ruma-api-macros/src/derive_outgoing.rs delete mode 100644 ruma-api-macros/src/derive_outgoing/wrap_incoming.rs diff --git a/Cargo.toml b/Cargo.toml index cd9e86af..b418e679 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ serde_urlencoded = "0.6.1" strum = "0.18.0" [dev-dependencies] -ruma-events = "0.19.0" +ruma-events = { git = "https://github.com/ruma/ruma-events", branch = "event-json" } [features] default = ["with-ruma-api-macros"] diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 3bcd71ba..8ebbbeb9 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -298,7 +298,7 @@ impl ToTokens for Api { let extract_request_body = if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { quote! { - let request_body: ::Incoming = + let request_body: RequestBody = match ruma_api::exports::serde_json::from_slice(request.body().as_slice()) { Ok(body) => body, Err(err) => { @@ -368,7 +368,7 @@ impl ToTokens for Api { || self.response.newtype_body_field().is_some() { quote! { - let response_body: ::Incoming = + let response_body: ResponseBody = match ruma_api::exports::serde_json::from_slice(response.body().as_slice()) { Ok(body) => body, Err(err) => { diff --git a/ruma-api-macros/src/api/request.rs b/ruma-api-macros/src/api/request.rs index c95bf4fc..408060fc 100644 --- a/ruma-api-macros/src/api/request.rs +++ b/ruma-api-macros/src/api/request.rs @@ -310,34 +310,21 @@ impl ToTokens for Request { let request_body_struct = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; - let derive_deserialize = if body_field.has_wrap_incoming_attr() { - TokenStream::new() - } else { - quote!(ruma_api::exports::serde::Deserialize) - }; - - Some((derive_deserialize, quote! { (#field); })) + Some(quote! { (#field); }) } else if self.has_body_fields() { let fields = self.fields.iter().filter(|f| f.is_body()); - let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { - TokenStream::new() - } else { - quote!(ruma_api::exports::serde::Deserialize) - }; let fields = fields.map(RequestField::field); - - Some((derive_deserialize, quote! { { #(#fields),* } })) + Some(quote! { { #(#fields),* } }) } else { None } - .map(|(derive_deserialize, def)| { + .map(|def| { quote! { /// Data in the request body. #[derive( Debug, - ruma_api::Outgoing, + ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize, - #derive_deserialize )] struct RequestBody #def } @@ -374,8 +361,7 @@ impl ToTokens for Request { }; let request = quote! { - #[derive(Debug, Clone, ruma_api::Outgoing)] - #[incoming_no_deserialize] + #[derive(Debug, Clone)] pub struct Request #request_def #request_body_struct diff --git a/ruma-api-macros/src/api/response.rs b/ruma-api-macros/src/api/response.rs index 94a38921..2bdef543 100644 --- a/ruma-api-macros/src/api/response.rs +++ b/ruma-api-macros/src/api/response.rs @@ -244,44 +244,28 @@ impl ToTokens for Response { quote! { { #(#fields),* } } }; - let (derive_deserialize, def) = - if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { - let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; - let derive_deserialize = if body_field.has_wrap_incoming_attr() { - TokenStream::new() - } else { - quote!(ruma_api::exports::serde::Deserialize) - }; - - (derive_deserialize, quote! { (#field); }) - } else if self.has_body_fields() { - let fields = self.fields.iter().filter(|f| f.is_body()); - let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { - TokenStream::new() - } else { - quote!(ruma_api::exports::serde::Deserialize) - }; - let fields = fields.map(ResponseField::field); - - (derive_deserialize, quote!({ #(#fields),* })) - } else { - (TokenStream::new(), quote!({})) - }; + let def = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { + let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; + quote! { (#field); } + } else if self.has_body_fields() { + let fields = self.fields.iter().filter_map(|f| f.as_body_field()); + quote!({ #(#fields),* }) + } else { + quote!({}) + }; let response_body_struct = quote! { /// Data in the response body. #[derive( Debug, - ruma_api::Outgoing, + ruma_api::exports::serde::Deserialize, ruma_api::exports::serde::Serialize, - #derive_deserialize )] struct ResponseBody #def }; let response = quote! { - #[derive(Debug, Clone, ruma_api::Outgoing)] - #[incoming_no_deserialize] + #[derive(Debug, Clone)] pub struct Response #response_def #response_body_struct diff --git a/ruma-api-macros/src/derive_outgoing.rs b/ruma-api-macros/src/derive_outgoing.rs deleted file mode 100644 index 0b1b0f51..00000000 --- a/ruma-api-macros/src/derive_outgoing.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::mem; - -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::{ - parse_quote, punctuated::Pair, spanned::Spanned, Attribute, Data, DeriveInput, Fields, - GenericArgument, Path, PathArguments, Type, TypePath, -}; - -mod wrap_incoming; - -use wrap_incoming::Meta; - -enum StructKind { - Struct, - Tuple, -} - -pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result { - if !input.generics.params.is_empty() { - return Err(syn::Error::new_spanned( - input.generics, - "derive(Outgoing) doesn't currently support types with generics!", - )); - } - - let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) { - TokenStream::new() - } else { - quote!(ruma_api::exports::serde::Deserialize) - }; - - let (mut fields, struct_kind): (Vec<_>, _) = match input.data { - Data::Enum(_) | Data::Union(_) => { - panic!("#[derive(Outgoing)] is only supported for structs") - } - Data::Struct(s) => match s.fields { - Fields::Named(fs) => { - (fs.named.into_pairs().map(Pair::into_value).collect(), StructKind::Struct) - } - Fields::Unnamed(fs) => { - (fs.unnamed.into_pairs().map(Pair::into_value).collect(), StructKind::Tuple) - } - Fields::Unit => return Ok(impl_outgoing_with_incoming_self(input.ident)), - }, - }; - - let mut any_attribute = false; - - for field in &mut fields { - let mut field_meta = None; - - let mut remaining_attrs = Vec::new(); - for attr in mem::replace(&mut field.attrs, Vec::new()) { - if let Some(meta) = Meta::from_attribute(&attr)? { - if field_meta.is_some() { - return Err(syn::Error::new_spanned( - attr, - "duplicate #[wrap_incoming] attribute", - )); - } - field_meta = Some(meta); - any_attribute = true; - } else { - remaining_attrs.push(attr); - } - } - field.attrs = remaining_attrs; - - if let Some(attr) = field_meta { - if let Some(type_to_wrap) = attr.type_to_wrap { - wrap_generic_arg(&type_to_wrap, &mut field.ty, attr.wrapper_type.as_ref())?; - } else { - wrap_ty(&mut field.ty, attr.wrapper_type)?; - } - } - } - - if !any_attribute { - return Ok(impl_outgoing_with_incoming_self(input.ident)); - } - - let vis = input.vis; - let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = input.ident); - let original_ident = input.ident; - let incoming_ident = format_ident!("Incoming{}", original_ident, span = Span::call_site()); - - let struct_def = match struct_kind { - StructKind::Struct => quote! { { #(#fields,)* } }, - StructKind::Tuple => quote! { ( #(#fields,)* ); }, - }; - - Ok(quote! { - #[doc = #doc] - #[derive(Debug, #derive_deserialize)] - #vis struct #incoming_ident #struct_def - - impl ruma_api::Outgoing for #original_ident { - type Incoming = #incoming_ident; - } - }) -} - -fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool { - for attr in attrs { - match &attr.path { - Path { leading_colon: None, segments } - if segments.len() == 1 && segments[0].ident == "incoming_no_deserialize" => - { - return true - } - _ => {} - } - } - - false -} - -fn impl_outgoing_with_incoming_self(ident: Ident) -> TokenStream { - quote! { - impl ruma_api::Outgoing for #ident { - type Incoming = Self; - } - } -} - -fn wrap_ty(ty: &mut Type, path: Option) -> syn::Result<()> { - if let Some(wrap_ty) = path { - *ty = parse_quote!(#wrap_ty<#ty>); - } else { - match ty { - Type::Path(TypePath { path, .. }) => { - let ty_ident = &mut path.segments.last_mut().unwrap().ident; - let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site()); - *ty_ident = parse_quote!(#ident); - } - _ => return Err(syn::Error::new_spanned(ty, "Can't wrap this type")), - } - } - - Ok(()) -} - -fn wrap_generic_arg(type_to_wrap: &Type, of: &mut Type, with: Option<&Path>) -> syn::Result<()> { - let mut span = None; - wrap_generic_arg_impl(type_to_wrap, of, with, &mut span)?; - - if span.is_some() { - Ok(()) - } else { - Err(syn::Error::new_spanned( - of, - format!( - "Couldn't find generic argument `{}` in this type", - type_to_wrap.to_token_stream() - ), - )) - } -} - -fn wrap_generic_arg_impl( - type_to_wrap: &Type, - of: &mut Type, - with: Option<&Path>, - span: &mut Option, -) -> syn::Result<()> { - // TODO: Support things like array types? - let ty_path = match of { - Type::Path(TypePath { path, .. }) => path, - _ => return Ok(()), - }; - - let args = match &mut ty_path.segments.last_mut().unwrap().arguments { - PathArguments::AngleBracketed(ab) => &mut ab.args, - _ => return Ok(()), - }; - - for arg in args.iter_mut() { - let ty = match arg { - GenericArgument::Type(ty) => ty, - _ => continue, - }; - - if ty == type_to_wrap { - if let Some(s) = span { - let mut error = syn::Error::new( - *s, - format!( - "`{}` found multiple times, this is not currently supported", - type_to_wrap.to_token_stream() - ), - ); - error.combine(syn::Error::new_spanned(ty, "second occurrence")); - return Err(error); - } - - *span = Some(ty.span()); - - if let Some(wrapper_type) = with { - *ty = parse_quote!(#wrapper_type<#ty>); - } else if let Type::Path(TypePath { path, .. }) = ty { - let ty_ident = &mut path.segments.last_mut().unwrap().ident; - let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site()); - *ty_ident = parse_quote!(#ident); - } else { - return Err(syn::Error::new_spanned(ty, "Can't wrap this type")); - } - } else { - wrap_generic_arg_impl(type_to_wrap, ty, with, span)?; - } - } - - Ok(()) -} diff --git a/ruma-api-macros/src/derive_outgoing/wrap_incoming.rs b/ruma-api-macros/src/derive_outgoing/wrap_incoming.rs deleted file mode 100644 index f13c6f3c..00000000 --- a/ruma-api-macros/src/derive_outgoing/wrap_incoming.rs +++ /dev/null @@ -1,58 +0,0 @@ -use syn::{ - parse::{Parse, ParseStream}, - Ident, Path, Type, -}; - -mod kw { - use syn::custom_keyword; - custom_keyword!(with); -} - -/// The inside of a `#[wrap_incoming]` attribute -#[derive(Default)] -pub struct Meta { - pub type_to_wrap: Option, - pub wrapper_type: Option, -} - -impl Meta { - /// Check if the given attribute is a wrap_incoming attribute. If it is, parse it. - pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { - if attr.path.is_ident("wrap_incoming") { - if attr.tokens.is_empty() { - Ok(Some(Self::default())) - } else { - attr.parse_args().map(Some) - } - } else { - Ok(None) - } - } -} - -impl Parse for Meta { - fn parse(input: ParseStream) -> syn::Result { - let mut type_to_wrap = None; - let mut wrapper_type = try_parse_wrapper_type(input)?; - - if wrapper_type.is_none() && input.peek(Ident) { - type_to_wrap = Some(input.parse()?); - wrapper_type = try_parse_wrapper_type(input)?; - } - - if input.is_empty() { - Ok(Self { type_to_wrap, wrapper_type }) - } else { - Err(input.error("expected end of attribute args")) - } - } -} - -fn try_parse_wrapper_type(input: ParseStream) -> syn::Result> { - if input.peek(kw::with) { - input.parse::()?; - Ok(Some(input.parse()?)) - } else { - Ok(None) - } -} diff --git a/ruma-api-macros/src/lib.rs b/ruma-api-macros/src/lib.rs index 570578e7..b8e39bde 100644 --- a/ruma-api-macros/src/lib.rs +++ b/ruma-api-macros/src/lib.rs @@ -14,15 +14,11 @@ use std::convert::TryFrom as _; use proc_macro::TokenStream; use quote::ToTokens; -use syn::{parse_macro_input, DeriveInput}; +use syn::parse_macro_input; -use self::{ - api::{Api, RawApi}, - derive_outgoing::expand_derive_outgoing, -}; +use self::api::{Api, RawApi}; mod api; -mod derive_outgoing; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { @@ -32,48 +28,3 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } - -/// Derive the `Outgoing` trait, possibly generating an 'Incoming' version of the struct this -/// derive macro is used on. Specifically, if no `#[wrap_incoming]` attribute is used on any of the -/// fields of the struct, this simple implementation will be generated: -/// -/// ```ignore -/// impl Outgoing for MyType { -/// type Incoming = Self; -/// } -/// ``` -/// -/// If, however, `#[wrap_incoming]` is used (which is the only reason you should ever use this -/// derive macro manually), a new struct `IncomingT` (where `T` is the type this derive is used on) -/// is generated, with all of the fields with `#[wrap_incoming]` replaced: -/// -/// ```ignore -/// #[derive(Outgoing)] -/// struct MyType { -/// pub foo: Foo, -/// #[wrap_incoming] -/// pub bar: Bar, -/// #[wrap_incoming(Baz)] -/// pub baz: Option, -/// #[wrap_incoming(with EventResult)] -/// pub x: XEvent, -/// #[wrap_incoming(YEvent with EventResult)] -/// pub ys: Vec, -/// } -/// -/// // generated -/// struct IncomingMyType { -/// pub foo: Foo, -/// pub bar: IncomingBar, -/// pub baz: Option, -/// pub x: EventResult, -/// pub ys: Vec>, -/// } -/// ``` -// TODO: Make it clear that `#[wrap_incoming]` and `#[wrap_incoming(Type)]` without the "with" part -// are (only) useful for fallible deserialization of nested structures. -#[proc_macro_derive(Outgoing, attributes(wrap_incoming, incoming_no_deserialize))] -pub fn derive_outgoing(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - expand_derive_outgoing(input).unwrap_or_else(|err| err.to_compile_error()).into() -} diff --git a/src/lib.rs b/src/lib.rs index 0c8e8ad4..0d0f1069 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,19 +191,9 @@ use http::Method; /// } /// } /// ``` -/// -/// ## Fallible deserialization -/// -/// All request and response types also derive [`Outgoing`][Outgoing]. As such, to allow fallible -/// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the -/// documentation for [the derive macro](derive.Outgoing.html). -// TODO: Explain the concept of fallible deserialization before jumping to `ruma_api::Outgoing` #[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; -#[cfg(feature = "with-ruma-api-macros")] -pub use ruma_api_macros::Outgoing; - pub mod error; /// This module is used to support the generated code from ruma-api-macros. /// It is not considered part of ruma-api's public API. @@ -219,18 +209,6 @@ pub mod exports { use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; -/// A type that can be sent to another party that understands the matrix protocol. If any of the -/// fields of `Self` don't implement serde's `Deserialize`, you can derive this trait to generate a -/// corresponding 'Incoming' type that supports deserialization. This is useful for things like -/// ruma_events' `EventResult` type. For more details, see the [derive macro's documentation][doc]. -/// -/// [doc]: derive.Outgoing.html -// TODO: Better explain how this trait relates to serde's traits -pub trait Outgoing { - /// The 'Incoming' variant of `Self`. - type Incoming; -} - /// Gives users the ability to define their own serializable/deserializable errors. pub trait EndpointError: Sized { /// Tries to construct `Self` from an `http::Response`. @@ -245,16 +223,14 @@ pub trait EndpointError: Sized { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: Outgoing + TryInto>, Error = IntoHttpError> -where - ::Incoming: TryFrom>, Error = FromHttpRequestError>, - ::Incoming: TryFrom< - http::Response>, - Error = FromHttpResponseError<::ResponseError>, - >, +pub trait Endpoint: + TryInto>, Error = IntoHttpError> + + TryFrom>, Error = FromHttpRequestError> { /// Data returned in a successful response from the endpoint. - type Response: Outgoing + TryInto>, Error = IntoHttpError>; + type Response: TryInto>, Error = IntoHttpError> + + TryFrom>, Error = FromHttpResponseError>; + /// Error type returned when response from endpoint fails. type ResponseError: EndpointError; @@ -300,7 +276,7 @@ mod tests { FromHttpRequestError, FromHttpResponseError, IntoHttpError, RequestDeserializationError, ServerError, Void, }, - Endpoint, Metadata, Outgoing, + Endpoint, Metadata, }; /// A request to create a new room alias. @@ -310,10 +286,6 @@ mod tests { pub room_alias: RoomAliasId, // path } - impl Outgoing for Request { - type Incoming = Self; - } - impl Endpoint for Request { type Response = Response; type ResponseError = Void; @@ -394,10 +366,6 @@ mod tests { #[derive(Clone, Copy, Debug)] pub struct Response; - impl Outgoing for Response { - type Incoming = Self; - } - impl TryFrom>> for Response { type Error = FromHttpResponseError; diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 5b4f9948..b4bf6434 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,7 +1,6 @@ pub mod some_endpoint { - use ruma_api::{ruma_api, Outgoing}; - use ruma_events::{collections::all, sticker::StickerEvent, tag::TagEvent, EventResult}; - use serde::Serialize; + use ruma_api::ruma_api; + use ruma_events::{collections::all, tag::TagEvent, EventJson}; ruma_api! { metadata { @@ -43,27 +42,13 @@ pub mod some_endpoint { #[serde(skip_serializing_if = "Option::is_none")] pub optional_flag: Option, - // This is how you usually use `#[wrap_incoming]` with event types - #[wrap_incoming(with EventResult)] - pub event: TagEvent, + // Use `EventJson` instead of the actual event to allow additional fields to be sent... + pub event: EventJson, - // Same for lists of events - #[wrap_incoming(all::RoomEvent with EventResult)] - pub list_of_events: Vec, - - // This is how `#[wrap_incoming]` is used with nested `EventResult`s - #[wrap_incoming] - pub object: ObjectContainingEvents, + // ... and to allow unknown events when the endpoint deals with event collections. + pub list_of_events: Vec>, } } - - #[derive(Clone, Debug, Serialize, Outgoing)] - pub struct ObjectContainingEvents { - #[wrap_incoming(TagEvent with EventResult)] - pub event_list_1: Vec, - #[wrap_incoming(StickerEvent with EventResult)] - pub event_list_2: Vec, - } } pub mod newtype_body_endpoint { From 85cef85b607af63d91ed3a557c836680a919aee1 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 22 Apr 2020 21:39:05 +0200 Subject: [PATCH 284/295] Update ruma-events, bump versions --- Cargo.toml | 6 +++--- ruma-api-macros/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b418e679..ecb85c39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.2" +version = "0.16.0-rc.3" edition = "2018" [dependencies] http = "0.2.1" percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.16.0-rc.2", path = "ruma-api-macros", optional = true } +ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros", optional = true } ruma-identifiers = "0.16.0" serde = { version = "1.0.106", features = ["derive"], optional = true } serde_json = "1.0.51" @@ -26,7 +26,7 @@ serde_urlencoded = "0.6.1" strum = "0.18.0" [dev-dependencies] -ruma-events = { git = "https://github.com/ruma/ruma-events", branch = "event-json" } +ruma-events = "0.21.0-beta.1" [features] default = ["with-ruma-api-macros"] diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 509d17f8..13da99ed 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.2" +version = "0.16.0-rc.3" edition = "2018" [dependencies] From 0c38d8aca5364fe63e5a504260646af0d413c553 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 24 Apr 2020 21:03:09 +0200 Subject: [PATCH 285/295] Switch CI from travis to builds.sr.ht --- .builds/beta.yml | 30 ++++++++++++++++++++++++++++++ .builds/msrv.yml | 26 ++++++++++++++++++++++++++ .builds/nightly.yml | 32 ++++++++++++++++++++++++++++++++ .builds/stable.yml | 32 ++++++++++++++++++++++++++++++++ .travis.yml | 33 --------------------------------- 5 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 .builds/beta.yml create mode 100644 .builds/msrv.yml create mode 100644 .builds/nightly.yml create mode 100644 .builds/stable.yml delete mode 100644 .travis.yml diff --git a/.builds/beta.yml b/.builds/beta.yml new file mode 100644 index 00000000..612008ec --- /dev/null +++ b/.builds/beta.yml @@ -0,0 +1,30 @@ +image: archlinux +packages: + - rustup +sources: + - https://github.com/ruma/ruma-api +tasks: + - rustup: | + # We specify --profile minimal because we'd otherwise download docs + rustup toolchain install beta --profile minimal -c rustfmt -c clippy + rustup default beta + - test: | + cd ruma-api + + # We don't want the build to stop on individual failure of independent + # tools, so capture tool exit codes and set the task exit code manually + set +e + + cargo fmt -- --check + fmt_exit=$? + + cargo clippy --all-targets --all-features -- -D warnings + clippy_exit=$? + + cargo test --no-default-features --verbose + test1_exit=$? + + cargo test --all-features --verbose + test2_exit=$? + + exit $(( $fmt_exit || $clippy_exit || $test1_exit || $test2_exit )) diff --git a/.builds/msrv.yml b/.builds/msrv.yml new file mode 100644 index 00000000..f2821317 --- /dev/null +++ b/.builds/msrv.yml @@ -0,0 +1,26 @@ +image: archlinux +packages: + - rustup +sources: + - https://github.com/ruma/ruma-api +tasks: + - rustup: | + # We specify --profile minimal because we'd otherwise download docs + rustup toolchain install 1.40.0 --profile minimal + rustup default 1.40.0 + - test: | + cd ruma-api + + # We don't want the build to stop on individual failure of independent + # tools, so capture tool exit codes and set the task exit code manually + set +e + + # Only make sure the code builds with the MSRV. Tests can require later + # Rust versions, don't compile or run them. + cargo build --no-default-features --verbose + build1_exit=$? + + cargo build --all-features --verbose + build2_exit=$? + + exit $(( $build1_exit || $build2_exit )) diff --git a/.builds/nightly.yml b/.builds/nightly.yml new file mode 100644 index 00000000..6b597d7d --- /dev/null +++ b/.builds/nightly.yml @@ -0,0 +1,32 @@ +image: archlinux +packages: + - rustup +sources: + - https://github.com/ruma/ruma-api +tasks: + - rustup: | + rustup toolchain install nightly --profile minimal + rustup default nightly + + # Try installing rustfmt & clippy for nightly, but don't fail the build + # if they are not available + rustup component add rustfmt || true + rustup component add clippy || true + - test: | + cd ruma-api + + # We don't want the build to stop on individual failure of independent + # tools, so capture tool exit codes and set the task exit code manually + set +e + + if ( rustup component list | grep -q rustfmt ); then + cargo fmt -- --check + fi + fmt_exit=$? + + if ( rustup component list | grep -q clippy ); then + cargo clippy --all-targets --all-features -- -D warnings + fi + clippy_exit=$? + + exit $(( $fmt_exit || $clippy_exit )) diff --git a/.builds/stable.yml b/.builds/stable.yml new file mode 100644 index 00000000..bca18aa7 --- /dev/null +++ b/.builds/stable.yml @@ -0,0 +1,32 @@ +image: archlinux +packages: + - rustup +sources: + - https://github.com/ruma/ruma-api +tasks: + - rustup: | + # We specify --profile minimal because we'd otherwise download docs + rustup toolchain install stable --profile minimal -c rustfmt -c clippy + rustup default stable + - test: | + cd ruma-api + + # We don't want the build to stop on individual failure of independent + # tools, so capture tool exit codes and set the task exit code manually + set +e + + cargo fmt -- --check + fmt_exit=$? + + cargo clippy --all-targets --all-features -- -D warnings + clippy_exit=$? + + cargo test --no-default-features --verbose + test1_exit=$? + + cargo test --all-features --verbose + test2_exit=$? + + exit $(( $fmt_exit || $clippy_exit || $test1_exit || $test2_exit )) + # TODO: Add audit task once cargo-audit binary releases are available. + # See https://github.com/RustSec/cargo-audit/issues/66 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0626a95f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: "rust" -cache: "cargo" -rust: - - 1.40.0 - - stable - - beta - - nightly -jobs: - allow_failures: - - rust: nightly - fast_finish: true - -before_script: - - rustup component add rustfmt - - rustup component add clippy || true - - | - if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then - cargo install --force cargo-audit - fi - - cargo generate-lockfile -script: - - | - if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then - cargo audit - fi - - cargo fmt --all -- --check - - | - if ( rustup component list | grep -q clippy ); then - cargo clippy --all --all-targets --all-features -- -D warnings - fi - - cargo build --all --verbose - - cargo test --all --verbose -if: "type != push OR (tag IS blank AND branch = master)" From f1789032fdc138d02d5be90bf2701333a6dd33e6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 24 Apr 2020 21:29:57 +0200 Subject: [PATCH 286/295] Remove travis badge from readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index be592881..a75064c3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![crates.io page](https://img.shields.io/crates/v/ruma-api.svg)](https://crates.io/crates/ruma-api) [![docs.rs page](https://docs.rs/ruma-api/badge.svg)](https://docs.rs/ruma-api/) -[![build status](https://travis-ci.org/ruma/ruma-api.svg?branch=master)](https://travis-ci.org/ruma/ruma-api) ![license: MIT](https://img.shields.io/crates/l/ruma-api.svg) **ruma-api** contains core types used to define the requests and responses for each endpoint in the various [Matrix](https://matrix.org/) API specifications. From 0efac871bc3bd5a6734b118e2a2e9f4fd589e8b1 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 24 Apr 2020 21:33:10 +0200 Subject: [PATCH 287/295] Remove the option to disable macro reexport --- Cargo.toml | 14 +++----------- src/lib.rs | 2 -- tests/no_fields.rs | 2 +- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ecb85c39..f6e95da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ edition = "2018" [dependencies] http = "0.2.1" -percent-encoding = { version = "2.1.0", optional = true } -ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros", optional = true } +percent-encoding = "2.1.0" +ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros" } ruma-identifiers = "0.16.0" -serde = { version = "1.0.106", features = ["derive"], optional = true } +serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.51" serde_urlencoded = "0.6.1" strum = "0.18.0" @@ -28,14 +28,6 @@ strum = "0.18.0" [dev-dependencies] ruma-events = "0.21.0-beta.1" -[features] -default = ["with-ruma-api-macros"] -with-ruma-api-macros = [ - "percent-encoding", - "ruma-api-macros", - "serde", -] - [workspace] members = [ "ruma-api-macros", diff --git a/src/lib.rs b/src/lib.rs index 0d0f1069..2d7e97bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,13 +191,11 @@ use http::Method; /// } /// } /// ``` -#[cfg(feature = "with-ruma-api-macros")] pub use ruma_api_macros::ruma_api; pub mod error; /// This module is used to support the generated code from ruma-api-macros. /// It is not considered part of ruma-api's public API. -#[cfg(feature = "with-ruma-api-macros")] #[doc(hidden)] pub mod exports { pub use http; diff --git a/tests/no_fields.rs b/tests/no_fields.rs index 50e0227d..e2508737 100644 --- a/tests/no_fields.rs +++ b/tests/no_fields.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use ruma_api_macros::ruma_api; +use ruma_api::ruma_api; ruma_api! { metadata { From e24826cea593c87125bc73fe66008675ace4e434 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 24 Apr 2020 21:40:06 +0200 Subject: [PATCH 288/295] Simplify CI scripts --- .builds/beta.yml | 9 +++------ .builds/msrv.yml | 12 +----------- .builds/stable.yml | 7 ++----- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/.builds/beta.yml b/.builds/beta.yml index 612008ec..c56e6ea1 100644 --- a/.builds/beta.yml +++ b/.builds/beta.yml @@ -21,10 +21,7 @@ tasks: cargo clippy --all-targets --all-features -- -D warnings clippy_exit=$? - cargo test --no-default-features --verbose - test1_exit=$? + cargo test --verbose + test_exit=$? - cargo test --all-features --verbose - test2_exit=$? - - exit $(( $fmt_exit || $clippy_exit || $test1_exit || $test2_exit )) + exit $(( $fmt_exit || $clippy_exit || $test_exit )) diff --git a/.builds/msrv.yml b/.builds/msrv.yml index f2821317..de504675 100644 --- a/.builds/msrv.yml +++ b/.builds/msrv.yml @@ -11,16 +11,6 @@ tasks: - test: | cd ruma-api - # We don't want the build to stop on individual failure of independent - # tools, so capture tool exit codes and set the task exit code manually - set +e - # Only make sure the code builds with the MSRV. Tests can require later # Rust versions, don't compile or run them. - cargo build --no-default-features --verbose - build1_exit=$? - - cargo build --all-features --verbose - build2_exit=$? - - exit $(( $build1_exit || $build2_exit )) + cargo build --verbose diff --git a/.builds/stable.yml b/.builds/stable.yml index bca18aa7..58a6ce91 100644 --- a/.builds/stable.yml +++ b/.builds/stable.yml @@ -21,12 +21,9 @@ tasks: cargo clippy --all-targets --all-features -- -D warnings clippy_exit=$? - cargo test --no-default-features --verbose - test1_exit=$? - cargo test --all-features --verbose - test2_exit=$? + test_exit=$? - exit $(( $fmt_exit || $clippy_exit || $test1_exit || $test2_exit )) + exit $(( $fmt_exit || $clippy_exit || $test_exit )) # TODO: Add audit task once cargo-audit binary releases are available. # See https://github.com/RustSec/cargo-audit/issues/66 From 3428e9dd928dcf06b53c1d0bbaaa2caed7149fb6 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 4 May 2020 15:46:24 +0000 Subject: [PATCH 289/295] Migrate to ruma-serde from serde_urlencoded --- Cargo.toml | 2 +- ruma-api-macros/src/api.rs | 10 +++++----- src/error.rs | 12 ++++++------ src/lib.rs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6e95da2..0c626c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,9 @@ http = "0.2.1" percent-encoding = "2.1.0" ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros" } ruma-identifiers = "0.16.0" +ruma-serde = "0.1.3" serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.51" -serde_urlencoded = "0.6.1" strum = "0.18.0" [dev-dependencies] diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 8ebbbeb9..bb2d6b8f 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -204,7 +204,7 @@ impl ToTokens for Api { // error when the type of the field with the query_map // attribute doesn't implement IntoIterator // - // This is necessary because the serde_urlencoded::to_string + // This is necessary because the ruma_serde::urlencoded::to_string // call will result in a runtime error when the type cannot be // encoded as a list key-value pairs (?key1=value1&key2=value2) // @@ -217,7 +217,7 @@ impl ToTokens for Api { assert_trait_impl::<#field_type>(); let request_query = RequestQuery(request.#field_name); - format!("?{}", ruma_api::exports::serde_urlencoded::to_string(request_query)?) + format!("?{}", ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?) }) } else if self.request.has_query_fields() { let request_query_init_fields = self.request.request_query_init_fields(); @@ -227,7 +227,7 @@ impl ToTokens for Api { #request_query_init_fields }; - format!("?{}", ruma_api::exports::serde_urlencoded::to_string(request_query)?) + format!("?{}", ruma_api::exports::ruma_serde::urlencoded::to_string(request_query)?) }) } else { quote! { @@ -237,7 +237,7 @@ impl ToTokens for Api { let extract_request_query = if self.request.query_map_field().is_some() { quote! { - let request_query = match ruma_api::exports::serde_urlencoded::from_str( + let request_query = match ruma_api::exports::ruma_serde::urlencoded::from_str( &request.uri().query().unwrap_or("") ) { Ok(query) => query, @@ -251,7 +251,7 @@ impl ToTokens for Api { } else if self.request.has_query_fields() { quote! { let request_query: RequestQuery = - match ruma_api::exports::serde_urlencoded::from_str( + match ruma_api::exports::ruma_serde::urlencoded::from_str( &request.uri().query().unwrap_or("") ) { Ok(query) => query, diff --git a/src/error.rs b/src/error.rs index 69e265f9..20ec0282 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,8 +29,8 @@ impl From for IntoHttpError { } #[doc(hidden)] -impl From for IntoHttpError { - fn from(err: serde_urlencoded::ser::Error) -> Self { +impl From for IntoHttpError { + fn from(err: ruma_serde::urlencoded::error::Error) -> Self { Self(SerializationError::Query(err)) } } @@ -195,7 +195,7 @@ impl std::error::Error for ServerError {} #[derive(Debug)] enum SerializationError { Json(serde_json::Error), - Query(serde_urlencoded::ser::Error), + Query(ruma_serde::urlencoded::error::Error), } /// This type is public so it is accessible from `ruma_api!` generated code. @@ -205,7 +205,7 @@ enum SerializationError { pub enum DeserializationError { Utf8(std::str::Utf8Error), Json(serde_json::Error), - Query(serde_urlencoded::de::Error), + Query(ruma_serde::urlencoded::de::Error), Ident(ruma_identifiers::Error), // String <> Enum conversion failed. This can currently only happen in path // segment deserialization @@ -239,8 +239,8 @@ impl From for DeserializationError { } #[doc(hidden)] -impl From for DeserializationError { - fn from(err: serde_urlencoded::de::Error) -> Self { +impl From for DeserializationError { + fn from(err: ruma_serde::urlencoded::de::Error) -> Self { Self::Query(err) } } diff --git a/src/lib.rs b/src/lib.rs index 2d7e97bd..ed681e3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,9 +200,9 @@ pub mod error; pub mod exports { pub use http; pub use percent_encoding; + pub use ruma_serde; pub use serde; pub use serde_json; - pub use serde_urlencoded; } use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; From fb265db5ebca32b60ed322f5cefd06cca5981f28 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 4 May 2020 17:47:52 +0200 Subject: [PATCH 290/295] Update dependencies --- Cargo.toml | 6 +++--- ruma-api-macros/Cargo.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0c626c78..288750b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,14 +19,14 @@ edition = "2018" http = "0.2.1" percent-encoding = "2.1.0" ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros" } -ruma-identifiers = "0.16.0" +ruma-identifiers = "0.16.1" ruma-serde = "0.1.3" serde = { version = "1.0.106", features = ["derive"] } -serde_json = "1.0.51" +serde_json = "1.0.52" strum = "0.18.0" [dev-dependencies] -ruma-events = "0.21.0-beta.1" +ruma-events = "0.21.0" [workspace] members = [ diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 13da99ed..8c3557da 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -16,9 +16,9 @@ version = "0.16.0-rc.3" edition = "2018" [dependencies] -proc-macro2 = "1.0.10" -quote = "1.0.3" -syn = { version = "1.0.17", features = ["full", "extra-traits"] } +proc-macro2 = "1.0.12" +quote = "1.0.4" +syn = { version = "1.0.18", features = ["full", "extra-traits"] } [lib] proc-macro = true From 4211eb4497cb5368c53c70841e4a0d0989a26574 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 4 May 2020 18:08:00 +0200 Subject: [PATCH 291/295] Annotate .unwrap()s --- ruma-api-macros/src/api.rs | 4 ++++ src/lib.rs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index bb2d6b8f..876b5ed5 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -440,6 +440,8 @@ impl ToTokens for Api { *http_request.uri_mut() = ruma_api::exports::http::uri::Builder::new() .path_and_query(path_and_query.as_str()) .build() + // The only way this can fail is if the path given in the API definition is + // invalid. It is okay to panic in that case. .unwrap(); { #add_headers_to_request } @@ -460,6 +462,8 @@ impl ToTokens for Api { .header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") #serialize_response_headers .body(#body) + // Since we require header names to come from the `http::header` module, + // this cannot fail. .unwrap(); Ok(response) } diff --git a/src/lib.rs b/src/lib.rs index ed681e3f..2259ba1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -315,7 +315,9 @@ mod tests { .method(metadata.method) .uri(path) .body(serde_json::to_vec(&request_body)?) - .expect("http request building to succeed"); + // this cannot fail because we don't give user-supplied data to any of the + // builder methods + .unwrap(); Ok(http_request) } From d560ed76ca82e7191464c33c3c41436bb124914d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 4 May 2020 18:16:53 +0200 Subject: [PATCH 292/295] Bump versions --- CHANGELOG.md | 2 +- Cargo.toml | 4 ++-- ruma-api-macros/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7072530f..38adf935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Breaking changes: -* Update ruma-identifiers to 0.15.1 +* Update ruma-identifiers to 0.16.1 Improvements: diff --git a/Cargo.toml b/Cargo.toml index 288750b5..28bca483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.3" +version = "0.16.0" edition = "2018" [dependencies] http = "0.2.1" percent-encoding = "2.1.0" -ruma-api-macros = { version = "=0.16.0-rc.3", path = "ruma-api-macros" } +ruma-api-macros = { version = "=0.16.0", path = "ruma-api-macros" } ruma-identifiers = "0.16.1" ruma-serde = "0.1.3" serde = { version = "1.0.106", features = ["derive"] } diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 8c3557da..3bd6b203 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0-rc.3" +version = "0.16.0" edition = "2018" [dependencies] From 78717b71bfd9d126c85749e7fde0e41ea15c55ff Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 4 May 2020 18:24:16 +0200 Subject: [PATCH 293/295] Update change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38adf935..8fa24392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Breaking changes: * Update ruma-identifiers to 0.16.1 +* Remove the `Outgoing` trait and update the `Endpoint` trait and code generation accordingly Improvements: From 4d702558bb357b0b964464d73367234b373b6287 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 14 May 2020 13:07:14 +0200 Subject: [PATCH 294/295] Update deps, bump versions --- CHANGELOG.md | 7 +++++++ Cargo.toml | 12 ++++++------ ruma-api-macros/Cargo.toml | 6 +++--- src/error.rs | 6 +++--- src/lib.rs | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa24392..3a0e9900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # [unreleased] +# 0.16.1 + +Bug fixes: + +* Update ruma-serde to 0.2.0, fixing some issues with query string deserialization (some issues + still remain but will be fixed in a semver-compatible version) + # 0.16.0 Breaking changes: diff --git a/Cargo.toml b/Cargo.toml index 28bca483..b813d604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,21 +12,21 @@ license = "MIT" name = "ruma-api" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0" +version = "0.16.1" edition = "2018" [dependencies] http = "0.2.1" percent-encoding = "2.1.0" -ruma-api-macros = { version = "=0.16.0", path = "ruma-api-macros" } +ruma-api-macros = { version = "=0.16.1", path = "ruma-api-macros" } ruma-identifiers = "0.16.1" -ruma-serde = "0.1.3" -serde = { version = "1.0.106", features = ["derive"] } -serde_json = "1.0.52" +ruma-serde = "0.2.0" +serde = { version = "1.0.110", features = ["derive"] } +serde_json = "1.0.53" strum = "0.18.0" [dev-dependencies] -ruma-events = "0.21.0" +ruma-events = "0.21.1" [workspace] members = [ diff --git a/ruma-api-macros/Cargo.toml b/ruma-api-macros/Cargo.toml index 3bd6b203..8a2f2c70 100644 --- a/ruma-api-macros/Cargo.toml +++ b/ruma-api-macros/Cargo.toml @@ -12,13 +12,13 @@ license = "MIT" name = "ruma-api-macros" readme = "README.md" repository = "https://github.com/ruma/ruma-api" -version = "0.16.0" +version = "0.16.1" edition = "2018" [dependencies] proc-macro2 = "1.0.12" -quote = "1.0.4" -syn = { version = "1.0.18", features = ["full", "extra-traits"] } +quote = "1.0.5" +syn = { version = "1.0.21", features = ["full", "extra-traits"] } [lib] proc-macro = true diff --git a/src/error.rs b/src/error.rs index 20ec0282..31233b62 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,8 +29,8 @@ impl From for IntoHttpError { } #[doc(hidden)] -impl From for IntoHttpError { - fn from(err: ruma_serde::urlencoded::error::Error) -> Self { +impl From for IntoHttpError { + fn from(err: ruma_serde::urlencoded::ser::Error) -> Self { Self(SerializationError::Query(err)) } } @@ -195,7 +195,7 @@ impl std::error::Error for ServerError {} #[derive(Debug)] enum SerializationError { Json(serde_json::Error), - Query(ruma_serde::urlencoded::error::Error), + Query(ruma_serde::urlencoded::ser::Error), } /// This type is public so it is accessible from `ruma_api!` generated code. diff --git a/src/lib.rs b/src/lib.rs index 2259ba1e..078f368c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. - +#![doc(html_favicon_url = "https://www.ruma.io/favicon.ico")] #![warn(rust_2018_idioms)] #![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)] From 2151711f64e99a5da370d48fa92795f2d4799866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Dom=C3=ADnguez?= Date: Sat, 23 May 2020 15:27:38 +0200 Subject: [PATCH 295/295] Add NonAuthEndpoint trait This is a way to mark any type implementing `Endpoint` as not requiring authentication information through the type system. This is useful for clients that build on top of the ruma crates to check this condition at compile time, avoiding a runtime error. --- ruma-api-macros/src/api.rs | 10 ++++++++++ src/lib.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 876b5ed5..e4a3a381 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -90,6 +90,14 @@ impl ToTokens for Api { let rate_limited = &self.metadata.rate_limited; let requires_authentication = &self.metadata.requires_authentication; + let non_auth_endpoint_impl = if requires_authentication.value { + quote! { + impl ruma_api::NonAuthEndpoint for Request {} + } + } else { + TokenStream::new() + }; + let request_type = &self.request; let response_type = &self.response; @@ -507,6 +515,8 @@ impl ToTokens for Api { requires_authentication: #requires_authentication, }; } + + #non_auth_endpoint_impl }; api.to_tokens(tokens); diff --git a/src/lib.rs b/src/lib.rs index 078f368c..4239732c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,6 +236,11 @@ pub trait Endpoint: const METADATA: Metadata; } +/// A Matrix API endpoint that doesn't require authentication. +/// +/// This marker trait is to indicate that a type implementing `Endpoint` doesn't require any authentication. +pub trait NonAuthEndpoint: Endpoint {} + /// Metadata about an API endpoint. #[derive(Clone, Debug)] pub struct Metadata {