Skip to content

Commit

Permalink
Generate a proxy structure for better namespacing.
Browse files Browse the repository at this point in the history
Prior to this commit, it was impossible to 'use' a route from a separate
namespace for use in a 'routes!' macro. Naturally, this was a common
source of confusion amongst users. This commit obviates this deficiency
by generating a "proxy" structure that can be imported and converted
into a 'Route'/'Catcher' or their static variants.

This change is largely backwards compatible but can break existing code
when routes are named identically to other types in the namespace.
  • Loading branch information
SergioBenitez committed Oct 13, 2020
1 parent 29f4726 commit 092e03f
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 151 deletions.
60 changes: 35 additions & 25 deletions core/codegen/src/attribute/catch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ use devise::{syn, MetaItem, Spanned, Result, FromMeta, Diagnostic};

use crate::http_codegen::{self, Optional};
use crate::proc_macro2::{TokenStream, Span};
use crate::syn_ext::{IdentExt, ReturnTypeExt, TokenStreamExt};
use crate::syn_ext::{ReturnTypeExt, TokenStreamExt};
use self::syn::{Attribute, parse::Parser};
use crate::{CATCH_FN_PREFIX, CATCH_STRUCT_PREFIX};

/// The raw, parsed `#[catch(code)]` attribute.
#[derive(Debug, FromMeta)]
Expand Down Expand Up @@ -75,14 +74,13 @@ pub fn _catch(
// Gather everything we'll need to generate the catcher.
let user_catcher_fn = &catch.function;
let user_catcher_fn_name = catch.function.sig.ident.clone();
let generated_struct_name = user_catcher_fn_name.prepend(CATCH_STRUCT_PREFIX);
let generated_fn_name = user_catcher_fn_name.prepend(CATCH_FN_PREFIX);
let (vis, catcher_status) = (&catch.function.vis, &catch.status);
let status_code = Optional(catcher_status.as_ref().map(|s| s.0.code));

// Variables names we'll use and reuse.
define_vars_and_mods!(catch.function.span().into() =>
req, status, _Box, Request, Response, ErrorHandlerFuture, Status);
req, status, _Box, Request, Response, StaticCatcherInfo, Catcher,
ErrorHandlerFuture, Status);

// Determine the number of parameters that will be passed in.
if catch.function.sig.inputs.len() > 2 {
Expand Down Expand Up @@ -119,29 +117,41 @@ pub fn _catch(
Ok(quote! {
#user_catcher_fn

/// Rocket code generated wrapping catch function.
#[doc(hidden)]
#vis fn #generated_fn_name<'_b>(
#status: #Status,
#req: &'_b #Request
) -> #ErrorHandlerFuture<'_b> {
#_Box::pin(async move {
let __response = #catcher_response;
#Response::build()
.status(#status)
.merge(__response)
.ok()
})
#[allow(non_camel_case_types)]
/// Rocket code generated proxy structure.
#vis struct #user_catcher_fn_name { }

/// Rocket code generated proxy static conversion implementation.
impl From<#user_catcher_fn_name> for #StaticCatcherInfo {
fn from(_: #user_catcher_fn_name) -> #StaticCatcherInfo {
fn monomorphized_function<'_b>(
#status: #Status,
#req: &'_b #Request
) -> #ErrorHandlerFuture<'_b> {
#_Box::pin(async move {
let __response = #catcher_response;
#Response::build()
.status(#status)
.merge(__response)
.ok()
})
}

#StaticCatcherInfo {
code: #status_code,
handler: monomorphized_function,
}
}
}

/// Rocket code generated static catcher info.
#[doc(hidden)]
#[allow(non_upper_case_globals)]
#vis static #generated_struct_name: ::rocket::StaticCatcherInfo =
::rocket::StaticCatcherInfo {
code: #status_code,
handler: #generated_fn_name,
};
/// Rocket code generated proxy conversion implementation.
impl From<#user_catcher_fn_name> for #Catcher {
#[inline]
fn from(_: #user_catcher_fn_name) -> #Catcher {
#StaticCatcherInfo::from(#user_catcher_fn_name {}).into()
}
}
})
}

Expand Down
68 changes: 39 additions & 29 deletions core/codegen/src/attribute/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::http_codegen::{Method, MediaType, RoutePath, DataSegment, Optional};
use crate::attribute::segments::{Source, Kind, Segment};
use crate::syn::{Attribute, parse::Parser};

use crate::{ROUTE_FN_PREFIX, ROUTE_STRUCT_PREFIX, URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX};
use crate::{URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX};

/// The raw, parsed `#[route]` attribute.
#[derive(Debug, FromMeta)]
Expand Down Expand Up @@ -408,11 +408,9 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
}

// Gather everything we need.
define_vars_and_mods!(req, data, _Box, Request, Data, StaticRouteInfo, HandlerFuture);
define_vars_and_mods!(req, data, _Box, Request, Data, Route, StaticRouteInfo, HandlerFuture);
let (vis, user_handler_fn) = (&route.function.vis, &route.function);
let user_handler_fn_name = &user_handler_fn.sig.ident;
let generated_fn_name = user_handler_fn_name.prepend(ROUTE_FN_PREFIX);
let generated_struct_name = user_handler_fn_name.prepend(ROUTE_STRUCT_PREFIX);
let generated_internal_uri_macro = generate_internal_uri_macro(&route);
let generated_respond_expr = generate_respond_expr(&route);

Expand All @@ -424,36 +422,48 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
Ok(quote! {
#user_handler_fn

/// Rocket code generated wrapping route function.
#[doc(hidden)]
#vis fn #generated_fn_name<'_b>(
#req: &'_b #Request,
#data: #Data
) -> #HandlerFuture<'_b> {
#_Box::pin(async move {
#(#req_guard_definitions)*
#(#parameter_definitions)*
#data_stmt

#generated_respond_expr
})
#[allow(non_camel_case_types)]
/// Rocket code generated proxy structure.
#vis struct #user_handler_fn_name { }

/// Rocket code generated proxy static conversion implementation.
impl From<#user_handler_fn_name> for #StaticRouteInfo {
fn from(_: #user_handler_fn_name) -> #StaticRouteInfo {
fn monomorphized_function<'_b>(
#req: &'_b #Request,
#data: #Data
) -> #HandlerFuture<'_b> {
#_Box::pin(async move {
#(#req_guard_definitions)*
#(#parameter_definitions)*
#data_stmt

#generated_respond_expr
})
}

#StaticRouteInfo {
name: stringify!(#user_handler_fn_name),
method: #method,
path: #path,
handler: monomorphized_function,
format: #format,
rank: #rank,
}
}
}

/// Rocket code generated proxy conversion implementation.
impl From<#user_handler_fn_name> for #Route {
#[inline]
fn from(_: #user_handler_fn_name) -> #Route {
#StaticRouteInfo::from(#user_handler_fn_name {}).into()
}
}

/// Rocket code generated wrapping URI macro.
#generated_internal_uri_macro

/// Rocket code generated static route info.
#[doc(hidden)]
#[allow(non_upper_case_globals)]
#vis static #generated_struct_name: #StaticRouteInfo =
#StaticRouteInfo {
name: stringify!(#user_handler_fn_name),
method: #method,
path: #path,
handler: #generated_fn_name,
format: #format,
rank: #rank,
};
}.into())
}

Expand Down
63 changes: 20 additions & 43 deletions core/codegen/src/bang/mod.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,42 @@
use devise::Result;

use crate::syn_ext::IdentExt;
use crate::syn::{Path, punctuated::Punctuated, parse::Parser, Token};
use crate::syn::spanned::Spanned;
use crate::proc_macro2::TokenStream;
use crate::{ROUTE_STRUCT_PREFIX, CATCH_STRUCT_PREFIX};
use crate::syn::spanned::Spanned;

mod uri;
mod uri_parsing;
mod test_guide;

pub fn prefix_last_segment(path: &mut Path, prefix: &str) {
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
last_seg.ident = last_seg.ident.prepend(prefix);
}

fn _prefixed_vec(
prefix: &str,
fn struct_maker_vec(
input: proc_macro::TokenStream,
ty: &TokenStream
ty: TokenStream,
) -> Result<TokenStream> {
// Parse a comma-separated list of paths.
let mut paths = <Punctuated<Path, Token![,]>>::parse_terminated.parse(input)?;

// Prefix the last segment in each path with `prefix`.
paths.iter_mut().for_each(|p| prefix_last_segment(p, prefix));

// Return a `vec!` of the prefixed, mapped paths.
let prefixed_mapped_paths = paths.iter()
.map(|path| quote_spanned!(path.span().into() => #ty::from(&#path)));
define_vars_and_mods!(_Vec);

Ok(quote!(vec![#(#prefixed_mapped_paths),*]))
}
// Parse a comma-separated list of paths.
let paths = <Punctuated<Path, Token![,]>>::parse_terminated.parse(input)?;
let exprs = paths.iter()
.map(|path| quote_spanned!(path.span() => {
let ___struct = #path {};
let ___item: #ty = ___struct.into();
___item
}));

fn prefixed_vec(
prefix: &str,
input: proc_macro::TokenStream,
ty: TokenStream
) -> TokenStream {
define_vars_and_mods!(_Vec);
_prefixed_vec(prefix, input, &ty)
.map(|vec| quote!({
let __vector: #_Vec<#ty> = #vec;
__vector
}))
.unwrap_or_else(|diag| {
let diag_tokens = diag.emit_as_expr_tokens();
quote!({
#diag_tokens
let __vec: #_Vec<#ty> = vec![];
__vec
})
})
Ok(quote!({
let ___vec: #_Vec<#ty> = vec![#(#exprs),*];
___vec
}))
}

pub fn routes_macro(input: proc_macro::TokenStream) -> TokenStream {
prefixed_vec(ROUTE_STRUCT_PREFIX, input, quote!(::rocket::Route))
struct_maker_vec(input, quote!(::rocket::Route))
.unwrap_or_else(|diag| diag.emit_as_expr_tokens())
}

pub fn catchers_macro(input: proc_macro::TokenStream) -> TokenStream {
prefixed_vec(CATCH_STRUCT_PREFIX, input, quote!(::rocket::Catcher))
struct_maker_vec(input, quote!(::rocket::Catcher))
.unwrap_or_else(|diag| diag.emit_as_expr_tokens())
}

pub fn uri_macro(input: proc_macro::TokenStream) -> TokenStream {
Expand Down
7 changes: 6 additions & 1 deletion core/codegen/src/bang/uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::http::route::{RouteSegment, Kind, Source};
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
use crate::http_codegen::Optional;
use crate::syn_ext::IdentExt;
use crate::bang::{prefix_last_segment, uri_parsing::*};
use crate::bang::uri_parsing::*;
use crate::proc_macro2::TokenStream;

use crate::URI_MACRO_PREFIX;
Expand All @@ -23,6 +23,11 @@ macro_rules! p {
($n:expr, "parameter") => (p!(@go $n, "1 parameter", format!("{} parameters", $n)));
}

pub fn prefix_last_segment(path: &mut syn::Path, prefix: &str) {
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
last_seg.ident = last_seg.ident.prepend(prefix);
}

pub fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
let input2: TokenStream = input.clone().into();
let mut params = syn::parse2::<UriParams>(input)?;
Expand Down
7 changes: 3 additions & 4 deletions core/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ vars_and_mods! {
Response => rocket::response::Response,
Data => rocket::data::Data,
StaticRouteInfo => rocket::StaticRouteInfo,
StaticCatcherInfo => rocket::StaticCatcherInfo,
Route => rocket::Route,
Catcher => rocket::Catcher,
SmallVec => rocket::http::private::SmallVec,
Status => rocket::http::Status,
HandlerFuture => rocket::handler::HandlerFuture,
Expand Down Expand Up @@ -123,10 +126,6 @@ use crate::http::Method;
use proc_macro::TokenStream;
use devise::{proc_macro2, syn};

static ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
static CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
static CATCH_FN_PREFIX: &str = "rocket_catch_fn_";
static ROUTE_FN_PREFIX: &str = "rocket_route_fn_";
static URI_MACRO_PREFIX: &str = "rocket_uri_macro_";
static ROCKET_PARAM_PREFIX: &str = "__rocket_param_";

Expand Down
20 changes: 20 additions & 0 deletions core/codegen/tests/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,23 @@ fn test_full_route() {
assert_eq!(response.into_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
sky, name, "A A", "inside", path, simple, expected_uri));
}

mod scopes {
mod other {
#[get("/world")]
pub fn world() -> &'static str {
"Hello, world!"
}
}

#[get("/hello")]
pub fn hello() -> &'static str {
"Hello, outside world!"
}

use other::world;

fn _rocket() -> rocket::Rocket {
rocket::ignite().mount("/", rocket::routes![hello, world])
}
}
8 changes: 7 additions & 1 deletion core/http/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ use smallvec::{Array, SmallVec};
// TODO: It would be nice if we could somehow have one trait that could give us
// either SmallVec or Vec.
/// Trait implemented by types that can be converted into a collection.
pub trait IntoCollection<T> {
pub trait IntoCollection<T>: Sized {
/// Converts `self` into a collection.
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A>;

#[doc(hidden)]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, f: F) -> SmallVec<A>;

#[doc(hidden)]
fn mapped_vec<U, F: FnMut(T) -> U>(self, f: F) -> Vec<U> {
let small = self.mapped::<U, F, [U; 0]>(f);
small.into_vec()
}
}

impl<T> IntoCollection<T> for T {
Expand Down
5 changes: 3 additions & 2 deletions core/lib/src/catcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,9 @@ impl<F: Clone + Sync + Send + 'static> ErrorHandler for F
}

#[doc(hidden)]
impl<'a> From<&'a StaticCatcherInfo> for Catcher {
fn from(info: &'a StaticCatcherInfo) -> Catcher {
impl From<StaticCatcherInfo> for Catcher {
#[inline]
fn from(info: StaticCatcherInfo) -> Catcher {
Catcher::new(info.code, info.handler)
}
}
Expand Down
6 changes: 3 additions & 3 deletions core/lib/src/router/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,11 @@ impl fmt::Debug for Route {
}

#[doc(hidden)]
impl From<&StaticRouteInfo> for Route {
fn from(info: &StaticRouteInfo) -> Route {
impl From<StaticRouteInfo> for Route {
fn from(info: StaticRouteInfo) -> Route {
// This should never panic since `info.path` is statically checked.
let mut route = Route::new(info.method, info.path, info.handler);
route.format = info.format.clone();
route.format = info.format;
route.name = Some(info.name);
if let Some(rank) = info.rank {
route.rank = rank;
Expand Down
Loading

0 comments on commit 092e03f

Please sign in to comment.