mirror of https://github.com/rwf2/Rocket.git
Add query params to Rocket. Use Ident for attribute params.
This commit is contained in:
parent
ec38d70449
commit
327b28a98e
|
@ -5,7 +5,7 @@ members = [
|
||||||
"examples/extended_validation",
|
"examples/extended_validation",
|
||||||
"examples/forms",
|
"examples/forms",
|
||||||
"examples/hello_person",
|
"examples/hello_person",
|
||||||
"examples/hello_query_params",
|
"examples/query_params",
|
||||||
"examples/hello_world",
|
"examples/hello_world",
|
||||||
"examples/manual_routes",
|
"examples/manual_routes",
|
||||||
"examples/optional_redirect",
|
"examples/optional_redirect",
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
#![feature(plugin)]
|
|
||||||
#![plugin(rocket_macros)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
use rocket::{Rocket, Error};
|
|
||||||
|
|
||||||
// One idea of what we could get.
|
|
||||||
// #[route(GET, path = "/hello?{name,age}")]
|
|
||||||
// fn hello(name: &str, age: &str) -> String {
|
|
||||||
// "Hello!".to_string()
|
|
||||||
// // format!("Hello, {} year old named {}!", age, name)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Another idea.
|
|
||||||
// #[route(GET, path = "/hello")]
|
|
||||||
// fn hello(q: QueryParams) -> IOResult<String> {
|
|
||||||
// format!("Hello, {} year old named {}!", q.get("name")?, q.get("age")?)
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[get("/hello")]
|
|
||||||
fn hello() -> &'static str {
|
|
||||||
"Hello there! Don't have query params yet, but we're working on it."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Rocket::new("localhost", 8000).mount_and_launch("/", routes![hello]);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hello_query"
|
name = "query_params"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
workspace = "../../"
|
workspace = "../../"
|
|
@ -0,0 +1,25 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::{Rocket, Error};
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Person<'r> {
|
||||||
|
name: &'r str,
|
||||||
|
age: Option<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/hello?<person>")]
|
||||||
|
fn hello(person: Person) -> String {
|
||||||
|
if let Some(age) = person.age {
|
||||||
|
format!("Hello, {} year old named {}!", age, person.name)
|
||||||
|
} else {
|
||||||
|
format!("Hello {}!", person.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Rocket::new("localhost", 8000).mount_and_launch("/", routes![hello]);
|
||||||
|
}
|
|
@ -13,9 +13,9 @@ pub trait FromFormValue<'v>: Sized {
|
||||||
|
|
||||||
fn parse(v: &'v str) -> Result<Self, Self::Error>;
|
fn parse(v: &'v str) -> Result<Self, Self::Error>;
|
||||||
|
|
||||||
// Returns a default value to be used when the form field does not exist. If
|
/// Returns a default value to be used when the form field does not exist.
|
||||||
// this returns None, then the field is required. Otherwise, this should
|
/// If this returns None, then the field is required. Otherwise, this should
|
||||||
// return Some(default_value).
|
/// return Some(default_value).
|
||||||
fn default() -> Option<Self> {
|
fn default() -> Option<Self> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -95,27 +95,37 @@ impl<'v, T: FromFormValue<'v>> FromFormValue<'v> for Result<T, T::Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn form_items<'f>(string: &'f str, items: &mut [(&'f str, &'f str)]) -> usize {
|
pub struct FormItems<'f>(pub &'f str);
|
||||||
let mut param_num = 0;
|
|
||||||
let mut rest = string;
|
impl<'f> Iterator for FormItems<'f> {
|
||||||
while !rest.is_empty() && param_num < items.len() {
|
type Item = (&'f str, &'f str);
|
||||||
let (key, remainder) = match rest.find('=') {
|
|
||||||
Some(index) => (&rest[..index], &rest[(index + 1)..]),
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
None => return param_num
|
let string = self.0;
|
||||||
|
let (key, rest) = match string.find('=') {
|
||||||
|
Some(index) => (&string[..index], &string[(index + 1)..]),
|
||||||
|
None => return None
|
||||||
};
|
};
|
||||||
|
|
||||||
rest = remainder;
|
|
||||||
let (value, remainder) = match rest.find('&') {
|
let (value, remainder) = match rest.find('&') {
|
||||||
Some(index) => (&rest[..index], &rest[(index + 1)..]),
|
Some(index) => (&rest[..index], &rest[(index + 1)..]),
|
||||||
None => (rest, "")
|
None => (rest, "")
|
||||||
};
|
};
|
||||||
|
|
||||||
rest = remainder;
|
self.0 = remainder;
|
||||||
items[param_num] = (key, value);
|
Some((key, value))
|
||||||
param_num += 1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
param_num
|
|
||||||
|
pub fn form_items<'f>(string: &'f str, items: &mut [(&'f str, &'f str)]) -> usize {
|
||||||
|
let mut param_count = 0;
|
||||||
|
for (i, item) in FormItems(string).take(items.len()).enumerate() {
|
||||||
|
items[i] = item;
|
||||||
|
param_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
param_count
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -16,7 +16,7 @@ impl<'a> URI<'a> {
|
||||||
let uri = uri.as_ref();
|
let uri = uri.as_ref();
|
||||||
|
|
||||||
let (path, query) = match uri.find('?') {
|
let (path, query) = match uri.find('?') {
|
||||||
Some(index) => (&uri[..index], Some(&uri[index..])),
|
Some(index) => (&uri[..index], Some(&uri[(index + 1)..])),
|
||||||
None => (uri, None)
|
None => (uri, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,6 +40,10 @@ impl<'a> URI<'a> {
|
||||||
Segments(self.path)
|
Segments(self.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn query(&self) -> Option<&'a str> {
|
||||||
|
self.query
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &'a str {
|
pub fn as_str(&self) -> &'a str {
|
||||||
self.uri
|
self.uri
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
|
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX};
|
||||||
use utils::{emit_item, span, sep_by_tok, SpanExt, IdentExt, ArgExt, option_as_expr};
|
use utils::{emit_item, span, sep_by_tok, SpanExt, IdentExt, ArgExt, option_as_expr};
|
||||||
|
@ -6,7 +7,7 @@ use parser::RouteParams;
|
||||||
|
|
||||||
use syntax::codemap::{Span, Spanned};
|
use syntax::codemap::{Span, Spanned};
|
||||||
use syntax::tokenstream::TokenTree;
|
use syntax::tokenstream::TokenTree;
|
||||||
use syntax::ast::{Arg, Ident, Stmt, Expr, MetaItem, Path};
|
use syntax::ast::{Name, 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::{self, str_to_ident};
|
use syntax::parse::token::{self, str_to_ident};
|
||||||
|
@ -21,6 +22,7 @@ fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: This should return an Expr! (Ext is not a path.)
|
||||||
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> Path {
|
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> Path {
|
||||||
quote_enum!(ecx, *level => ::rocket::content_type::TopLevel {
|
quote_enum!(ecx, *level => ::rocket::content_type::TopLevel {
|
||||||
Star, Text, Image, Audio, Video, Application, Multipart, Model, Message;
|
Star, Text, Image, Audio, Video, Application, Multipart, Model, Message;
|
||||||
|
@ -28,6 +30,7 @@ fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> Path {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: This should return an Expr! (Ext is not a path.)
|
||||||
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> Path {
|
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> Path {
|
||||||
quote_enum!(ecx, *level => ::rocket::content_type::SubLevel {
|
quote_enum!(ecx, *level => ::rocket::content_type::SubLevel {
|
||||||
Star, Plain, Html, Xml, Javascript, Css, EventStream, Json,
|
Star, Plain, Html, Xml, Javascript, Css, EventStream, Json,
|
||||||
|
@ -45,24 +48,31 @@ fn content_type_to_expr(ecx: &ExtCtxt, ct: Option<ContentType>) -> Option<P<Expr
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RouteGenerateExt {
|
trait RouteGenerateExt {
|
||||||
|
fn gen_form(&self, &ExtCtxt, Option<&Spanned<Ident>>, P<Expr>) -> Option<Stmt>;
|
||||||
|
fn missing_declared_err<T: Display>(&self, ecx: &ExtCtxt, arg: &Spanned<T>);
|
||||||
|
|
||||||
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt>;
|
fn generate_form_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) -> (&String, Path, P<Expr>, P<Expr>);
|
fn explode(&self, ecx: &ExtCtxt) -> (&String, Path, P<Expr>, P<Expr>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteGenerateExt for RouteParams {
|
impl RouteGenerateExt for RouteParams {
|
||||||
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
fn missing_declared_err<T: Display>(&self, ecx: &ExtCtxt, arg: &Spanned<T>) {
|
||||||
let param = self.form_param.as_ref();
|
let fn_span = self.annotated_fn.span();
|
||||||
let arg = param.and_then(|p| self.annotated_fn.find_input(p.value()));
|
let msg = format!("'{}' is declared as an argument...", arg.node);
|
||||||
|
ecx.span_err(arg.span, &msg);
|
||||||
|
ecx.span_err(fn_span, "...but isn't in the function signature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_form(&self, ecx: &ExtCtxt, param: Option<&Spanned<Ident>>,
|
||||||
|
form_string: P<Expr>) -> Option<Stmt> {
|
||||||
|
let arg = param.and_then(|p| self.annotated_fn.find_input(&p.node.name));
|
||||||
if param.is_none() {
|
if param.is_none() {
|
||||||
return None;
|
return None;
|
||||||
} else if arg.is_none() {
|
} else if arg.is_none() {
|
||||||
let param = param.unwrap();
|
self.missing_declared_err(ecx, ¶m.unwrap());
|
||||||
let fn_span = self.annotated_fn.span();
|
|
||||||
let msg = format!("'{}' is declared as an argument...", param.value());
|
|
||||||
ecx.span_err(param.span, &msg);
|
|
||||||
ecx.span_err(fn_span, "...but isn't in the function signature.");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,18 +80,38 @@ impl RouteGenerateExt for RouteParams {
|
||||||
let (name, ty) = (arg.ident().unwrap().prepend(PARAM_PREFIX), &arg.ty);
|
let (name, ty) = (arg.ident().unwrap().prepend(PARAM_PREFIX), &arg.ty);
|
||||||
Some(quote_stmt!(ecx,
|
Some(quote_stmt!(ecx,
|
||||||
let $name: $ty =
|
let $name: $ty =
|
||||||
if let Ok(s) = ::std::str::from_utf8(_req.data.as_slice()) {
|
match ::rocket::form::FromForm::from_form_string($form_string) {
|
||||||
if let Ok(v) = ::rocket::form::FromForm::from_form_string(s) {
|
Ok(v) => v,
|
||||||
v
|
Err(_) => return ::rocket::Response::forward()
|
||||||
} else {
|
|
||||||
return ::rocket::Response::not_found();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ::rocket::Response::server_error();
|
|
||||||
};
|
};
|
||||||
).expect("form statement"))
|
).expect("form statement"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_form_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
|
let param = self.form_param.as_ref().map(|p| &p.value);
|
||||||
|
let expr = quote_expr!(ecx,
|
||||||
|
match ::std::str::from_utf8(_req.data.as_slice()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return ::rocket::Response::server_error()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
self.gen_form(ecx, param, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_query_statement(&self, ecx: &ExtCtxt) -> Option<Stmt> {
|
||||||
|
let param = self.query_param.as_ref();
|
||||||
|
let expr = quote_expr!(ecx,
|
||||||
|
match _req.uri().query() {
|
||||||
|
// FIXME: Don't reinterpret as UTF8 again.
|
||||||
|
Some(query) => query,
|
||||||
|
None => return ::rocket::Response::forward()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
self.gen_form(ecx, param, expr)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add some kind of logging facility in Rocket to get be able to log
|
// TODO: Add some kind of logging facility in Rocket to get be able to log
|
||||||
// an error/debug message if parsing a parameter fails.
|
// an error/debug message if parsing a parameter fails.
|
||||||
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
|
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
|
||||||
|
@ -91,16 +121,13 @@ impl RouteGenerateExt for RouteParams {
|
||||||
// Retrieve an iterator over the user's path parameters and ensure that
|
// Retrieve an iterator over the user's path parameters and ensure that
|
||||||
// each parameter appears in the function signature.
|
// each parameter appears in the function signature.
|
||||||
for param in ¶ms {
|
for param in ¶ms {
|
||||||
if self.annotated_fn.find_input(param.node).is_none() {
|
if self.annotated_fn.find_input(¶m.node.name).is_none() {
|
||||||
let fn_span = self.annotated_fn.span();
|
self.missing_declared_err(ecx, ¶m);
|
||||||
let msg = format!("'{}' is declared as an argument...", param.node);
|
|
||||||
ecx.span_err(param.span, &msg);
|
|
||||||
ecx.span_err(fn_span, "...but isn't in the function signature.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a function thats checks if an argument was declared in `path`.
|
// Create a function thats checks if an argument was declared in `path`.
|
||||||
let set: HashSet<&str> = params.iter().map(|p| p.node).collect();
|
let set: HashSet<&Name> = params.iter().map(|p| &p.node.name).collect();
|
||||||
let declared = &|arg: &&Arg| set.contains(&*arg.name().unwrap());
|
let declared = &|arg: &&Arg| set.contains(&*arg.name().unwrap());
|
||||||
|
|
||||||
// These are all of the arguments in the function signature.
|
// These are all of the arguments in the function signature.
|
||||||
|
@ -117,11 +144,13 @@ impl RouteGenerateExt for RouteParams {
|
||||||
).expect("declared param parsing statement"));
|
).expect("declared param parsing statement"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A from_request parameter is one that isnt't declared and isn't `form`.
|
// A from_request parameter is one that isn't declared, `form`, or query.
|
||||||
let from_request = |a: &&Arg| {
|
let from_request = |a: &&Arg| {
|
||||||
let a_name = &*a.name().unwrap();
|
!declared(a) && self.form_param.as_ref().map_or(true, |p| {
|
||||||
!declared(a)
|
!a.named(&p.value().name)
|
||||||
&& self.form_param.as_ref().map_or(true, |p| p.value() != a_name)
|
}) && self.query_param.as_ref().map_or(true, |p| {
|
||||||
|
!a.named(&p.node.name)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the code for `form_request` parameters.
|
// Generate the code for `form_request` parameters.
|
||||||
|
@ -170,7 +199,10 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
|
|
||||||
// Parse the route and generate the code to create the form and param vars.
|
// Parse the route and generate the code to create the form and param vars.
|
||||||
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
let route = RouteParams::from(ecx, sp, known_method, meta_item, annotated);
|
||||||
|
debug!("Route params: {:?}", route);
|
||||||
|
|
||||||
let form_statement = route.generate_form_statement(ecx);
|
let form_statement = route.generate_form_statement(ecx);
|
||||||
|
let query_statement = route.generate_query_statement(ecx);
|
||||||
let param_statements = route.generate_param_statements(ecx);
|
let param_statements = route.generate_param_statements(ecx);
|
||||||
let fn_arguments = route.generate_fn_arguments(ecx);
|
let fn_arguments = route.generate_fn_arguments(ecx);
|
||||||
|
|
||||||
|
@ -181,6 +213,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
||||||
fn $route_fn_name<'rocket>(_req: &'rocket ::rocket::Request<'rocket>)
|
fn $route_fn_name<'rocket>(_req: &'rocket ::rocket::Request<'rocket>)
|
||||||
-> ::rocket::Response<'rocket> {
|
-> ::rocket::Response<'rocket> {
|
||||||
$form_statement
|
$form_statement
|
||||||
|
$query_statement
|
||||||
$param_statements
|
$param_statements
|
||||||
let result = $user_fn_name($fn_arguments);
|
let result = $user_fn_name($fn_arguments);
|
||||||
::rocket::Response::new(result)
|
::rocket::Response::new(result)
|
||||||
|
|
|
@ -3,6 +3,7 @@ use syntax::codemap::{Span, Spanned};
|
||||||
use syntax::ext::base::Annotatable;
|
use syntax::ext::base::Annotatable;
|
||||||
use utils::{ArgExt, span};
|
use utils::{ArgExt, span};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Function(Spanned<(Ident, FnDecl)>);
|
pub struct Function(Spanned<(Ident, FnDecl)>);
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
|
@ -33,7 +34,7 @@ impl Function {
|
||||||
self.0.span
|
self.0.span
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_input<'a>(&'a self, name: &str) -> Option<&'a Arg> {
|
pub fn find_input<'a>(&'a self, name: &Name) -> Option<&'a Arg> {
|
||||||
self.decl().inputs.iter().filter(|arg| arg.named(name)).next()
|
self.decl().inputs.iter().filter(|arg| arg.named(name)).next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use syntax::ast::Ident;
|
||||||
use syntax::ext::base::ExtCtxt;
|
use syntax::ext::base::ExtCtxt;
|
||||||
use syntax::codemap::{Span, Spanned, BytePos};
|
use syntax::codemap::{Span, Spanned, BytePos};
|
||||||
|
use syntax::parse::token::str_to_ident;
|
||||||
|
|
||||||
use utils::span;
|
use utils::span;
|
||||||
|
|
||||||
|
@ -20,9 +22,9 @@ impl<'s, 'a, 'c: 'a> ParamIter<'s, 'a, 'c> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
|
impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
|
||||||
type Item = Spanned<&'s str>;
|
type Item = Spanned<Ident>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Spanned<&'s str>> {
|
fn next(&mut self) -> Option<Spanned<Ident>> {
|
||||||
// Find the start and end indexes for the next parameter, if any.
|
// Find the start and end indexes for the next parameter, if any.
|
||||||
let (start, end) = match (self.string.find('<'), self.string.find('>')) {
|
let (start, end) = match (self.string.find('<'), self.string.find('>')) {
|
||||||
(Some(i), Some(j)) => (i, j),
|
(Some(i), Some(j)) => (i, j),
|
||||||
|
@ -51,7 +53,7 @@ impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> {
|
||||||
} else {
|
} else {
|
||||||
self.string = &self.string[(end + 1)..];
|
self.string = &self.string[(end + 1)..];
|
||||||
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
|
self.span.lo = self.span.lo + BytePos((end + 1) as u32);
|
||||||
Some(span(param, param_span))
|
Some(span(str_to_ident(param), param_span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::collections::HashSet;
|
||||||
use syntax::ast::*;
|
use syntax::ast::*;
|
||||||
use syntax::ext::base::{ExtCtxt, Annotatable};
|
use syntax::ext::base::{ExtCtxt, Annotatable};
|
||||||
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
use syntax::codemap::{Span, Spanned, dummy_spanned};
|
||||||
|
use syntax::parse::token::str_to_ident;
|
||||||
|
|
||||||
use utils::{span, MetaItemExt, SpanExt};
|
use utils::{span, MetaItemExt, SpanExt};
|
||||||
use super::{Function, ParamIter};
|
use super::{Function, ParamIter};
|
||||||
|
@ -16,11 +17,13 @@ use rocket::{Method, ContentType};
|
||||||
/// the user supplied the information. This structure can only be obtained by
|
/// the user supplied the information. This structure can only be obtained by
|
||||||
/// calling the `RouteParams::from` function and passing in the entire decorator
|
/// calling the `RouteParams::from` function and passing in the entire decorator
|
||||||
/// environment.
|
/// environment.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RouteParams {
|
pub struct RouteParams {
|
||||||
pub annotated_fn: Function,
|
pub annotated_fn: Function,
|
||||||
pub method: Spanned<Method>,
|
pub method: Spanned<Method>,
|
||||||
pub path: Spanned<String>,
|
pub path: Spanned<String>,
|
||||||
pub form_param: Option<KVSpanned<String>>,
|
pub form_param: Option<KVSpanned<Ident>>,
|
||||||
|
pub query_param: Option<Spanned<Ident>>,
|
||||||
pub format: Option<KVSpanned<ContentType>>,
|
pub format: Option<KVSpanned<ContentType>>,
|
||||||
pub rank: Option<KVSpanned<isize>>,
|
pub rank: Option<KVSpanned<isize>>,
|
||||||
}
|
}
|
||||||
|
@ -66,8 +69,8 @@ impl RouteParams {
|
||||||
ecx.span_fatal(sp, "malformed attribute");
|
ecx.span_fatal(sp, "malformed attribute");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the required path parameter.
|
// Parse the required path and optional query parameters.
|
||||||
let path = parse_path(ecx, &attr_params[0]);
|
let (path, query) = parse_path(ecx, &attr_params[0]);
|
||||||
|
|
||||||
// Parse all of the optional parameters.
|
// Parse all of the optional parameters.
|
||||||
let mut seen_keys = HashSet::new();
|
let mut seen_keys = HashSet::new();
|
||||||
|
@ -105,6 +108,7 @@ impl RouteParams {
|
||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
form_param: form,
|
form_param: form,
|
||||||
|
query_param: query,
|
||||||
format: format,
|
format: format,
|
||||||
rank: rank,
|
rank: rank,
|
||||||
annotated_fn: function,
|
annotated_fn: function,
|
||||||
|
@ -137,6 +141,22 @@ pub fn kv_from_nested(item: &NestedMetaItem) -> Option<KVSpanned<LitKind>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn param_string_to_ident(ecx: &ExtCtxt, s: Spanned<&str>) -> Option<Ident> {
|
||||||
|
let string = s.node;
|
||||||
|
if string.starts_with('<') && string.ends_with('>') {
|
||||||
|
let param = &string[1..(string.len() - 1)];
|
||||||
|
if param.chars().all(char::is_alphanumeric) {
|
||||||
|
return Some(str_to_ident(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
ecx.span_err(s.span, "parameter name must be alphanumeric");
|
||||||
|
} else {
|
||||||
|
ecx.span_err(s.span, "parameters must start with '<' and end with '>'");
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<Method> {
|
fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<Method> {
|
||||||
if let Some(word) = meta_item.word() {
|
if let Some(word) = meta_item.word() {
|
||||||
if let Ok(method) = Method::from_str(&*word.name()) {
|
if let Ok(method) = Method::from_str(&*word.name()) {
|
||||||
|
@ -157,18 +177,29 @@ fn parse_method(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<Method> {
|
||||||
return dummy_spanned(Method::Get);
|
return dummy_spanned(Method::Get);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<String> {
|
fn parse_path(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> (Spanned<String>, Option<Spanned<Ident>>) {
|
||||||
|
let from_string = |string: &str, sp: Span| {
|
||||||
|
if let Some(q) = string.find('?') {
|
||||||
|
let path = span(string[..q].to_string(), sp);
|
||||||
|
let q_str = span(&string[(q + 1)..], sp);
|
||||||
|
let query = param_string_to_ident(ecx, q_str).map(|i| span(i, sp));
|
||||||
|
return (path, query);
|
||||||
|
} else {
|
||||||
|
return (span(string.to_string(), sp), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let sp = meta_item.span();
|
let sp = meta_item.span();
|
||||||
if let Some((name, lit)) = meta_item.name_value() {
|
if let Some((name, lit)) = meta_item.name_value() {
|
||||||
if name != "path" {
|
if name != "path" {
|
||||||
ecx.span_err(sp, "the first key, if any, must be 'path'");
|
ecx.span_err(sp, "the first key, if any, must be 'path'");
|
||||||
} else if let LitKind::Str(ref s, _) = lit.node {
|
} else if let LitKind::Str(ref s, _) = lit.node {
|
||||||
return span(s.to_string(), lit.span);
|
return from_string(s, lit.span);
|
||||||
} else {
|
} else {
|
||||||
ecx.span_err(lit.span, "`path` value must be a string")
|
ecx.span_err(lit.span, "`path` value must be a string")
|
||||||
}
|
}
|
||||||
} else if let Some(s) = meta_item.str_lit() {
|
} else if let Some(s) = meta_item.str_lit() {
|
||||||
return span(s.to_string(), sp);
|
return from_string(s, sp);
|
||||||
} else {
|
} else {
|
||||||
ecx.struct_span_err(sp, r#"expected `path = string` or a path string"#)
|
ecx.struct_span_err(sp, r#"expected `path = string` or a path string"#)
|
||||||
.help(r#"you can specify the path directly as a string, \
|
.help(r#"you can specify the path directly as a string, \
|
||||||
|
@ -177,7 +208,7 @@ fn parse_path(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<String> {
|
||||||
.emit();
|
.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
dummy_spanned("".to_string())
|
(dummy_spanned("".to_string()), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
||||||
|
@ -186,15 +217,10 @@ fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanne
|
||||||
Some(kv.map_ref(|_| f(ecx, kv)))
|
Some(kv.map_ref(|_| f(ecx, kv)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_form(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> String {
|
fn parse_form(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> Ident {
|
||||||
if let LitKind::Str(ref s, _) = *kv.value() {
|
if let LitKind::Str(ref s, _) = *kv.value() {
|
||||||
if s.starts_with('<') && s.ends_with('>') {
|
if let Some(ident) = param_string_to_ident(ecx, span(s, kv.value.span)) {
|
||||||
let form_param = s[1..(s.len() - 1)].to_string();
|
return ident;
|
||||||
if form_param.chars().all(char::is_alphanumeric) {
|
|
||||||
return form_param;
|
|
||||||
}
|
|
||||||
|
|
||||||
ecx.span_err(kv.value.span, "parameter name must be alphanumeric");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +230,7 @@ fn parse_form(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> String {
|
||||||
parameter inside '<' '>'. e.g: form = "<login>""#)
|
parameter inside '<' '>'. e.g: form = "<login>""#)
|
||||||
.emit();
|
.emit();
|
||||||
|
|
||||||
"".to_string()
|
str_to_ident("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use syntax::ast::{Arg, PatKind, Ident};
|
use syntax::ast::{Arg, PatKind, Ident, Name};
|
||||||
|
|
||||||
pub trait ArgExt {
|
pub trait ArgExt {
|
||||||
fn ident(&self) -> Option<&Ident>;
|
fn ident(&self) -> Option<&Ident>;
|
||||||
|
|
||||||
fn name(&self) -> Option<String> {
|
fn name(&self) -> Option<&Name> {
|
||||||
self.ident().map(|ident| {
|
self.ident().map(|ident| {
|
||||||
ident.name.to_string()
|
&ident.name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn named(&self, name: &str) -> bool {
|
fn named(&self, name: &Name) -> bool {
|
||||||
self.name().map_or(false, |a| a == name)
|
self.name().map_or(false, |a| a == name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue