mirror of https://github.com/rwf2/Rocket.git
Propogate route names through codegen to runtime.
This commit modifies `codegen` so that a route's name (the name of the route handler) is stored in the generated static route information structure and later propogated into the corresponding `Route` structure. The primary advantage of this change is an improvement to debug and error messages which now include route names. The collision error message, in particular, has been improved dramatically in this commit. Additionally, the `LaunchError::Collision` variant now contains a vector of the colliding routes.
This commit is contained in:
parent
944736208e
commit
4df7ce6bf5
|
@ -12,6 +12,7 @@ use syntax::ast::{Arg, Ident, Stmt, Expr, MetaItem, Path};
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
use syntax::ext::build::AstBuilder;
|
use syntax::ext::build::AstBuilder;
|
||||||
use syntax::parse::token;
|
use syntax::parse::token;
|
||||||
|
use syntax::symbol::InternedString;
|
||||||
use syntax::ptr::P;
|
use syntax::ptr::P;
|
||||||
|
|
||||||
use rocket::http::{Method, MediaType};
|
use rocket::http::{Method, MediaType};
|
||||||
|
@ -46,7 +47,7 @@ trait RouteGenerateExt {
|
||||||
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
||||||
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt>;
|
||||||
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree>;
|
||||||
fn explode(&self, ecx: &ExtCtxt) -> (&str, Path, P<Expr>, P<Expr>);
|
fn explode(&self, ecx: &ExtCtxt) -> (InternedString, &str, Path, P<Expr>, P<Expr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteGenerateExt for RouteParams {
|
impl RouteGenerateExt for RouteParams {
|
||||||
|
@ -223,14 +224,15 @@ impl RouteGenerateExt for RouteParams {
|
||||||
sep_by_tok(ecx, &args, token::Comma)
|
sep_by_tok(ecx, &args, token::Comma)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn explode(&self, ecx: &ExtCtxt) -> (&str, Path, P<Expr>, P<Expr>) {
|
fn explode(&self, ecx: &ExtCtxt) -> (InternedString, &str, Path, P<Expr>, P<Expr>) {
|
||||||
|
let name = self.annotated_fn.ident().name.as_str();
|
||||||
let path = &self.uri.node.as_str();
|
let path = &self.uri.node.as_str();
|
||||||
let method = method_to_path(ecx, self.method.node);
|
let method = method_to_path(ecx, self.method.node);
|
||||||
let format = self.format.as_ref().map(|kv| kv.value().clone());
|
let format = self.format.as_ref().map(|kv| kv.value().clone());
|
||||||
let media_type = option_as_expr(ecx, &media_type_to_expr(ecx, format));
|
let media_type = option_as_expr(ecx, &media_type_to_expr(ecx, format));
|
||||||
let rank = option_as_expr(ecx, &self.rank);
|
let rank = option_as_expr(ecx, &self.rank);
|
||||||
|
|
||||||
(path, method, media_type, rank)
|
(name, path, method, media_type, rank)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,12 +274,13 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
// Generate and emit the static route info that uses the just generated
|
// Generate and emit the static route info that uses the just generated
|
||||||
// function as its handler. A proper Rocket route will be created from this.
|
// function as its handler. A proper Rocket route will be created from this.
|
||||||
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
|
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
|
||||||
let (path, method, media_type, rank) = route.explode(ecx);
|
let (name, path, method, media_type, rank) = route.explode(ecx);
|
||||||
let static_route_info_item = quote_item!(ecx,
|
let static_route_info_item = quote_item!(ecx,
|
||||||
/// Rocket code generated static route information structure.
|
/// Rocket code generated static route information structure.
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub static $struct_name: ::rocket::StaticRouteInfo =
|
pub static $struct_name: ::rocket::StaticRouteInfo =
|
||||||
::rocket::StaticRouteInfo {
|
::rocket::StaticRouteInfo {
|
||||||
|
name: $name,
|
||||||
method: $method,
|
method: $method,
|
||||||
path: $path,
|
path: $path,
|
||||||
handler: $route_fn_name,
|
handler: $route_fn_name,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use handler::{Handler, ErrorHandler};
|
||||||
use http::{Method, MediaType};
|
use http::{Method, MediaType};
|
||||||
|
|
||||||
pub struct StaticRouteInfo {
|
pub struct StaticRouteInfo {
|
||||||
|
pub name: &'static str,
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
pub path: &'static str,
|
pub path: &'static str,
|
||||||
pub format: Option<MediaType>,
|
pub format: Option<MediaType>,
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
use std::{io, fmt};
|
use std::{io, fmt};
|
||||||
use std::sync::atomic::{Ordering, AtomicBool};
|
use std::sync::atomic::{Ordering, AtomicBool};
|
||||||
|
|
||||||
|
use yansi::Paint;
|
||||||
|
|
||||||
use http::hyper;
|
use http::hyper;
|
||||||
|
use router::Route;
|
||||||
|
|
||||||
/// [unstable] Error type for Rocket. Likely to change.
|
/// [unstable] Error type for Rocket. Likely to change.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -31,7 +34,7 @@ pub enum Error {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LaunchErrorKind {
|
pub enum LaunchErrorKind {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
Collision,
|
Collision(Vec<(Route, Route)>),
|
||||||
FailedFairing,
|
FailedFairing,
|
||||||
Unknown(Box<::std::error::Error + Send + Sync>)
|
Unknown(Box<::std::error::Error + Send + Sync>)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +159,7 @@ impl fmt::Display for LaunchErrorKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
LaunchErrorKind::Io(ref e) => write!(f, "I/O error: {}", e),
|
LaunchErrorKind::Io(ref e) => write!(f, "I/O error: {}", e),
|
||||||
LaunchErrorKind::Collision => write!(f, "route collisions detected"),
|
LaunchErrorKind::Collision(_) => write!(f, "route collisions detected"),
|
||||||
LaunchErrorKind::FailedFairing => write!(f, "a launch fairing failed"),
|
LaunchErrorKind::FailedFairing => write!(f, "a launch fairing failed"),
|
||||||
LaunchErrorKind::Unknown(ref e) => write!(f, "unknown error: {}", e)
|
LaunchErrorKind::Unknown(ref e) => write!(f, "unknown error: {}", e)
|
||||||
}
|
}
|
||||||
|
@ -185,7 +188,7 @@ impl ::std::error::Error for LaunchError {
|
||||||
self.mark_handled();
|
self.mark_handled();
|
||||||
match *self.kind() {
|
match *self.kind() {
|
||||||
LaunchErrorKind::Io(_) => "an I/O error occured during launch",
|
LaunchErrorKind::Io(_) => "an I/O error occured during launch",
|
||||||
LaunchErrorKind::Collision => "route collisions were detected",
|
LaunchErrorKind::Collision(_) => "route collisions were detected",
|
||||||
LaunchErrorKind::FailedFairing => "a launch fairing reported an error",
|
LaunchErrorKind::FailedFairing => "a launch fairing reported an error",
|
||||||
LaunchErrorKind::Unknown(_) => "an unknown error occured during launch"
|
LaunchErrorKind::Unknown(_) => "an unknown error occured during launch"
|
||||||
}
|
}
|
||||||
|
@ -203,8 +206,13 @@ impl Drop for LaunchError {
|
||||||
error!("Rocket failed to launch due to an I/O error.");
|
error!("Rocket failed to launch due to an I/O error.");
|
||||||
panic!("{}", e);
|
panic!("{}", e);
|
||||||
}
|
}
|
||||||
LaunchErrorKind::Collision => {
|
LaunchErrorKind::Collision(ref collisions) => {
|
||||||
error!("Rocket failed to launch due to routing collisions.");
|
error!("Rocket failed to launch due to the following routing collisions:");
|
||||||
|
for &(ref a, ref b) in collisions {
|
||||||
|
info_!("{} {} {}", a, Paint::red("collides with").italic(), b)
|
||||||
|
}
|
||||||
|
|
||||||
|
info_!("Note: Collisions can usually be resolved by ranking routes.");
|
||||||
panic!("route collisions detected");
|
panic!("route collisions detected");
|
||||||
}
|
}
|
||||||
LaunchErrorKind::FailedFairing => {
|
LaunchErrorKind::FailedFairing => {
|
||||||
|
|
|
@ -409,7 +409,9 @@ impl Rocket {
|
||||||
|
|
||||||
for (name, value) in config.extras() {
|
for (name, value) in config.extras() {
|
||||||
info_!("{} {}: {}",
|
info_!("{} {}: {}",
|
||||||
Paint::yellow("[extra]"), name, Paint::white(LoggedValue(value)));
|
Paint::yellow("[extra]"),
|
||||||
|
Paint::blue(name),
|
||||||
|
Paint::white(LoggedValue(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Rocket {
|
Rocket {
|
||||||
|
@ -475,7 +477,7 @@ impl Rocket {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
|
pub fn mount(mut self, base: &str, routes: Vec<Route>) -> Self {
|
||||||
info!("🛰 {} '{}':", Paint::purple("Mounting"), base);
|
info!("🛰 {} '{}':", Paint::purple("Mounting"), Paint::blue(base));
|
||||||
|
|
||||||
if base.contains('<') {
|
if base.contains('<') {
|
||||||
error_!("Bad mount point: '{}'.", base);
|
error_!("Bad mount point: '{}'.", base);
|
||||||
|
@ -622,8 +624,10 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn prelaunch_check(&self) -> Option<LaunchError> {
|
pub(crate) fn prelaunch_check(&self) -> Option<LaunchError> {
|
||||||
if self.router.has_collisions() {
|
let collisions = self.router.collisions();
|
||||||
Some(LaunchError::from(LaunchErrorKind::Collision))
|
if !collisions.is_empty() {
|
||||||
|
let owned = collisions.iter().map(|&(a, b)| (a.clone(), b.clone()));
|
||||||
|
Some(LaunchError::from(LaunchErrorKind::Collision(owned.collect())))
|
||||||
} else if self.fairings.had_failure() {
|
} else if self.fairings.had_failure() {
|
||||||
Some(LaunchError::from(LaunchErrorKind::FailedFairing))
|
Some(LaunchError::from(LaunchErrorKind::FailedFairing))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -44,14 +44,13 @@ impl Router {
|
||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_collisions(&self) -> bool {
|
pub fn collisions(&self) -> Vec<(&Route, &Route)> {
|
||||||
let mut result = false;
|
let mut result = vec![];
|
||||||
for routes in self.routes.values() {
|
for routes in self.routes.values() {
|
||||||
for (i, a_route) in routes.iter().enumerate() {
|
for (i, a_route) in routes.iter().enumerate() {
|
||||||
for b_route in routes.iter().skip(i + 1) {
|
for b_route in routes.iter().skip(i + 1) {
|
||||||
if a_route.collides_with(b_route) {
|
if a_route.collides_with(b_route) {
|
||||||
result = true;
|
result.push((a_route, b_route));
|
||||||
error!("{} and {} collide!", a_route, b_route);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +59,13 @@ impl Router {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is slow. Don't expose this publicly; only for tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
fn has_collisions(&self) -> bool {
|
||||||
|
!self.collisions().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn routes<'a>(&'a self) -> impl Iterator<Item=&'a Route> + 'a {
|
pub fn routes<'a>(&'a self) -> impl Iterator<Item=&'a Route> + 'a {
|
||||||
self.routes.values().flat_map(|v| v.iter())
|
self.routes.values().flat_map(|v| v.iter())
|
||||||
|
|
|
@ -10,6 +10,8 @@ use http::uri::URI;
|
||||||
|
|
||||||
/// A route: a method, its handler, path, rank, and format/media type.
|
/// A route: a method, its handler, path, rank, and format/media type.
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
|
/// The name of this route, if one was given.
|
||||||
|
pub name: Option<&'static str>,
|
||||||
/// The method this route matches against.
|
/// The method this route matches against.
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
/// The function that should be called when the route matches.
|
/// The function that should be called when the route matches.
|
||||||
|
@ -80,6 +82,7 @@ impl Route {
|
||||||
{
|
{
|
||||||
let uri = URI::from(path.as_ref().to_string());
|
let uri = URI::from(path.as_ref().to_string());
|
||||||
Route {
|
Route {
|
||||||
|
name: None,
|
||||||
method: m,
|
method: m,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
rank: default_rank(&uri),
|
rank: default_rank(&uri),
|
||||||
|
@ -110,6 +113,7 @@ impl Route {
|
||||||
where S: AsRef<str>
|
where S: AsRef<str>
|
||||||
{
|
{
|
||||||
Route {
|
Route {
|
||||||
|
name: None,
|
||||||
method: m,
|
method: m,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
base: URI::from("/"),
|
base: URI::from("/"),
|
||||||
|
@ -220,6 +224,7 @@ impl Route {
|
||||||
impl Clone for Route {
|
impl Clone for Route {
|
||||||
fn clone(&self) -> Route {
|
fn clone(&self) -> Route {
|
||||||
Route {
|
Route {
|
||||||
|
name: self.name,
|
||||||
method: self.method,
|
method: self.method,
|
||||||
handler: self.handler,
|
handler: self.handler,
|
||||||
rank: self.rank,
|
rank: self.rank,
|
||||||
|
@ -242,6 +247,11 @@ impl fmt::Display for Route {
|
||||||
write!(f, " {}", Yellow.paint(format))?;
|
write!(f, " {}", Yellow.paint(format))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(name) = self.name {
|
||||||
|
write!(f, " {}{}{}",
|
||||||
|
Cyan.paint("("), Purple.paint(name), Cyan.paint(")"))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,6 +267,7 @@ impl<'a> From<&'a StaticRouteInfo> for Route {
|
||||||
fn from(info: &'a StaticRouteInfo) -> Route {
|
fn from(info: &'a StaticRouteInfo) -> Route {
|
||||||
let mut route = Route::new(info.method, info.path, info.handler);
|
let mut route = Route::new(info.method, info.path, info.handler);
|
||||||
route.format = info.format.clone();
|
route.format = info.format.clone();
|
||||||
|
route.name = Some(info.name);
|
||||||
if let Some(rank) = info.rank {
|
if let Some(rank) = info.rank {
|
||||||
route.rank = rank;
|
route.rank = rank;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue