Overhealed route decorator. URI struct now understands query part.

This commit is contained in:
Sergio Benitez 2016-07-18 21:11:22 -07:00
parent 26b7b814f4
commit 92671a0cba
6 changed files with 188 additions and 205 deletions

View File

@ -4,7 +4,7 @@ use method::Method;
pub use hyper::server::Request as HyperRequest;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Request<'a> {
params: Option<Vec<&'a str>>,
pub method: Method,

View File

@ -93,12 +93,12 @@ impl Rocket {
return handle_not_found(res);
}
// Okay, we've got a route. Unwrap it, generate a request, and try to
// dispatch.
println!("\t=> {}", Magenta.paint("Dispatching request."));
// Okay, we've got a route. Unwrap it, generate a request, and dispatch.
let route = route.unwrap();
let params = route.get_params(uri);
let request = Request::new(method, uri, Some(params), &buf);
println!("\t=> {}", Magenta.paint("Dispatching request."));
let outcome = (route.handler)(request).respond(res);
// TODO: keep trying lower ranked routes before dispatching a not found

View File

@ -5,15 +5,26 @@ use std::fmt::{self, Write};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URI<'a> {
uri: &'a str,
path: &'a str,
query: Option<&'a str>,
segment_count: Cell<Option<usize>>
}
impl<'a> URI<'a> {
pub fn new<T: AsRef<str> + ?Sized>(path: &'a T) -> URI<'a> {
pub fn new<T: AsRef<str> + ?Sized>(uri: &'a T) -> URI<'a> {
let uri = uri.as_ref();
let (path, query) = match uri.find('?') {
Some(index) => (&uri[..index], Some(&uri[index..])),
None => (uri, None)
};
URI {
segment_count: Cell::new(None),
path: path.as_ref(),
uri: uri,
path: path,
query: query,
}
}
@ -30,14 +41,14 @@ impl<'a> URI<'a> {
}
pub fn as_str(&self) -> &'a str {
self.path
self.uri
}
}
impl<'a> fmt::Display for URI<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut last = '\0';
for c in self.path.chars() {
for c in self.uri.chars() {
if !(c == '/' && last == '/') { f.write_char(c)?; }
last = c;
}
@ -50,7 +61,7 @@ unsafe impl<'a> Sync for URI<'a> { /* It's safe! */ }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URIBuf {
path: String,
uri: String,
segment_count: Cell<Option<usize>>
}
@ -65,25 +76,25 @@ impl URIBuf {
}
pub fn segments(&self) -> Segments {
Segments(self.path.as_str())
self.as_uri_uncached().segments()
}
fn as_uri_uncached(&self) -> URI {
URI::new(self.path.as_str())
URI::new(self.uri.as_str())
}
pub fn as_uri(&self) -> URI {
let mut uri = URI::new(self.path.as_str());
let mut uri = URI::new(self.uri.as_str());
uri.segment_count = self.segment_count.clone();
uri
}
pub fn as_str(&self) -> &str {
self.path.as_str()
self.uri.as_str()
}
pub fn to_string(&self) -> String {
self.path.clone()
self.uri.clone()
}
}
@ -96,19 +107,19 @@ impl fmt::Display for URIBuf {
}
impl From<String> for URIBuf {
fn from(path: String) -> URIBuf {
fn from(uri: String) -> URIBuf {
URIBuf {
segment_count: Cell::new(None),
path: path,
uri: uri,
}
}
}
impl<'a> From<&'a str> for URIBuf {
fn from(path: &'a str) -> URIBuf {
fn from(uri: &'a str) -> URIBuf {
URIBuf {
segment_count: Cell::new(None),
path: path.to_string(),
uri: uri.to_string(),
}
}
}

View File

@ -17,17 +17,15 @@ struct Params {
}
fn get_error_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
assert_meta_item_list(ecx, meta_item, "error");
// Ensure we can unwrap the k = v params.
let params = meta_item.node.get_list_items().unwrap();
// Ensure we've been supplied with a k = v meta item. Error out if not.
let params = meta_item.expect_list(ecx, "Bad use. Expected: #[error(...)]");
// Now grab all of the required and optional parameters.
let req: [&'static str; 1] = ["code"];
let kv_pairs = get_key_values(ecx, meta_item.span, &req, &[], &*params);
// Ensure we have a code, just to keep parsing and generating errors.
let code = kv_pairs.get("code").map_or(dummy_kvspan(404), |c| {
let code = kv_pairs.get("code").map_or(KVSpanned::dummy(404), |c| {
let numeric_code = match c.node.parse() {
Ok(n) => n,
Err(_) => {

View File

@ -2,13 +2,10 @@ use super::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX};
use utils::*;
use std::str::FromStr;
use std::collections::HashSet;
use std::collections::HashMap;
use syntax::ext::quote::rt::ToTokens;
use syntax::codemap::{Span, BytePos, /* DUMMY_SP, */ Spanned};
use syntax::tokenstream::TokenTree;
use syntax::ast::{Ident, PatKind, Stmt};
use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl, Ty};
use syntax::ast::{Stmt, Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;
use syntax::print::pprust::{item_to_string, stmt_to_string};
@ -54,12 +51,10 @@ pub fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable)
(item, wrap_span(&*fn_decl, item.span))
}
fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
// First, check that the macro was used in the #[route(a, b, ..)] form.
assert_meta_item_list(ecx, meta_item, "error");
// Ensure we can unwrap the k = v params.
let params = meta_item.node.get_list_items().unwrap();
// Parses the MetaItem derived from the route(...) macro.
fn parse_route(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
// Ensure we've been supplied with a k = v meta item. Error out if not.
let params = meta_item.expect_list(ecx, "Bad use. Expected: #[route(...)]");
if params.len() < 1 {
bad_method_err(ecx, meta_item.span, "HTTP method parameter is missing.");
ecx.span_fatal(meta_item.span, "At least 2 arguments are required.");
@ -89,7 +84,7 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
let kv_pairs = get_key_values(ecx, meta_item.span, &req, &opt, kv_params);
// Ensure we have a path, just to keep parsing and generating errors.
let path = kv_pairs.get("path").map_or(dummy_kvspan("/".to_string()), |s| {
let path = kv_pairs.get("path").map_or(KVSpanned::dummy("/".to_string()), |s| {
s.clone().map(String::from)
});
@ -107,7 +102,7 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
.emit();
}
if f.node.chars().filter(|c| *c == '<').count() != 1 {
if f.node.chars().filter(|c| *c == '<' || *c == '>').count() != 2 {
ecx.span_err(f.p_span, "`form` must contain exactly one parameter");
}
@ -121,29 +116,11 @@ fn get_route_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params {
}
}
// Is there a better way to do this? I need something with ToTokens for the
// quote_expr macro that builds the route struct. I tried using
// str_to_ident("rocket::Method::Options"), but this seems to miss the context,
// and you get an 'ident not found' on compile. I also tried using the path expr
// builder from ASTBuilder: same thing.
fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
match method {
Method::Options => quote_expr!(ecx, rocket::Method::Options),
Method::Get => quote_expr!(ecx, rocket::Method::Get),
Method::Post => quote_expr!(ecx, rocket::Method::Post),
Method::Put => quote_expr!(ecx, rocket::Method::Put),
Method::Delete => quote_expr!(ecx, rocket::Method::Delete),
Method::Head => quote_expr!(ecx, rocket::Method::Head),
Method::Trace => quote_expr!(ecx, rocket::Method::Trace),
Method::Connect => quote_expr!(ecx, rocket::Method::Connect),
Method::Patch => quote_expr!(ecx, rocket::Method::Patch),
}
}
// TODO: Put something like this in the library. Maybe as an iterator?
pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>)
-> HashSet<&'a str> {
let mut seen = HashSet::new();
pub fn extract_params<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>)
-> Vec<Spanned<&'a str>> {
let mut output_params = vec![];
let bad_match_err = "Parameter string is malformed.";
let mut start = 0;
@ -163,13 +140,7 @@ pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>)
if i > start + 1 {
let param_name = &params.node[(start + 1)..i];
if seen.contains(param_name) {
let msg = format!("\"{}\" appears more than once in \
the parameter string.", param_name);
ecx.span_err(param_span, msg.as_str());
}
seen.insert(param_name);
output_params.push(wrap_span(param_name, param_span))
} else {
ecx.span_err(param_span, "Parameter names cannot be empty.");
}
@ -180,95 +151,52 @@ pub fn gen_params_hashset<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>)
}
}
seen
output_params
}
#[derive(Debug)]
struct SimpleArg {
name: String,
ty: P<Ty>,
span: Span
}
pub fn gen_kv_string_hashset<'a>(ecx: &ExtCtxt, params: &'a KVSpanned<String>)
-> HashSet<&'a str> {
pub fn extract_params_from_kv<'a>(ecx: &ExtCtxt, params: &'a KVSpanned<String>)
-> Vec<Spanned<&'a str>> {
let mut param_span = params.v_span;
param_span.lo = params.v_span.lo + BytePos(1);
let params = Spanned {
extract_params(ecx, &Spanned {
span: param_span,
node: &*params.node
};
gen_params_hashset(ecx, &params)
})
}
impl SimpleArg {
fn new<T: ToString>(name: T, ty: P<Ty>, sp: Span) -> SimpleArg {
SimpleArg { name: name.to_string(), ty: ty, span: sp }
}
fn as_str(&self) -> &str {
self.name.as_str()
}
}
impl ToTokens for SimpleArg {
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
str_to_ident(self.as_str()).to_tokens(cx)
}
}
fn get_fn_params<'a>(ecx: &ExtCtxt, dec_span: Span, path: &'a KVSpanned<String>,
fn_decl: &Spanned<&FnDecl>, mut external: HashSet<&'a str>)
-> Vec<SimpleArg> {
fn get_fn_params<'a, T: Iterator<Item=&'a Spanned<&'a str>>>(ecx: &ExtCtxt,
declared_params: T, fn_decl: &Spanned<&FnDecl>) -> Vec<SimpleArg> {
debug!("FUNCTION: {:?}", fn_decl);
let mut path_params = gen_kv_string_hashset(ecx, &path);
// Ensure that there are no collisions between path parameters and external
// params. If there are, get rid of one of them so we don't double error.
let new_external = external.clone();
for param in path_params.intersection(&new_external) {
let msg = format!("'{}' appears as a parameter more than once.", param);
external.remove(param);
ecx.span_err(dec_span, msg.as_str());
// First, check that all of the parameters are unique.
let mut seen: HashMap<&str, &Spanned<&str>> = HashMap::new();
for item in declared_params {
if seen.contains_key(item.node) {
let msg = format!(
"\"{}\" was declared as a parameter more than once.", item.node);
ecx.span_err(item.span, msg.as_str());
} else {
seen.insert(item.node, item);
}
}
// Ensure every param in the function declaration is in `path`. Also add
// each param name in the declaration to the result vector.
// Ensure every param in the function declaration was declared by the user.
let mut result = vec![];
for arg in &fn_decl.node.inputs {
let ident: &Ident = match arg.pat.node {
PatKind::Ident(_, ref ident, _) => &ident.node,
_ => {
ecx.span_err(arg.pat.span, "Expected an identifier.");
return result
}
};
let name = ident.to_string();
if !path_params.remove(name.as_str()) && !external.remove(name.as_str()) {
let msg1 = format!("'{}' appears in the function declaration...", name);
let msg2 = format!("...but does not appear as a parameter \
(e.g., <{}>).", name);
ecx.span_err(arg.pat.span, msg1.as_str());
ecx.span_err(dec_span, msg2.as_str());
let name = arg.pat.expect_ident(ecx, "Expected identifier.");
if seen.remove(&*name.to_string()).is_none() {
let msg = format!("'{}' appears in the function declaration \
but does not appear as a parameter in the attribute.", name);
ecx.span_err(arg.pat.span, msg.as_str());
}
result.push(SimpleArg::new(name, arg.ty.clone(), arg.pat.span));
}
// Ensure every param in `path` and `exclude` is in the function declaration.
for item in path_params {
let msg = format!("'{}' appears in the path string...", item);
ecx.span_err(path.v_span, msg.as_str());
ecx.span_err(fn_decl.span, "...but does not appear in the function \
declration.");
}
// FIXME: need the spans for the external params
for item in external {
let msg = format!("'{}' appears as a parameter...", item);
ecx.span_err(dec_span, msg.as_str());
// Ensure every declared parameter is in the function declaration.
for item in seen.values() {
let msg = format!("'{}' was declared in the attribute...", item.node);
ecx.span_err(item.span, msg.as_str());
ecx.span_err(fn_decl.span, "...but does not appear in the function \
declaration.");
}
@ -277,40 +205,37 @@ fn get_fn_params<'a>(ecx: &ExtCtxt, dec_span: Span, path: &'a KVSpanned<String>,
}
fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<SimpleArg>,
form_params: &HashSet<&str>) -> Option<Stmt> {
form_params: &[Spanned<&str>]) -> Option<Stmt> {
if form_params.len() < 1 {
return None
} else if form_params.len() > 1 {
panic!("Allowed more than 1 form parameter!");
}
let param_ty;
let param_ident;
let param_name = form_params.iter().next().unwrap();
{
let param_name = &form_params[0].node;
let (param_ty, param_ident) = {
// Get the first item in the hashset, i.e., the form params variable name.
let fn_arg = fn_args.iter().filter(|a| &&*a.name == param_name).next();
if fn_arg.is_none() {
// This happens when a form parameter doesn't appear in the function.
// We should have already caught this, so just return None.
return None;
}
param_ty = fn_arg.unwrap().ty.clone();
param_ident = str_to_ident(param_name);
}
(fn_arg.unwrap().ty.clone(), str_to_ident(param_name))
};
// Remove the paramter from the function arguments.
debug!("Form parameter variable: {}: {:?}", param_name, param_ty);
let fn_arg_index = fn_args.iter().position(|a| &&*a.name == param_name).unwrap();
fn_args.remove(fn_arg_index);
// The actual code we'll be inserting.
quote_stmt!(ecx,
// TODO: Actually get the form parameters to pass into from_form_string.
// Alternatively, pass in some already parsed thing.
let $param_ident: $param_ty = {
let form_string = std::str::from_utf8(_req.data);
if form_string.is_err() {
return ::rocket::Response::not_found()
return ::rocket::Response::server_error();
};
match ::rocket::form::FromForm::from_form_string(form_string.unwrap()) {
@ -324,30 +249,50 @@ fn get_form_stmt(ecx: &ExtCtxt, fn_args: &mut Vec<SimpleArg>,
)
}
// Is there a better way to do this? I need something with ToTokens for the
// quote_expr macro that builds the route struct. I tried using
// str_to_ident("rocket::Method::Options"), but this seems to miss the context,
// and you get an 'ident not found' on compile. I also tried using the path expr
// builder from ASTBuilder: same thing.
fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
match method {
Method::Options => quote_expr!(ecx, ::rocket::Method::Options),
Method::Get => quote_expr!(ecx, ::rocket::Method::Get),
Method::Post => quote_expr!(ecx, ::rocket::Method::Post),
Method::Put => quote_expr!(ecx, ::rocket::Method::Put),
Method::Delete => quote_expr!(ecx, ::rocket::Method::Delete),
Method::Head => quote_expr!(ecx, ::rocket::Method::Head),
Method::Trace => quote_expr!(ecx, ::rocket::Method::Trace),
Method::Connect => quote_expr!(ecx, ::rocket::Method::Connect),
Method::Patch => quote_expr!(ecx, ::rocket::Method::Patch),
}
}
// FIXME: Compilation fails when parameters have the same name as the function!
pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
// Get the encompassing item and function declaration for the annotated func.
let (item, fn_decl) = get_fn_decl(ecx, sp, annotated);
let route_params = get_route_params(ecx, meta_item);
// TODO: move this elsewhere
let mut external_params = HashSet::new();
let mut form_param_hashset = HashSet::new();
if let Some(ref form) = route_params.form {
form_param_hashset = gen_kv_string_hashset(ecx, form);
external_params.extend(&form_param_hashset);
}
// Parse and retrieve all of the parameters of the route.
let route = parse_route(ecx, meta_item);
let mut fn_params = get_fn_params(ecx, sp, &route_params.path, &fn_decl,
external_params.clone());
// Get a list of the user declared parameters in `path` and `form`.
let path_params = extract_params_from_kv(ecx, &route.path);
let form_thing = route.form.unwrap_or_default(); // Default is empty string.
let form_params = extract_params_from_kv(ecx, &form_thing);
// Ensure the params match the function declaration and return the params.
let all_params = path_params.iter().chain(form_params.iter());
let mut fn_params = get_fn_params(ecx, all_params, &fn_decl);
// Create a comma seperated list (token tree) of the function parameters
// We pass this in to the user's function that we're wrapping.
let fn_param_idents = token_separate(ecx, &fn_params, token::Comma);
// Generate the statements that will attempt to parse forms during run-time.
// let form_span = route_params.form.map_or(DUMMY_SP, |f| f.span.clone());
let form_stmt = get_form_stmt(ecx, &mut fn_params, &form_param_hashset);
// Calling this function also remove the form parameter from fn_params.
let form_stmt = get_form_stmt(ecx, &mut fn_params, &form_params);
form_stmt.as_ref().map(|s| debug!("Form stmt: {:?}", stmt_to_string(s)));
// Generate the statements that will attempt to parse the paramaters during
@ -384,8 +329,8 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
push(Annotatable::Item(route_fn_item));
let struct_name = prepend_ident(ROUTE_STRUCT_PREFIX, &item.ident);
let path = route_params.path.node;
let method = method_variant_to_expr(ecx, route_params.method.node);
let path = &route.path.node;
let method = method_variant_to_expr(ecx, route.method.node);
push(Annotatable::Item(quote_item!(ecx,
#[allow(non_upper_case_globals)]
pub static $struct_name: rocket::StaticRouteInfo = rocket::StaticRouteInfo {

View File

@ -1,7 +1,7 @@
use syntax::parse::{token};
use syntax::parse::token::Token;
use syntax::tokenstream::TokenTree;
use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind};
use syntax::ast::{Path, Ident, MetaItem, MetaItemKind, LitKind, Ty, self};
use syntax::ext::base::{ExtCtxt};
use syntax::codemap::{Span, Spanned, BytePos, DUMMY_SP};
use syntax::ext::quote::rt::ToTokens;
@ -50,22 +50,30 @@ pub fn dummy_span<T>(t: T) -> Spanned<T> {
}
}
#[inline]
pub fn dummy_kvspan<T>(t: T) -> KVSpanned<T> {
#[derive(Debug, Clone)]
pub struct KVSpanned<T> {
pub k_span: Span, // Span for the key.
pub v_span: Span, // Span for the value.
pub p_span: Span, // Span for the full parameter.
pub node: T // The value.
}
impl<T> KVSpanned<T> {
#[inline]
pub fn dummy(t: T) -> KVSpanned<T> {
KVSpanned {
k_span: DUMMY_SP,
v_span: DUMMY_SP,
p_span: DUMMY_SP,
node: t,
}
}
}
#[derive(Debug, Clone)]
pub struct KVSpanned<T> {
pub k_span: Span,
pub v_span: Span,
pub p_span: Span,
pub node: T
impl<T: Default> Default for KVSpanned<T> {
fn default() -> KVSpanned<T> {
KVSpanned::dummy(T::default())
}
}
impl<T: ToTokens> ToTokens for KVSpanned<T> {
@ -147,27 +155,38 @@ pub fn token_separate<T: ToTokens>(ecx: &ExtCtxt, things: &[T],
output
}
pub fn assert_meta_item_list(ecx: &ExtCtxt, meta_item: &MetaItem, s: &str) {
if !meta_item.node.is_list() {
let msg = format!("Incorrect use of macro. Expected: #[{}(...)]", s);
ecx.span_fatal(meta_item.span, msg.as_str());
}
}
pub trait MetaItemExt {
fn is_list(&self) -> bool;
fn get_list_items(&self) -> Option<&Vec<P<MetaItem>>>;
fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec<P<MetaItem>>;
fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str;
}
impl MetaItemExt for MetaItemKind {
fn is_list(&self) -> bool {
self.get_list_items().is_some()
impl MetaItemExt for MetaItem {
fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec<P<MetaItem>> {
match self.node {
MetaItemKind::List(_, ref params) => params,
_ => ecx.span_fatal(self.span, msg)
}
}
fn get_list_items(&self) -> Option<&Vec<P<MetaItem>>> {
match *self {
MetaItemKind::List(_, ref params) => Some(params),
_ => None
fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str {
match self.node {
MetaItemKind::Word(ref s) => &*s,
_ => ecx.span_fatal(self.span, msg)
}
}
}
pub trait PatExt {
fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident;
}
impl PatExt for ast::Pat {
fn expect_ident<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Ident {
match self.node {
ast::PatKind::Ident(_, ref ident, _) => &ident.node,
_ => {
ecx.span_fatal(self.span, msg)
}
}
}
}
@ -198,16 +217,26 @@ pub fn prefix_paths(prefix: &str, paths: &mut Vec<Path>) {
}
}
// pub fn find_value_for(key: &str, kv_params: &[P<MetaItem>]) -> Option<String> {
// for param in kv_params {
// if let MetaItemKind::NameValue(ref name, ref value) = param.node {
// if &**name == key {
// if let LitKind::Str(ref string, _) = value.node {
// return Some(String::from(&**string));
// }
// }
// }
// }
#[derive(Debug)]
pub struct SimpleArg {
pub name: String,
pub ty: P<Ty>,
pub span: Span
}
impl SimpleArg {
pub fn new<T: ToString>(name: T, ty: P<Ty>, sp: Span) -> SimpleArg {
SimpleArg { name: name.to_string(), ty: ty, span: sp }
}
pub fn as_str(&self) -> &str {
self.name.as_str()
}
}
impl ToTokens for SimpleArg {
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
token::str_to_ident(self.as_str()).to_tokens(cx)
}
}
// None
// }