mirror of https://github.com/rwf2/Rocket.git
Generate a proxy structure for better namespacing.
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.
This commit is contained in:
parent
29f4726127
commit
092e03f720
|
@ -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)]
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Rocket code generated proxy structure.
|
||||
#vis struct #user_handler_fn_name { }
|
||||
|
||||
#generated_respond_expr
|
||||
})
|
||||
/// 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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
|
||||
Ok(quote!(vec![#(#prefixed_mapped_paths),*]))
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
// 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
|
||||
}));
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
|
@ -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_";
|
||||
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -8,7 +8,7 @@ fn google() -> Redirect {
|
|||
}
|
||||
|
||||
#[get("/rocket")]
|
||||
fn rocket() -> Redirect {
|
||||
fn redirect() -> Redirect {
|
||||
Redirect::to("https://rocket.rs:80")
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ mod test_absolute_uris_okay {
|
|||
|
||||
#[test]
|
||||
fn redirect_works() {
|
||||
let rocket = rocket::ignite().mount("/", routes![google, rocket]);
|
||||
let rocket = rocket::ignite().mount("/", routes![google, redirect]);
|
||||
let client = Client::new(rocket).unwrap();
|
||||
|
||||
let response = client.get("/google").dispatch();
|
||||
|
|
|
@ -119,47 +119,6 @@ requests to `"/hello/world"` will be directed to the `world` function.
|
|||
|
||||
! note: In many cases, the base path will simply be `"/"`.
|
||||
|
||||
### Namespacing
|
||||
|
||||
When a route is declared inside a module other than the root, you may find
|
||||
yourself with unexpected errors when mounting:
|
||||
|
||||
```rust,compile_fail
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
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 main() {
|
||||
// error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope
|
||||
rocket::ignite().mount("/hello", routes![hello, world]);
|
||||
}
|
||||
```
|
||||
|
||||
This occurs because the `routes!` macro implicitly converts the route's name
|
||||
into the name of a structure generated by Rocket's code generation. The solution
|
||||
is to refer to the route using a namespaced path instead:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate rocket;
|
||||
|
||||
# #[get("/")] pub fn hello() {}
|
||||
# mod other { #[get("/world")] pub fn world() {} }
|
||||
|
||||
rocket::ignite().mount("/hello", routes![hello, other::world]);
|
||||
```
|
||||
|
||||
## Launching
|
||||
|
||||
Now that Rocket knows about the route, you can tell Rocket to start accepting
|
||||
|
|
Loading…
Reference in New Issue