From 0cf7c9aa3ab9104e348c8cbac07f7b8957384a0a Mon Sep 17 00:00:00 2001 From: Dominic Date: Sun, 6 Oct 2019 15:03:30 +0200 Subject: [PATCH] add derive macro for resource --- Cargo.lock | 2 +- example/Cargo.toml | 1 - example/src/main.rs | 21 ++++++---- gotham_restful/Cargo.toml | 5 ++- gotham_restful/src/lib.rs | 11 +++++ gotham_restful/src/openapi/types.rs | 4 +- gotham_restful_derive/src/lib.rs | 8 ++++ gotham_restful_derive/src/method.rs | 35 +++++++++++++++- gotham_restful_derive/src/resource.rs | 60 +++++++++++++++++++++++++++ 9 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 gotham_restful_derive/src/resource.rs diff --git a/Cargo.lock b/Cargo.lock index 0a2bcfc..da0438a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,7 +220,6 @@ dependencies = [ "fake 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "gotham 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "gotham_restful 0.0.1", - "gotham_restful_derive 0.0.1", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", @@ -358,6 +357,7 @@ dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "gotham 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "gotham_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gotham_restful_derive 0.0.1", "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/example/Cargo.toml b/example/Cargo.toml index 0226424..2601ef9 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -17,7 +17,6 @@ gitlab = { repository = "msrd0/gotham-restful", branch = "master" } fake = "2.2" gotham = "0.4" gotham_restful = { path = "../gotham_restful", features = ["openapi"] } -gotham_restful_derive = { path = "../gotham_restful_derive", features = ["openapi"] } log = "0.4" log4rs = { version = "0.8", features = ["console_appender"], default-features = false } serde = "1" diff --git a/example/src/main.rs b/example/src/main.rs index 6c706a2..fb291ff 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,5 +1,4 @@ #[macro_use] extern crate log; -#[macro_use] extern crate gotham_restful_derive; use fake::{faker::internet::en::Username, Fake}; use gotham::{ @@ -17,13 +16,19 @@ use log4rs::{ }; use serde::{Deserialize, Serialize}; -rest_resource!{Users, route => { - route.read_all::(); - route.read::(); - route.create::(); - route.update_all::(); - route.update::(); -}} +#[derive(Resource)] +#[rest_resource(ReadAll, Read, Create, DeleteAll, Delete, Update, UpdateAll)] +struct Users +{ +} + +// rest_resource!{Users, route => { +// route.read_all::(); +// route.read::(); +// route.create::(); +// route.update_all::(); +// route.update::(); +// }} #[derive(Deserialize, OpenapiType, Serialize)] struct User diff --git a/gotham_restful/Cargo.toml b/gotham_restful/Cargo.toml index 0b6b146..21d816b 100644 --- a/gotham_restful/Cargo.toml +++ b/gotham_restful/Cargo.toml @@ -21,6 +21,7 @@ failure = "0.1" futures = "0.1" gotham = "0.4" gotham_derive = "0.4" +gotham_restful_derive = { path = "../gotham_restful_derive" } hyper = "0.12" indexmap = { version = "1.0", optional = true } log = { version = "0.4", optional = true } @@ -30,5 +31,5 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" [features] -default = ["openapi", "chrono"] -openapi = ["indexmap", "log", "openapiv3"] +default = [] +openapi = ["gotham_restful_derive/openapi", "indexmap", "log", "openapiv3"] diff --git a/gotham_restful/src/lib.rs b/gotham_restful/src/lib.rs index d90d932..9204f42 100644 --- a/gotham_restful/src/lib.rs +++ b/gotham_restful/src/lib.rs @@ -4,6 +4,17 @@ pub use hyper::StatusCode; use serde::{de::DeserializeOwned, Serialize}; +pub use gotham_restful_derive::*; +/// Not public API +#[doc(hidden)] +pub mod export +{ + #[cfg(feature = "openapi")] + pub use indexmap::IndexMap; + #[cfg(feature = "openapi")] + pub use openapiv3; +} + pub mod helper; #[cfg(feature = "openapi")] diff --git a/gotham_restful/src/openapi/types.rs b/gotham_restful/src/openapi/types.rs index f0167f0..e380454 100644 --- a/gotham_restful/src/openapi/types.rs +++ b/gotham_restful/src/openapi/types.rs @@ -5,8 +5,10 @@ use chrono::{ use indexmap::IndexMap; use openapiv3::{ ArrayType, IntegerType, NumberType, ObjectType, ReferenceOr::Item, ReferenceOr::Reference, Schema, - SchemaData, SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty + SchemaData, SchemaKind, StringType, Type }; +#[cfg(feature = "chrono")] +use openapiv3::{StringFormat, VariantOrUnknownOrEmpty}; #[derive(Debug, Clone, PartialEq)] pub struct OpenapiSchema diff --git a/gotham_restful_derive/src/lib.rs b/gotham_restful_derive/src/lib.rs index b6477f6..29d3106 100644 --- a/gotham_restful_derive/src/lib.rs +++ b/gotham_restful_derive/src/lib.rs @@ -4,6 +4,8 @@ use proc_macro::TokenStream; mod method; use method::{expand_method, Method}; +mod resource; +use resource::expand_resource; #[cfg(feature = "openapi")] mod openapi_type; @@ -14,6 +16,12 @@ pub fn derive_openapi_type(tokens : TokenStream) -> TokenStream openapi_type::expand(tokens) } +#[proc_macro_derive(Resource, attributes(rest_resource))] +pub fn derive_resource(tokens : TokenStream) -> TokenStream +{ + expand_resource(tokens) +} + #[proc_macro_attribute] pub fn rest_read_all(attr : TokenStream, item : TokenStream) -> TokenStream { diff --git a/gotham_restful_derive/src/method.rs b/gotham_restful_derive/src/method.rs index 010246f..b4fcc4c 100644 --- a/gotham_restful_derive/src/method.rs +++ b/gotham_restful_derive/src/method.rs @@ -7,6 +7,7 @@ use syn::{ ReturnType, parse_macro_input }; +use std::str::FromStr; pub enum Method { @@ -19,9 +20,27 @@ pub enum Method Delete } +impl FromStr for Method +{ + type Err = String; + fn from_str(str : &str) -> Result + { + match str { + "ReadAll" | "read_all" => Ok(Self::ReadAll), + "Read" | "read" => Ok(Self::Read), + "Create" | "create" => Ok(Self::Create), + "UpdateAll" | "update_all" => Ok(Self::UpdateAll), + "Update" | "update" => Ok(Self::Update), + "DeleteAll" | "delete_all" => Ok(Self::DeleteAll), + "Delete" | "delete" => Ok(Self::Delete), + _ => Err("unknown method".to_string()) + } + } +} + impl Method { - fn trait_ident(&self) -> Ident + pub fn trait_ident(&self) -> Ident { use Method::*; @@ -37,7 +56,7 @@ impl Method format_ident!("Resource{}", name) } - fn fn_ident(&self) -> Ident + pub fn fn_ident(&self) -> Ident { use Method::*; @@ -52,6 +71,11 @@ impl Method }; format_ident!("{}", name) } + + pub fn setup_ident(&self) -> Ident + { + format_ident!("{}_setup_impl", self.fn_ident()) + } } pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) -> TokenStream @@ -87,6 +111,7 @@ pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) - let trait_ident = method.trait_ident(); let fn_ident = method.fn_ident(); + let setup_ident = method.setup_ident(); let output = quote! { impl ::gotham_restful::#trait_ident<#(#generics),*> for #ident @@ -98,6 +123,12 @@ pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) - #ret_stmt } } + + #[deny(dead_code)] + fn #setup_ident(route : &mut D) + { + route.#fn_ident::<#ident, #(#generics),*>(); + } }; output.into() } diff --git a/gotham_restful_derive/src/resource.rs b/gotham_restful_derive/src/resource.rs new file mode 100644 index 0000000..a2ce5e2 --- /dev/null +++ b/gotham_restful_derive/src/resource.rs @@ -0,0 +1,60 @@ +use crate::method::Method; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream, Result as SynResult}, + punctuated::Punctuated, + token::Comma, + Ident, + ItemStruct, + parenthesized, + parse_macro_input +}; +use std::str::FromStr; + +struct MethodList(Punctuated); + +impl Parse for MethodList +{ + fn parse(input: ParseStream) -> SynResult + { + let content; + let _paren = parenthesized!(content in input); + let list : Punctuated = Punctuated::parse_separated_nonempty(&content)?; + Ok(Self(list)) + } +} + +pub fn expand_resource(tokens : TokenStream) -> TokenStream +{ + let input = parse_macro_input!(tokens as ItemStruct); + let ident = input.ident; + + let methods : Vec = input.attrs.into_iter().filter(|attr| + attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("rest_resource".to_string()) // TODO wtf + ).flat_map(|attr| { + let m : MethodList = syn::parse2(attr.tokens).expect("unable to parse attributes"); + m.0.into_iter() + }).map(|method| { + let method = Method::from_str(&method.to_string()).expect("unknown method"); + let ident = method.setup_ident(); + quote!(#ident(&mut route);) + }).collect(); + + let output = quote! { + impl ::gotham_restful::Resource for #ident + { + fn name() -> String + { + stringify!(#ident).to_string() + } + + fn setup(mut route : D) + { + #(#methods)* + } + } + }; + output.into() +}