Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit cda4c520f19517867667b9f4a96cee19a14f1934
parent 34421f13f3265221fe0ebda72c104f9ce3c303d9
Author: Divyahans Gupta <divyahansg@gmail.com>
Date:   Wed, 24 Oct 2018 22:14:05 -0700

Add derive for 'UriDisplay'.

Diffstat:
Mcore/codegen/src/derive/mod.rs | 1+
Acore/codegen/src/derive/uri_display.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/codegen/src/lib.rs | 5+++++
Mcore/codegen/tests/route.rs | 12++----------
Mcore/codegen/tests/typed-uris.rs | 13++-----------
5 files changed, 71 insertions(+), 21 deletions(-)

diff --git a/core/codegen/src/derive/mod.rs b/core/codegen/src/derive/mod.rs @@ -1,3 +1,4 @@ pub mod from_form; pub mod from_form_value; pub mod responder; +pub mod uri_display; diff --git a/core/codegen/src/derive/uri_display.rs b/core/codegen/src/derive/uri_display.rs @@ -0,0 +1,61 @@ +use proc_macro::{Span, TokenStream}; +use derive_utils::*; + +const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not allowed"; +const NO_NULLARY: &str = "nullary items are not allowed"; +const NO_EMPTY_ENUMS: &str = "empty enums are not allowed"; +const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one field"; + +fn validate_fields(fields: Fields, parent_span: Span) -> Result<()> { + if fields.count() == 0 { + return Err(parent_span.error(NO_EMPTY_FIELDS)) + } else if fields.are_unnamed() && fields.count() > 1 { + return Err(fields.span().error(ONLY_ONE_UNNAMED)); + } else if fields.are_unit() { + return Err(parent_span.error(NO_NULLARY)); + } + + Ok(()) +} + +fn validate_struct(gen: &DeriveGenerator, data: Struct) -> Result<()> { + validate_fields(data.fields(), gen.input.span()) +} + +fn validate_enum(gen: &DeriveGenerator, data: Enum) -> Result<()> { + if data.variants().count() == 0 { + return Err(gen.input.span().error(NO_EMPTY_ENUMS)); + } + + for variant in data.variants() { + validate_fields(variant.fields(), variant.span())?; + } + + Ok(()) +} + +pub fn derive_uri_display(input: TokenStream) -> TokenStream { + DeriveGenerator::build_for(input, "::rocket::http::uri::UriDisplay") + .generic_support(GenericSupport::Type | GenericSupport::Lifetime) + .data_support(DataSupport::Struct | DataSupport::Enum) + .validate_enum(validate_enum) + .validate_struct(validate_struct) + .map_type_generic(|_, ident, _| quote!(#ident : ::rocket::http::uri::UriDisplay)) + .function(|_, inner| quote! { + fn fmt(&self, f: &mut ::rocket::http::uri::Formatter) -> ::std::fmt::Result { + #inner + Ok(()) + } + }) + .map_field(|_, field| { + let span = field.span().into(); + let accessor = field.accessor(); + if let Some(ref ident) = field.ident { + let name = ident.to_string(); + quote_spanned!(span => f.write_named_value(#name, &#accessor)?;) + } else { + quote_spanned!(span => f.write_value(&#accessor)?;) + } + }) + .to_tokens() +} diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs @@ -583,6 +583,11 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { emit!(derive::responder::derive_responder(input)) } +#[proc_macro_derive(UriDisplay)] +pub fn derive_uri_display(input: TokenStream) -> TokenStream { + emit!(derive::uri_display::derive_uri_display(input)) +} + /// Generates a [`Vec`] of [`Route`]s from a set of route paths. /// /// The `routes!` macro expands a list of route paths into a [`Vec`] of their diff --git a/core/codegen/tests/route.rs b/core/codegen/tests/route.rs @@ -2,7 +2,6 @@ #[macro_use] extern crate rocket; -use std::fmt; use std::path::PathBuf; use rocket::{Request, Outcome::*}; @@ -10,22 +9,15 @@ use rocket::http::ext::Normalize; use rocket::local::Client; use rocket::data::{self, Data, FromDataSimple}; use rocket::request::Form; -use rocket::http::{Status, RawStr, ContentType, uri::{Formatter, UriDisplay}}; +use rocket::http::{Status, RawStr, ContentType}; // Use all of the code generation avaiable at once. -#[derive(FromForm)] +#[derive(FromForm, UriDisplay)] struct Inner<'r> { field: &'r RawStr } -// TODO: Make this deriveable. -impl<'a> UriDisplay for Inner<'a> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_named_value("field", &self.field) - } -} - struct Simple(String); impl FromDataSimple for Simple { diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs @@ -3,27 +3,18 @@ #[macro_use] extern crate rocket; -use std::fmt; use std::path::PathBuf; use rocket::http::{RawStr, Cookies}; -use rocket::http::uri::{Origin, Formatter, UriDisplay, FromUriParam}; +use rocket::http::uri::{Origin, FromUriParam}; use rocket::request::Form; -#[derive(FromForm)] +#[derive(FromForm, UriDisplay)] struct User<'a> { name: &'a RawStr, nickname: String, } -// TODO: Make this deriveable. -impl<'a> UriDisplay for User<'a> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_named_value("name", &self.name)?; - f.write_named_value("nickname", &self.nickname) - } -} - impl<'a, 'b> FromUriParam<(&'a str, &'b str)> for User<'a> { type Target = User<'a>; fn from_uri_param((name, nickname): (&'a str, &'b str)) -> User<'a> {