diff --git a/Cargo.lock b/Cargo.lock index 944d6ed..0a2bcfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,19 @@ dependencies = [ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "example" +version = "0.0.1" +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)", +] + [[package]] name = "failure" version = "0.1.5" @@ -328,19 +341,26 @@ dependencies = [ ] [[package]] -name = "gotham-restful" +name = "gotham_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gotham_restful" version = "0.0.1" dependencies = [ "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "fake 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", - "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "openapiv3 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", @@ -348,12 +368,15 @@ dependencies = [ ] [[package]] -name = "gotham_derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "gotham_restful_derive" +version = "0.0.1" dependencies = [ - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "fake 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "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)", + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0803aff..a7b19e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,8 @@ # -*- eval: (cargo-minor-mode 1) -*- -[package] -name = "gotham-restful" -version = "0.0.1" -authors = ["Dominic Meiser "] -edition = "2018" -description = "RESTful additions for Gotham" -keywords = ["gotham", "rest", "restful"] -license = "EPL-2.0" -readme = "README.md" -include = ["src/**/*", "Cargo.toml", "LICENSE"] -repository = "https://gitlab.com/msrd0/gotham-restful" - -[badges] -gitlab = { repository = "msrd0/gotham-restful", branch = "master" } - -[dependencies] -chrono = { version = "0.4", optional = true } -failure = "0.1" -futures = "0.1" -gotham = "0.4" -gotham_derive = "0.4" -hyper = "0.12" -indexmap = { version = "1.0", optional = true } -log = { version = "0.4", optional = true } -mime = "0.3" -openapiv3 = { version = "0.3", optional = true } -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[dev-dependencies] -fake = "2.2" -log = "0.4" -log4rs = { version = "0.8", features = ["console_appender"], default-features = false } - -[features] -default = ["openapi", "chrono"] -openapi = ["indexmap", "log", "openapiv3"] +[workspace] +members = [ + "gotham_restful", + "gotham_restful_derive", + "example" +] diff --git a/example/Cargo.toml b/example/Cargo.toml new file mode 100644 index 0000000..0226424 --- /dev/null +++ b/example/Cargo.toml @@ -0,0 +1,28 @@ +# -*- eval: (cargo-minor-mode 1) -*- + +[package] +name = "example" +version = "0.0.1" +authors = ["Dominic Meiser "] +edition = "2018" +license = "Unlicense" +readme = "README.md" +include = ["src/**/*", "Cargo.toml", "LICENSE"] +repository = "https://gitlab.com/msrd0/gotham-restful" + +[badges] +gitlab = { repository = "msrd0/gotham-restful", branch = "master" } + +[dependencies] +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" + +[dev-dependencies] +fake = "2.2" +log = "0.4" +log4rs = { version = "0.8", features = ["console_appender"], default-features = false } diff --git a/example/LICENSE b/example/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/example/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to diff --git a/examples/users.rs b/example/src/main.rs similarity index 95% rename from examples/users.rs rename to example/src/main.rs index 25c6fb9..631368e 100644 --- a/examples/users.rs +++ b/example/src/main.rs @@ -1,4 +1,5 @@ #[macro_use] extern crate log; +#[macro_use] extern crate gotham_restful_derive; use fake::{faker::internet::en::Username, Fake}; use gotham::{ @@ -14,6 +15,7 @@ use log4rs::{ config::{Appender, Config, Root}, encode::pattern::PatternEncoder }; +use serde::{Deserialize, Serialize}; rest_resource!{Users, route => { route.read_all::(); @@ -23,9 +25,10 @@ rest_resource!{Users, route => { route.update::(); }} -rest_struct!{User { +#[derive(Deserialize, OpenapiType, Serialize)] +struct User { username : String -}} +} impl ResourceReadAll>>> for Users { diff --git a/gotham_restful/Cargo.toml b/gotham_restful/Cargo.toml new file mode 100644 index 0000000..0b6b146 --- /dev/null +++ b/gotham_restful/Cargo.toml @@ -0,0 +1,34 @@ +# -*- eval: (cargo-minor-mode 1) -*- + +[package] +name = "gotham_restful" +version = "0.0.1" +authors = ["Dominic Meiser "] +edition = "2018" +description = "RESTful additions for Gotham" +keywords = ["gotham", "rest", "restful"] +license = "EPL-2.0" +readme = "../README.md" +include = ["src/**/*", "Cargo.toml", "../LICENSE"] +repository = "https://gitlab.com/msrd0/gotham-restful" + +[badges] +gitlab = { repository = "msrd0/gotham-restful", branch = "master" } + +[dependencies] +chrono = { version = "0.4", optional = true } +failure = "0.1" +futures = "0.1" +gotham = "0.4" +gotham_derive = "0.4" +hyper = "0.12" +indexmap = { version = "1.0", optional = true } +log = { version = "0.4", optional = true } +mime = "0.3" +openapiv3 = { version = "0.3", optional = true } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[features] +default = ["openapi", "chrono"] +openapi = ["indexmap", "log", "openapiv3"] diff --git a/src/helper.rs b/gotham_restful/src/helper.rs similarity index 100% rename from src/helper.rs rename to gotham_restful/src/helper.rs diff --git a/src/lib.rs b/gotham_restful/src/lib.rs similarity index 100% rename from src/lib.rs rename to gotham_restful/src/lib.rs diff --git a/src/openapi/mod.rs b/gotham_restful/src/openapi/mod.rs similarity index 100% rename from src/openapi/mod.rs rename to gotham_restful/src/openapi/mod.rs diff --git a/src/openapi/router.rs b/gotham_restful/src/openapi/router.rs similarity index 100% rename from src/openapi/router.rs rename to gotham_restful/src/openapi/router.rs diff --git a/src/openapi/types.rs b/gotham_restful/src/openapi/types.rs similarity index 100% rename from src/openapi/types.rs rename to gotham_restful/src/openapi/types.rs diff --git a/src/resource.rs b/gotham_restful/src/resource.rs similarity index 100% rename from src/resource.rs rename to gotham_restful/src/resource.rs diff --git a/src/result.rs b/gotham_restful/src/result.rs similarity index 100% rename from src/result.rs rename to gotham_restful/src/result.rs diff --git a/src/routing.rs b/gotham_restful/src/routing.rs similarity index 100% rename from src/routing.rs rename to gotham_restful/src/routing.rs diff --git a/gotham_restful_derive/Cargo.toml b/gotham_restful_derive/Cargo.toml new file mode 100644 index 0000000..0f78279 --- /dev/null +++ b/gotham_restful_derive/Cargo.toml @@ -0,0 +1,33 @@ +# -*- eval: (cargo-minor-mode 1) -*- + +[package] +name = "gotham_restful_derive" +version = "0.0.1" +authors = ["Dominic Meiser "] +edition = "2018" +description = "RESTful additions for Gotham - Derive" +keywords = ["gotham", "rest", "restful", "derive"] +license = "EPL-2.0" +readme = "../README.md" +include = ["src/**/*", "Cargo.toml", "../LICENSE"] +repository = "https://gitlab.com/msrd0/gotham-restful" + +[lib] +proc-macro = true + +[badges] +gitlab = { repository = "msrd0/gotham-restful", branch = "master" } + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "1", features = ["extra-traits", "full"] } + +[dev-dependencies] +fake = "2.2" +log = "0.4" +log4rs = { version = "0.8", features = ["console_appender"], default-features = false } + +[features] +default = ["openapi"] +openapi = [] diff --git a/gotham_restful_derive/src/lib.rs b/gotham_restful_derive/src/lib.rs new file mode 100644 index 0000000..3cddc5c --- /dev/null +++ b/gotham_restful_derive/src/lib.rs @@ -0,0 +1,13 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[cfg(feature = "openapi")] +mod openapi_type; + +#[cfg(feature = "openapi")] +#[proc_macro_derive(OpenapiType)] +pub fn derive_openapi_type(tokens : TokenStream) -> TokenStream +{ + openapi_type::expand(tokens) +} diff --git a/gotham_restful_derive/src/openapi_type.rs b/gotham_restful_derive/src/openapi_type.rs new file mode 100644 index 0000000..d24753c --- /dev/null +++ b/gotham_restful_derive/src/openapi_type.rs @@ -0,0 +1,99 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{ + Field, + Fields, + ItemStruct, + parse_macro_input +}; + +fn expand_field(field : &Field) -> TokenStream2 +{ + let ident = match &field.ident { + Some(ident) => ident, + None => panic!("Fields without ident are not supported") + }; + let ty = &field.ty; + + quote! {{ + let mut schema = <#ty>::to_schema(); + + if schema.nullable + { + schema.nullable = false; + schema.name = schema.name.map(|name| + if name.ends_with("OrNull") { name[..(name.len()-6)].to_string() } else { name }); + } + else + { + required.push(stringify!(#ident).to_string()); + } + + match schema.name.clone() { + Some(name) => { + properties.insert( + stringify!(#ident).to_string(), + ReferenceOr::Reference { reference: format!("#/components/schemas/{}", name) } + ); + dependencies.insert(name, schema); + }, + None => { + properties.insert( + stringify!(#ident).to_string(), + ReferenceOr::Item(Box::new(<#ty>::to_schema().to_schema())) + ); + } + } + }} +} + +pub fn expand(tokens : proc_macro::TokenStream) -> TokenStream +{ + let input = parse_macro_input!(tokens as ItemStruct); + + let ident = input.ident; + let generics = input.generics; + + let fields : Vec = match input.fields { + Fields::Named(fields) => { + fields.named.iter().map(|field| expand_field(field)).collect() + }, + Fields::Unnamed(_) => panic!("Unnamed fields are not supported"), + Fields::Unit => Vec::new() + }; + + let output = quote!{ + impl #generics ::gotham_restful::OpenapiType for #ident #generics + { + fn to_schema() -> ::gotham_restful::OpenapiSchema + { + use ::gotham_restful::{helper::openapi::*, OpenapiSchema}; + + let mut properties : IndexMap>> = IndexMap::new(); + let mut required : Vec = Vec::new(); + let mut dependencies : IndexMap = IndexMap::new(); + + #(#fields)* + + let schema = SchemaKind::Type(Type::Object(ObjectType { + properties, + required, + additional_properties: None, + min_properties: None, + max_properties: None + })); + + OpenapiSchema { + name: Some(stringify!(#ident).to_string()), + nullable: false, + schema, + dependencies + } + } + } + }; + + eprintln!("output: {}", output); + output.into() +}