mirror of https://github.com/rwf2/Rocket.git
Reimplement route attribute as a proc-macro.
This commits also implement the query reform from #608. It also consists of many, many breaking changes. Among them are: * Query parts in route paths use new query reform syntax. * Routing for queries is now lenient. - Default ranking has changed to reflect query reform. * Format routing matching has been fixed. - Routes with formats matching "accept" will always collide. - Routes with formats matching "content-type" require requests to have an equivalent content-type header to match. - Requests with imprecise content-types are treated as not having a content-type. * Generated routes and catchers respect visibility modifiers. * Raw getter methods from request were renamed and retooled. - In particular, the index parameter is based on segments in the route path, not dynamic parameters. * The method-based attributes no longer accept a keyed 'path'. * The 'rocket_codegen' crate is gone and will no longer be public. * The 'FormItems' iterator emits values of type 'FormItem'. - The internal form items' string can no longer be retrieved. * In general, routes are more strictly validated. * Logging from codegen now funnels through logging infrastructure. * Routing has been optimized by caching routing metadata. Resolves #93. Resolves #608. Resolves #693. Resolves #476.
This commit is contained in:
parent
3eb873d89d
commit
61f107f550
|
@ -10,8 +10,7 @@ Rocket is web framework for Rust (nightly) with a focus on ease-of-use,
|
||||||
expressibility, and speed. Here's an example of a complete Rocket application:
|
expressibility, and speed. Here's an example of a complete Rocket application:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#![feature(plugin, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -96,15 +96,15 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
||||||
match pool {
|
match pool {
|
||||||
Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))),
|
Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))),
|
||||||
Err(config_error) => {
|
Err(config_error) => {
|
||||||
::rocket::logger::log_err(false,
|
::rocket::logger::log_error(
|
||||||
&format!("Database configuration failure: '{}'", #name));
|
&format!("Database configuration failure: '{}'", #name));
|
||||||
::rocket::logger::log_err(true, &format!("{}", config_error));
|
::rocket::logger::log_error_(&format!("{}", config_error));
|
||||||
Err(rocket)
|
Err(rocket)
|
||||||
},
|
},
|
||||||
Ok(Err(pool_error)) => {
|
Ok(Err(pool_error)) => {
|
||||||
::rocket::logger::log_err(false,
|
::rocket::logger::log_error(
|
||||||
&format!("Failed to initialize pool for '{}'", #name));
|
&format!("Failed to initialize pool for '{}'", #name));
|
||||||
::rocket::logger::log_err(true, &format!("{:?}", pool_error));
|
::rocket::logger::log_error_(&format!("{:?}", pool_error));
|
||||||
Err(rocket)
|
Err(rocket)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,8 +41,8 @@
|
||||||
//! This crate is expected to grow with time, bringing in outside crates to be
|
//! This crate is expected to grow with time, bringing in outside crates to be
|
||||||
//! officially supported by Rocket.
|
//! officially supported by Rocket.
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
#[allow(unused_imports)] #[macro_use] extern crate log;
|
||||||
#[macro_use] extern crate rocket;
|
#[allow(unused_imports)] #[macro_use] extern crate rocket;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod route;
|
|
||||||
|
|
||||||
pub use self::route::*;
|
|
|
@ -1,395 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use ::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX, PARAM_PREFIX, URI_INFO_MACRO_PREFIX};
|
|
||||||
use ::{ROUTE_ATTR, ROUTE_INFO_ATTR};
|
|
||||||
use parser::{Param, RouteParams};
|
|
||||||
use utils::*;
|
|
||||||
|
|
||||||
use syntax::source_map::{Span, Spanned, dummy_spanned};
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ast::{Arg, Ident, Item, Stmt, Expr, MetaItem, Path};
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
|
||||||
use syntax::ext::build::AstBuilder;
|
|
||||||
use syntax::parse::token;
|
|
||||||
use syntax::symbol::LocalInternedString;
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
use rocket_http::{Method, MediaType};
|
|
||||||
|
|
||||||
fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
|
|
||||||
quote_enum!(ecx, method => ::rocket_http::Method -> ::rocket::http::Method {
|
|
||||||
Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: I think we also want the attributes here.
|
|
||||||
fn media_type_to_expr(ecx: &ExtCtxt, ct: Option<MediaType>) -> Option<P<Expr>> {
|
|
||||||
ct.map(|ct| {
|
|
||||||
let (top, sub) = (ct.top().as_str(), ct.sub().as_str());
|
|
||||||
quote_expr!(ecx, ::rocket::http::MediaType {
|
|
||||||
source: ::rocket::http::Source::None,
|
|
||||||
top: ::rocket::http::Indexed::Concrete(
|
|
||||||
::std::borrow::Cow::Borrowed($top)
|
|
||||||
),
|
|
||||||
sub: ::rocket::http::Indexed::Concrete(
|
|
||||||
::std::borrow::Cow::Borrowed($sub)
|
|
||||||
),
|
|
||||||
params: ::rocket::http::MediaParams::Static(&[])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RouteParams {
|
|
||||||
fn missing_declared_err<T: Display>(&self, ecx: &ExtCtxt, arg: &Spanned<T>) {
|
|
||||||
let (fn_span, fn_name) = (self.annotated_fn.span(), self.annotated_fn.ident());
|
|
||||||
ecx.struct_span_err(arg.span, &format!("unused dynamic parameter: `{}`", arg.node))
|
|
||||||
.span_note(fn_span, &format!("expected argument named `{}` in `{}` handler",
|
|
||||||
arg.node, fn_name))
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
return None;
|
|
||||||
} else if arg.is_none() {
|
|
||||||
self.missing_declared_err(ecx, param.unwrap());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let arg = arg.unwrap();
|
|
||||||
let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX);
|
|
||||||
let ty = strip_ty_lifetimes(arg.ty.clone());
|
|
||||||
Some(quote_stmt!(ecx,
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
let $name: $ty = {
|
|
||||||
let mut items = ::rocket::request::FormItems::from($form_string);
|
|
||||||
let form = ::rocket::request::FromForm::from_form(items.by_ref(), true);
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
let obj = match form {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return ::rocket::Outcome::Forward(__data)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !items.exhaust() {
|
|
||||||
println!(" => The query string {:?} is malformed.", $form_string);
|
|
||||||
return ::rocket::Outcome::Failure(::rocket::http::Status::BadRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj
|
|
||||||
}
|
|
||||||
).expect("form statement"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_data_statements(&self, ecx: &ExtCtxt) -> Option<(Stmt, Stmt)> {
|
|
||||||
let param = self.data_param.as_ref().map(|p| &p.value)?;
|
|
||||||
let arg = self.annotated_fn.find_input(¶m.node.name);
|
|
||||||
if arg.is_none() {
|
|
||||||
self.missing_declared_err(ecx, param);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let arg = arg.unwrap();
|
|
||||||
let name = arg.ident().expect("form param identifier").prepend(PARAM_PREFIX);
|
|
||||||
let ty = strip_ty_lifetimes(arg.ty.clone());
|
|
||||||
|
|
||||||
let transform_stmt = quote_stmt!(ecx,
|
|
||||||
let __transform = <$ty as ::rocket::data::FromData>::transform(__req, __data);
|
|
||||||
).expect("data statement");
|
|
||||||
|
|
||||||
let data_stmt = quote_stmt!(ecx,
|
|
||||||
#[allow(non_snake_case, unreachable_patterns)]
|
|
||||||
let $name: $ty = {
|
|
||||||
let __outcome = match __transform {
|
|
||||||
::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v)) => {
|
|
||||||
::rocket::data::Transform::Owned(::rocket::Outcome::Success(__v))
|
|
||||||
}
|
|
||||||
::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(ref v)) => {
|
|
||||||
let borrow = ::std::borrow::Borrow::borrow(v);
|
|
||||||
::rocket::data::Transform::Borrowed(::rocket::Outcome::Success(borrow))
|
|
||||||
}
|
|
||||||
::rocket::data::Transform::Owned(inner) => {
|
|
||||||
::rocket::data::Transform::Owned(inner)
|
|
||||||
}
|
|
||||||
::rocket::data::Transform::Borrowed(inner) => {
|
|
||||||
::rocket::data::Transform::Borrowed(inner.map(|_| unreachable!()))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match <$ty as ::rocket::data::FromData>::from_data(__req, __outcome) {
|
|
||||||
::rocket::Outcome::Success(d) => d,
|
|
||||||
::rocket::Outcome::Forward(d) => {
|
|
||||||
return ::rocket::Outcome::Forward(d);
|
|
||||||
}
|
|
||||||
::rocket::Outcome::Failure((code, _)) => {
|
|
||||||
return ::rocket::Outcome::Failure(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
).expect("data statement");
|
|
||||||
|
|
||||||
Some((transform_stmt, data_stmt))
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
Some(query) => query,
|
|
||||||
None => return ::rocket::Outcome::Forward(__data)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
self.gen_form(ecx, param, expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add some kind of logging facility in Rocket to get be able to log
|
|
||||||
// an error/debug message if parsing a parameter fails.
|
|
||||||
fn generate_param_statements(&self, ecx: &ExtCtxt) -> Vec<Stmt> {
|
|
||||||
let mut fn_param_statements = vec![];
|
|
||||||
let params = Param::parse_many(ecx, self.uri.node.path(), self.uri.span.trim(1))
|
|
||||||
.unwrap_or_else(|mut diag| { diag.emit(); vec![] });
|
|
||||||
|
|
||||||
// Generate a statement for every declared paramter in the path.
|
|
||||||
let mut declared_set = HashSet::new();
|
|
||||||
for (i, param) in params.iter().enumerate() {
|
|
||||||
declared_set.insert(param.ident().name);
|
|
||||||
let ty = match self.annotated_fn.find_input(¶m.ident().name) {
|
|
||||||
Some(arg) => strip_ty_lifetimes(arg.ty.clone()),
|
|
||||||
None => {
|
|
||||||
self.missing_declared_err(ecx, param.inner());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Note: the `None` case shouldn't happen if a route is matched.
|
|
||||||
let ident = param.ident().prepend(PARAM_PREFIX);
|
|
||||||
let expr = match *param {
|
|
||||||
Param::Single(_) => quote_expr!(ecx, match __req.get_param_str($i) {
|
|
||||||
Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s),
|
|
||||||
None => return ::rocket::Outcome::Forward(__data)
|
|
||||||
}),
|
|
||||||
Param::Many(_) => quote_expr!(ecx, match __req.get_raw_segments($i) {
|
|
||||||
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
|
|
||||||
None => return ::rocket::Outcome::Forward(__data)
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let original_ident = param.ident();
|
|
||||||
fn_param_statements.push(quote_stmt!(ecx,
|
|
||||||
#[allow(non_snake_case, unreachable_patterns)]
|
|
||||||
let $ident: $ty = match $expr {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
println!(" => Failed to parse '{}': {:?}",
|
|
||||||
stringify!($original_ident), e);
|
|
||||||
return ::rocket::Outcome::Forward(__data)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
).expect("declared param parsing statement"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A from_request parameter is one that isn't declared, data, or query.
|
|
||||||
let from_request = |a: &&Arg| {
|
|
||||||
if let Some(name) = a.name() {
|
|
||||||
!declared_set.contains(name)
|
|
||||||
&& self.data_param.as_ref().map_or(true, |p| {
|
|
||||||
!a.named(&p.value().name)
|
|
||||||
}) && self.query_param.as_ref().map_or(true, |p| {
|
|
||||||
!a.named(&p.node.name)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ecx.span_err(a.pat.span, "route argument names must be identifiers");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the code for `from_request` parameters.
|
|
||||||
let all = &self.annotated_fn.decl().inputs;
|
|
||||||
for arg in all.iter().filter(from_request) {
|
|
||||||
let ident = arg.ident().unwrap().prepend(PARAM_PREFIX);
|
|
||||||
let ty = strip_ty_lifetimes(arg.ty.clone());
|
|
||||||
fn_param_statements.push(quote_stmt!(ecx,
|
|
||||||
#[allow(non_snake_case, unreachable_patterns)]
|
|
||||||
let $ident: $ty = match
|
|
||||||
::rocket::request::FromRequest::from_request(__req) {
|
|
||||||
::rocket::Outcome::Success(v) => v,
|
|
||||||
::rocket::Outcome::Forward(_) =>
|
|
||||||
return ::rocket::Outcome::Forward(__data),
|
|
||||||
::rocket::Outcome::Failure((code, _)) => {
|
|
||||||
return ::rocket::Outcome::Failure(code)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
).expect("undeclared param parsing statement"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn_param_statements
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_fn_arguments(&self, ecx: &ExtCtxt) -> Vec<TokenTree> {
|
|
||||||
let args = self.annotated_fn.decl().inputs.iter()
|
|
||||||
.filter_map(|a| a.ident())
|
|
||||||
.map(|ident| ident.prepend(PARAM_PREFIX))
|
|
||||||
.collect::<Vec<Ident>>();
|
|
||||||
|
|
||||||
sep_by_tok(ecx, &args, token::Comma)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_uri_macro(&self, ecx: &ExtCtxt) -> P<Item> {
|
|
||||||
let macro_args = parse_as_tokens(ecx, "$($token:tt)*");
|
|
||||||
let macro_exp = parse_as_tokens(ecx, "$($token)*");
|
|
||||||
let macro_name = self.annotated_fn.ident().prepend(URI_INFO_MACRO_PREFIX);
|
|
||||||
|
|
||||||
// What we return if we find an inconsistency throughout.
|
|
||||||
let dummy = quote_item!(ecx, pub macro $macro_name($macro_args) { }).unwrap();
|
|
||||||
|
|
||||||
// Hacky check to see if the user's URI was valid.
|
|
||||||
if self.uri.span == dummy_spanned(()).span {
|
|
||||||
return dummy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the route uri path and paramters from the uri.
|
|
||||||
let route_path = self.uri.node.to_string();
|
|
||||||
let params = match Param::parse_many(ecx, &route_path, self.uri.span.trim(1)) {
|
|
||||||
Ok(params) => params,
|
|
||||||
Err(mut diag) => {
|
|
||||||
diag.cancel();
|
|
||||||
return dummy;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the list of arguments for the URI.
|
|
||||||
let mut fn_uri_args = vec![];
|
|
||||||
for param in ¶ms {
|
|
||||||
if let Some(arg) = self.annotated_fn.find_input(¶m.ident().name) {
|
|
||||||
let (pat, ty) = (&arg.pat, &arg.ty);
|
|
||||||
fn_uri_args.push(quote_tokens!(ecx, $pat: $ty))
|
|
||||||
} else {
|
|
||||||
return dummy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the call to the internal URI macro with all the info.
|
|
||||||
let args = sep_by_tok(ecx, &fn_uri_args, token::Comma);
|
|
||||||
quote_item!(ecx,
|
|
||||||
pub macro $macro_name($macro_args) {
|
|
||||||
rocket_internal_uri!($route_path, ($args), $macro_exp)
|
|
||||||
}
|
|
||||||
).expect("consistent uri macro item")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn explode(&self, ecx: &ExtCtxt) -> (LocalInternedString, String, Path, P<Expr>, P<Expr>) {
|
|
||||||
let name = self.annotated_fn.ident().name.as_str();
|
|
||||||
let path = self.uri.node.to_string();
|
|
||||||
let method = method_to_path(ecx, self.method.node);
|
|
||||||
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 rank = option_as_expr(ecx, &self.rank);
|
|
||||||
|
|
||||||
(name, path, method, media_type, rank)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Compilation fails when parameters have the same name as the function!
|
|
||||||
fn generic_route_decorator(known_method: Option<Spanned<Method>>,
|
|
||||||
ecx: &mut ExtCtxt,
|
|
||||||
sp: Span,
|
|
||||||
meta_item: &MetaItem,
|
|
||||||
annotated: Annotatable
|
|
||||||
) -> Vec<Annotatable> {
|
|
||||||
let mut output = Vec::new();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
debug!("Route params: {:?}", route);
|
|
||||||
|
|
||||||
let param_statements = route.generate_param_statements(ecx);
|
|
||||||
let query_statement = route.generate_query_statement(ecx);
|
|
||||||
let fn_arguments = route.generate_fn_arguments(ecx);
|
|
||||||
let uri_macro = route.generate_uri_macro(ecx);
|
|
||||||
let (transform_statement, data_statement) = route.generate_data_statements(ecx)
|
|
||||||
.map(|(a, b)| (Some(a), Some(b)))
|
|
||||||
.unwrap_or((None, None));
|
|
||||||
|
|
||||||
// Generate and emit the wrapping function with the Rocket handler signature.
|
|
||||||
let user_fn_name = route.annotated_fn.ident();
|
|
||||||
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
|
|
||||||
emit_item(&mut output, quote_item!(ecx,
|
|
||||||
// Allow the `unreachable_code` lint for those FromParam impls that have
|
|
||||||
// an `Error` associated type of !.
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
fn $route_fn_name<'_b>(__req: &'_b ::rocket::Request, __data: ::rocket::Data)
|
|
||||||
-> ::rocket::handler::Outcome<'_b> {
|
|
||||||
$param_statements
|
|
||||||
$query_statement
|
|
||||||
$transform_statement
|
|
||||||
$data_statement
|
|
||||||
let responder = $user_fn_name($fn_arguments);
|
|
||||||
::rocket::handler::Outcome::from(__req, responder)
|
|
||||||
}
|
|
||||||
).unwrap());
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
let struct_name = user_fn_name.prepend(ROUTE_STRUCT_PREFIX);
|
|
||||||
let (name, path, method, media_type, rank) = route.explode(ecx);
|
|
||||||
let static_route_info_item = quote_item!(ecx,
|
|
||||||
/// Rocket code generated static route information structure.
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub static $struct_name: ::rocket::StaticRouteInfo =
|
|
||||||
::rocket::StaticRouteInfo {
|
|
||||||
name: $name,
|
|
||||||
method: $method,
|
|
||||||
path: $path,
|
|
||||||
handler: $route_fn_name,
|
|
||||||
format: $media_type,
|
|
||||||
rank: $rank,
|
|
||||||
};
|
|
||||||
).expect("static route info");
|
|
||||||
|
|
||||||
// Attach a `rocket_route_info` attribute to the route info and emit it.
|
|
||||||
let attr_name = Ident::from_str(ROUTE_INFO_ATTR);
|
|
||||||
let info_attr = quote_attr!(ecx, #[$attr_name]);
|
|
||||||
attach_and_emit(&mut output, info_attr, Annotatable::Item(static_route_info_item));
|
|
||||||
|
|
||||||
// Attach a `rocket_route` attribute to the user's function and emit it.
|
|
||||||
let attr_name = Ident::from_str(ROUTE_ATTR);
|
|
||||||
let route_attr = quote_attr!(ecx, #[$attr_name($struct_name)]);
|
|
||||||
attach_and_emit(&mut output, route_attr, annotated);
|
|
||||||
|
|
||||||
// Emit the per-route URI macro.
|
|
||||||
emit_item(&mut output, uri_macro);
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn route_decorator(
|
|
||||||
ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
|
|
||||||
) -> Vec<Annotatable> {
|
|
||||||
generic_route_decorator(None, ecx, sp, meta_item, annotated)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! method_decorator {
|
|
||||||
($name:ident, $method:ident) => (
|
|
||||||
pub fn $name(
|
|
||||||
ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: Annotatable
|
|
||||||
) -> Vec<Annotatable> {
|
|
||||||
let i_sp = meta_item.span.shorten_to(stringify!($method).len());
|
|
||||||
let method = Some(span(Method::$method, i_sp));
|
|
||||||
generic_route_decorator(method, ecx, sp, meta_item, annotated)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
method_decorator!(get_decorator, Get);
|
|
||||||
method_decorator!(put_decorator, Put);
|
|
||||||
method_decorator!(post_decorator, Post);
|
|
||||||
method_decorator!(delete_decorator, Delete);
|
|
||||||
method_decorator!(head_decorator, Head);
|
|
||||||
method_decorator!(patch_decorator, Patch);
|
|
||||||
method_decorator!(options_decorator, Options);
|
|
|
@ -1,8 +1,4 @@
|
||||||
#![crate_type = "dylib"]
|
#![crate_type = "dylib"]
|
||||||
#![feature(quote, concat_idents, plugin_registrar, rustc_private)]
|
|
||||||
#![feature(custom_attribute)]
|
|
||||||
#![allow(unused_attributes)]
|
|
||||||
#![allow(deprecated)]
|
|
||||||
|
|
||||||
// TODO: Version URLs.
|
// TODO: Version URLs.
|
||||||
#![doc(html_root_url = "https://api.rocket.rs")]
|
#![doc(html_root_url = "https://api.rocket.rs")]
|
||||||
|
@ -389,51 +385,3 @@
|
||||||
//! ```
|
//! ```
|
||||||
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
//! ROCKET_CODEGEN_DEBUG=1 cargo build
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
extern crate syntax;
|
|
||||||
extern crate syntax_ext;
|
|
||||||
extern crate syntax_pos;
|
|
||||||
extern crate rustc_plugin;
|
|
||||||
extern crate rocket_http;
|
|
||||||
extern crate indexmap;
|
|
||||||
|
|
||||||
#[macro_use] mod utils;
|
|
||||||
mod parser;
|
|
||||||
mod decorators;
|
|
||||||
|
|
||||||
use rustc_plugin::Registry;
|
|
||||||
use syntax::ext::base::SyntaxExtension;
|
|
||||||
use syntax::symbol::Symbol;
|
|
||||||
|
|
||||||
const DEBUG_ENV_VAR: &str = "ROCKET_CODEGEN_DEBUG";
|
|
||||||
|
|
||||||
const PARAM_PREFIX: &str = "rocket_param_";
|
|
||||||
const ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
|
|
||||||
const ROUTE_FN_PREFIX: &str = "rocket_route_fn_";
|
|
||||||
const URI_INFO_MACRO_PREFIX: &str = "rocket_uri_for_";
|
|
||||||
|
|
||||||
const ROUTE_ATTR: &str = "rocket_route";
|
|
||||||
const ROUTE_INFO_ATTR: &str = "rocket_route_info";
|
|
||||||
|
|
||||||
macro_rules! register_decorators {
|
|
||||||
($registry:expr, $($name:expr => $func:ident),+) => (
|
|
||||||
$($registry.register_syntax_extension(Symbol::intern($name),
|
|
||||||
SyntaxExtension::MultiModifier(Box::new(decorators::$func)));
|
|
||||||
)+
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compiler hook for Rust to register plugins.
|
|
||||||
#[plugin_registrar]
|
|
||||||
pub fn plugin_registrar(reg: &mut Registry) {
|
|
||||||
register_decorators!(reg,
|
|
||||||
"route" => route_decorator,
|
|
||||||
"get" => get_decorator,
|
|
||||||
"put" => put_decorator,
|
|
||||||
"post" => post_decorator,
|
|
||||||
"delete" => delete_decorator,
|
|
||||||
"head" => head_decorator,
|
|
||||||
"patch" => patch_decorator,
|
|
||||||
"options" => options_decorator
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use syntax::ast::*;
|
|
||||||
use syntax::source_map::{Span, Spanned};
|
|
||||||
use syntax::ext::base::Annotatable;
|
|
||||||
use utils::{ArgExt, span};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Function(Spanned<(Ident, FnDecl)>);
|
|
||||||
|
|
||||||
impl Function {
|
|
||||||
pub fn from(annotated: &Annotatable) -> Result<Function, Span> {
|
|
||||||
if let Annotatable::Item(ref item) = *annotated {
|
|
||||||
if let ItemKind::Fn(ref decl, ..) = item.node {
|
|
||||||
let inner = (item.ident, decl.clone().into_inner());
|
|
||||||
return Ok(Function(span(inner, item.span)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(annotated.span())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ident(&self) -> &Ident {
|
|
||||||
&self.0.node.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decl(&self) -> &FnDecl {
|
|
||||||
&self.0.node.1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn span(&self) -> Span {
|
|
||||||
self.0.span
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_input<'a>(&'a self, name: &Name) -> Option<&'a Arg> {
|
|
||||||
self.decl().inputs.iter().find(|arg| arg.named(name))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use syntax::source_map::{Spanned, Span, dummy_spanned, DUMMY_SP};
|
|
||||||
use syntax::ext::base::ExtCtxt;
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ext::quote::rt::ToTokens;
|
|
||||||
use utils::span;
|
|
||||||
|
|
||||||
/// A spanned key-value pair in an attribute.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct KVSpanned<V> {
|
|
||||||
/// Span for the full key/value pair.
|
|
||||||
pub span: Span,
|
|
||||||
/// The spanned key.
|
|
||||||
pub key: Spanned<String>,
|
|
||||||
/// The spanned value.
|
|
||||||
pub value: Spanned<V>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V> KVSpanned<V> {
|
|
||||||
/// Maps a reference of the inner value of this span using `f`. The key,
|
|
||||||
/// value, and full spans will remain the same in the new KVSpan.
|
|
||||||
pub fn map_ref<U, F: FnOnce(&V) -> U>(&self, f: F) -> KVSpanned<U> {
|
|
||||||
KVSpanned {
|
|
||||||
span: self.span,
|
|
||||||
key: self.key.clone(),
|
|
||||||
value: span(f(&self.value.node), self.value.span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the unspanned key. Purely for convenience.
|
|
||||||
pub fn key(&self) -> &String {
|
|
||||||
&self.key.node
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the unspanned value. Purely for convenience.
|
|
||||||
pub fn value(&self) -> &V {
|
|
||||||
&self.value.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default> Default for KVSpanned<T> {
|
|
||||||
/// Returns a dummy KVSpan with an empty key for the default value of T.
|
|
||||||
fn default() -> KVSpanned<T> {
|
|
||||||
dummy_kvspanned("", T::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToTokens> ToTokens for KVSpanned<T> {
|
|
||||||
fn to_tokens(&self, cx: &ExtCtxt) -> Vec<TokenTree> {
|
|
||||||
self.value().to_tokens(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a KVSpanned value with dummy (meaningless) spans.
|
|
||||||
pub fn dummy_kvspanned<T, S: ToString>(key: S, value: T) -> KVSpanned<T> {
|
|
||||||
KVSpanned {
|
|
||||||
span: DUMMY_SP,
|
|
||||||
key: dummy_spanned(key.to_string()),
|
|
||||||
value: dummy_spanned(value),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
mod keyvalue;
|
|
||||||
mod route;
|
|
||||||
mod param;
|
|
||||||
mod function;
|
|
||||||
mod uri;
|
|
||||||
|
|
||||||
pub use self::keyvalue::KVSpanned;
|
|
||||||
pub use self::route::RouteParams;
|
|
||||||
pub use self::param::Param;
|
|
||||||
pub use self::function::Function;
|
|
|
@ -1,66 +0,0 @@
|
||||||
use syntax::ast::Ident;
|
|
||||||
use syntax::source_map::{Span, Spanned};
|
|
||||||
use syntax::ext::base::ExtCtxt;
|
|
||||||
use utils::SpanExt;
|
|
||||||
use syntax::parse::PResult;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Param {
|
|
||||||
Single(Spanned<Ident>),
|
|
||||||
Many(Spanned<Ident>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Param {
|
|
||||||
pub fn inner(&self) -> &Spanned<Ident> {
|
|
||||||
match *self {
|
|
||||||
Param::Single(ref ident) | Param::Many(ref ident) => ident,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ident(&self) -> &Ident {
|
|
||||||
match *self {
|
|
||||||
Param::Single(ref ident) | Param::Many(ref ident) => &ident.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_many<'a>(
|
|
||||||
ecx: &ExtCtxt<'a>,
|
|
||||||
mut string: &str,
|
|
||||||
mut span: Span
|
|
||||||
) -> PResult<'a, Vec<Param>> {
|
|
||||||
let err = |sp, msg| { Err(ecx.struct_span_err(sp, msg)) };
|
|
||||||
|
|
||||||
let mut params = vec![];
|
|
||||||
loop {
|
|
||||||
// Find the start and end indexes for the next parameter, if any.
|
|
||||||
let (start, end) = match string.find('<') {
|
|
||||||
Some(i) => match string.find('>') {
|
|
||||||
Some(j) if j > i => (i, j),
|
|
||||||
Some(_) => return err(span, "malformed parameters"),
|
|
||||||
None => return err(span, "malformed parameters")
|
|
||||||
},
|
|
||||||
_ => return Ok(params)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate the parameter's ident and span.
|
|
||||||
let param_span = span.trim_left(start).shorten_to(end + 1);
|
|
||||||
let full_param = &string[(start + 1)..end];
|
|
||||||
let (is_many, param) = if full_param.ends_with("..") {
|
|
||||||
(true, &full_param[..(full_param.len() - 2)])
|
|
||||||
} else {
|
|
||||||
(false, full_param)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Advance the string and span.
|
|
||||||
string = &string[(end + 1)..];
|
|
||||||
span = span.trim_left(end + 1);
|
|
||||||
|
|
||||||
let spanned_ident = param_span.wrap(Ident::from_str(param));
|
|
||||||
if is_many {
|
|
||||||
params.push(Param::Many(spanned_ident))
|
|
||||||
} else {
|
|
||||||
params.push(Param::Single(spanned_ident))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,281 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use syntax::ast::*;
|
|
||||||
use syntax::ext::base::{ExtCtxt, Annotatable};
|
|
||||||
use syntax::source_map::{Span, Spanned, dummy_spanned};
|
|
||||||
|
|
||||||
use utils::{MetaItemExt, SpanExt, span, is_valid_ident};
|
|
||||||
use super::Function;
|
|
||||||
use super::keyvalue::KVSpanned;
|
|
||||||
use super::uri::validate_uri;
|
|
||||||
use rocket_http::{Method, MediaType};
|
|
||||||
use rocket_http::uri::Origin;
|
|
||||||
|
|
||||||
/// This structure represents the parsed `route` attribute.
|
|
||||||
///
|
|
||||||
/// It contains all of the information supplied by the user and the span where
|
|
||||||
/// the user supplied the information. This structure can only be obtained by
|
|
||||||
/// calling the `RouteParams::from` function and passing in the entire decorator
|
|
||||||
/// environment.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RouteParams {
|
|
||||||
pub annotated_fn: Function,
|
|
||||||
pub method: Spanned<Method>,
|
|
||||||
pub uri: Spanned<Origin<'static>>,
|
|
||||||
pub data_param: Option<KVSpanned<Ident>>,
|
|
||||||
pub query_param: Option<Spanned<Ident>>,
|
|
||||||
pub format: Option<KVSpanned<MediaType>>,
|
|
||||||
pub rank: Option<KVSpanned<isize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RouteParams {
|
|
||||||
/// Parses the route attribute from the given decorator context. If the
|
|
||||||
/// parse is not successful, this function exits early with the appropriate
|
|
||||||
/// error message to the user.
|
|
||||||
pub fn from(
|
|
||||||
ecx: &mut ExtCtxt,
|
|
||||||
sp: Span,
|
|
||||||
known_method: Option<Spanned<Method>>,
|
|
||||||
meta_item: &MetaItem,
|
|
||||||
annotated: &Annotatable
|
|
||||||
) -> RouteParams {
|
|
||||||
let function = Function::from(annotated).unwrap_or_else(|item_sp| {
|
|
||||||
ecx.span_err(sp, "this attribute can only be used on functions...");
|
|
||||||
ecx.span_fatal(item_sp, "...but was applied to the item below.");
|
|
||||||
});
|
|
||||||
|
|
||||||
let meta_items = meta_item.meta_item_list().unwrap_or_else(|| {
|
|
||||||
ecx.struct_span_err(sp, "incorrect use of attribute")
|
|
||||||
.help("attributes in Rocket must have the form: #[name(...)]")
|
|
||||||
.emit();
|
|
||||||
ecx.span_fatal(sp, "malformed attribute");
|
|
||||||
});
|
|
||||||
|
|
||||||
if meta_items.is_empty() {
|
|
||||||
ecx.span_fatal(sp, "attribute requires at least 1 parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out the method. If it is known (i.e, because we're parsing a
|
|
||||||
// helper attribute), use that method directly. Otherwise, try to parse
|
|
||||||
// it from the list of meta items.
|
|
||||||
let (method, attr_params) = match known_method {
|
|
||||||
Some(method) => (method, meta_items),
|
|
||||||
None => (parse_method(ecx, &meta_items[0]), &meta_items[1..])
|
|
||||||
};
|
|
||||||
|
|
||||||
if attr_params.is_empty() {
|
|
||||||
ecx.struct_span_err(sp, "attribute requires at least a path")
|
|
||||||
.help(r#"example: #[get("/my/path")] or #[get(path = "/hi")]"#)
|
|
||||||
.emit();
|
|
||||||
ecx.span_fatal(sp, "malformed attribute");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the required path and optional query parameters.
|
|
||||||
let (uri, query) = parse_path(ecx, &attr_params[0]);
|
|
||||||
|
|
||||||
// Parse all of the optional parameters.
|
|
||||||
let mut seen_keys = HashSet::new();
|
|
||||||
let (mut rank, mut data, mut format) = Default::default();
|
|
||||||
for param in &attr_params[1..] {
|
|
||||||
let kv_opt = kv_from_nested(param);
|
|
||||||
if kv_opt.is_none() {
|
|
||||||
ecx.span_err(param.span(), "expected key = value");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let kv = kv_opt.unwrap();
|
|
||||||
match kv.key().as_str() {
|
|
||||||
"rank" => rank = parse_opt(ecx, &kv, parse_rank),
|
|
||||||
"data" => data = parse_opt(ecx, &kv, parse_data),
|
|
||||||
"format" => format = parse_opt(ecx, &kv, parse_format),
|
|
||||||
_ => {
|
|
||||||
let msg = format!("'{}' is not a known parameter", kv.key());
|
|
||||||
ecx.span_err(kv.span, &msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen_keys.contains(kv.key()) {
|
|
||||||
let msg = format!("{} was already defined", kv.key());
|
|
||||||
ecx.struct_span_warn(param.span, &msg)
|
|
||||||
.note("the last declared value will be used")
|
|
||||||
.emit();
|
|
||||||
} else {
|
|
||||||
seen_keys.insert(kv.key().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check: `data` should only be used with payload methods.
|
|
||||||
if let Some(ref data_param) = data {
|
|
||||||
if !method.node.supports_payload() {
|
|
||||||
ecx.struct_span_warn(data_param.span, "`data` route parameter \
|
|
||||||
used with non-payload-supporting method")
|
|
||||||
.note(&format!("'{}' does not typically support payloads", method.node))
|
|
||||||
.note("the 'format' attribute parameter will match against \
|
|
||||||
the 'Accept' header")
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RouteParams {
|
|
||||||
method, uri, format, rank,
|
|
||||||
data_param: data,
|
|
||||||
query_param: query,
|
|
||||||
annotated_fn: function,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid_method(method: Method) -> bool {
|
|
||||||
use rocket_http::Method::*;
|
|
||||||
match method {
|
|
||||||
Get | Put | Post | Delete | Head | Patch | Options => true,
|
|
||||||
Trace | Connect => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kv_from_nested(item: &NestedMetaItem) -> Option<KVSpanned<LitKind>> {
|
|
||||||
item.name_value().map(|(name, value)| {
|
|
||||||
let k_span = item.span().shorten_to(name.as_str().len());
|
|
||||||
KVSpanned {
|
|
||||||
key: span(name.to_string(), k_span),
|
|
||||||
value: value.clone(),
|
|
||||||
span: item.span(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn param_to_ident(ecx: &ExtCtxt, s: Spanned<&str>) -> Option<Spanned<Ident>> {
|
|
||||||
let string = s.node;
|
|
||||||
if string.starts_with('<') && string.ends_with('>') {
|
|
||||||
let param = &string[1..(string.len() - 1)];
|
|
||||||
if is_valid_ident(param) {
|
|
||||||
return Some(span(Ident::from_str(param), s.span.trim(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
let default_method = dummy_spanned(Method::Get);
|
|
||||||
let valid_methods = "valid methods are: `GET`, `PUT`, `POST`, `DELETE`, \
|
|
||||||
`HEAD`, `PATCH`, `OPTIONS`";
|
|
||||||
|
|
||||||
if let Some(word) = meta_item.word() {
|
|
||||||
if let Ok(method) = Method::from_str(&word.name().as_str()) {
|
|
||||||
if is_valid_method(method) {
|
|
||||||
return span(method, word.span());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg = format!("'{}' is not a valid method", word.ident);
|
|
||||||
ecx.struct_span_err(word.span, &msg).help(valid_methods).emit();
|
|
||||||
return default_method;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallthrough. Emit a generic error message and return default method.
|
|
||||||
let msg = "expected a valid HTTP method identifier";
|
|
||||||
ecx.struct_span_err(meta_item.span, msg).help(valid_methods).emit();
|
|
||||||
dummy_spanned(Method::Get)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_path(
|
|
||||||
ecx: &ExtCtxt,
|
|
||||||
meta_item: &NestedMetaItem
|
|
||||||
) -> (Spanned<Origin<'static>>, Option<Spanned<Ident>>) {
|
|
||||||
let sp = meta_item.span();
|
|
||||||
if let Some((name, lit)) = meta_item.name_value() {
|
|
||||||
if name != "path" {
|
|
||||||
ecx.span_err(sp, "the first key, if any, must be 'path'");
|
|
||||||
} else if let LitKind::Str(ref s, _) = lit.node {
|
|
||||||
return validate_uri(ecx, &s.as_str(), lit.span);
|
|
||||||
} else {
|
|
||||||
ecx.span_err(lit.span, "`path` value must be a string")
|
|
||||||
}
|
|
||||||
} else if let Some(s) = meta_item.str_lit() {
|
|
||||||
return validate_uri(ecx, &s.as_str(), sp);
|
|
||||||
} else {
|
|
||||||
ecx.struct_span_err(sp, r#"expected `path = string` or a path string"#)
|
|
||||||
.help(r#"you can specify the path directly as a string, \
|
|
||||||
e.g: "/hello/world", or as a key-value pair, \
|
|
||||||
e.g: path = "/hello/world" "#)
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
(dummy_spanned(Origin::dummy()), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_opt<O, T, F>(ecx: &ExtCtxt, kv: &KVSpanned<T>, f: F) -> Option<KVSpanned<O>>
|
|
||||||
where F: Fn(&ExtCtxt, &KVSpanned<T>) -> O
|
|
||||||
{
|
|
||||||
Some(kv.map_ref(|_| f(ecx, kv)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_data(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> Ident {
|
|
||||||
let mut ident = Ident::from_str("unknown");
|
|
||||||
if let LitKind::Str(ref s, _) = *kv.value() {
|
|
||||||
ident = Ident::from_str(&s.as_str());
|
|
||||||
if let Some(id) = param_to_ident(ecx, span(&s.as_str(), kv.value.span)) {
|
|
||||||
return id.node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let err_string = r#"`data` value must be a parameter, e.g: "<name>"`"#;
|
|
||||||
ecx.struct_span_fatal(kv.span, err_string)
|
|
||||||
.help(r#"data, if specified, must be a key-value pair where
|
|
||||||
the key is `data` and the value is a string with a single
|
|
||||||
parameter inside '<' '>'. e.g: data = "<user_form>""#)
|
|
||||||
.emit();
|
|
||||||
|
|
||||||
ident
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
|
|
||||||
if let LitKind::Int(n, _) = *kv.value() {
|
|
||||||
let max = isize::max_value();
|
|
||||||
if n <= max as u128 {
|
|
||||||
return n as isize;
|
|
||||||
} else {
|
|
||||||
let msg = format!("rank must be less than or equal to {}", max);
|
|
||||||
ecx.span_err(kv.value.span, msg.as_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ecx.struct_span_err(kv.span, r#"`rank` value must be an int"#)
|
|
||||||
.help(r#"the rank, if specified, must be a key-value pair where
|
|
||||||
the key is `rank` and the value is an integer.
|
|
||||||
e.g: rank = 1, or e.g: rank = 10"#)
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> MediaType {
|
|
||||||
if let LitKind::Str(ref s, _) = *kv.value() {
|
|
||||||
if let Some(ct) = MediaType::parse_flexible(&s.as_str()) {
|
|
||||||
if !ct.is_known() {
|
|
||||||
let msg = format!("'{}' is not a known media type", s);
|
|
||||||
ecx.span_warn(kv.value.span, &msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ct;
|
|
||||||
} else {
|
|
||||||
ecx.span_err(kv.value.span, "malformed media type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ecx.struct_span_err(kv.span, r#"`format` must be a "media/type""#)
|
|
||||||
.help(r#"format, if specified, must be a key-value pair where
|
|
||||||
the key is `format` and the value is a string representing the
|
|
||||||
media type accepted. e.g: format = "application/json".
|
|
||||||
shorthand is also accepted: format = "json"#)
|
|
||||||
.emit();
|
|
||||||
|
|
||||||
MediaType::Any
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
use syntax::ast::*;
|
|
||||||
use syntax::source_map::{Span, Spanned, dummy_spanned};
|
|
||||||
use syntax::ext::base::ExtCtxt;
|
|
||||||
|
|
||||||
use rocket_http::ext::IntoOwned;
|
|
||||||
use rocket_http::uri::{Uri, Origin};
|
|
||||||
use super::route::param_to_ident;
|
|
||||||
use utils::{span, SpanExt, is_valid_ident};
|
|
||||||
|
|
||||||
// We somewhat arbitrarily enforce absolute paths. This is mostly because we
|
|
||||||
// want the initial "/" to represent the mount point. Empty segments are
|
|
||||||
// stripped out at runtime. So, to avoid any confusion, we issue an error at
|
|
||||||
// compile-time for empty segments. At the moment, this disallows trailing
|
|
||||||
// slashes as well, since then the last segment is empty.
|
|
||||||
fn valid_path(ecx: &ExtCtxt, uri: &Origin, sp: Span) -> bool {
|
|
||||||
if !uri.is_normalized() {
|
|
||||||
let normalized = uri.to_normalized();
|
|
||||||
ecx.struct_span_err(sp, "paths cannot contain empty segments")
|
|
||||||
.note(&format!("expected '{}', found '{}'", normalized, uri))
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
uri.is_normalized()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_segments(ecx: &ExtCtxt, uri: &Origin, sp: Span) -> bool {
|
|
||||||
let mut validated = true;
|
|
||||||
let mut segments_span = None;
|
|
||||||
for segment in uri.segments() {
|
|
||||||
// We add one to the index to account for the '/'.
|
|
||||||
let index = segment.as_ptr() as usize - uri.path().as_ptr() as usize;
|
|
||||||
let span = sp.trim_left(index + 1).shorten_to(segment.len());
|
|
||||||
|
|
||||||
// If we're iterating after a '..' param, that's a hard error.
|
|
||||||
if let Some(span) = segments_span {
|
|
||||||
let rem_sp = sp.trim_left(index).trim_right(1);
|
|
||||||
ecx.struct_span_err(rem_sp, "text after a trailing '..' param")
|
|
||||||
.help("a segments param must be the final text in a path")
|
|
||||||
.span_note(span, "trailing param is here")
|
|
||||||
.emit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a dynamic param. If so, check it's well-formedness.
|
|
||||||
if segment.starts_with('<') && segment.ends_with('>') {
|
|
||||||
let mut param = &segment[1..(segment.len() - 1)];
|
|
||||||
if segment.ends_with("..>") {
|
|
||||||
segments_span = Some(span);
|
|
||||||
param = ¶m[..(param.len() - 2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if param.is_empty() {
|
|
||||||
ecx.span_err(span, "parameters cannot be empty");
|
|
||||||
} else if !is_valid_ident(param) {
|
|
||||||
ecx.struct_span_err(span, "parameter names must be valid identifiers")
|
|
||||||
.note(&format!("{:?} is not a valid identifier", param))
|
|
||||||
.emit();
|
|
||||||
} else if param == "_" {
|
|
||||||
ecx.struct_span_err(span, "parameters must be named")
|
|
||||||
.help("use a name such as `_guard` or `_param`")
|
|
||||||
.emit();
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
validated = false;
|
|
||||||
} else if segment.starts_with('<') {
|
|
||||||
if segment[1..].contains('<') || segment.contains('>') {
|
|
||||||
ecx.struct_span_err(span, "malformed parameter")
|
|
||||||
.help("parameters must be of the form '<param>'")
|
|
||||||
.emit();
|
|
||||||
} else {
|
|
||||||
ecx.struct_span_err(span, "parameter is missing a closing bracket")
|
|
||||||
.help(&format!("perhaps you meant '{}>'?", segment))
|
|
||||||
.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
validated = false;
|
|
||||||
} else if Uri::percent_encode(segment) != segment {
|
|
||||||
if segment.contains('<') || segment.contains('>') {
|
|
||||||
ecx.struct_span_err(span, "malformed parameter")
|
|
||||||
.help("parameters must be of the form '<param>'")
|
|
||||||
.emit();
|
|
||||||
} else {
|
|
||||||
ecx.span_err(span, "segment contains invalid characters");
|
|
||||||
}
|
|
||||||
|
|
||||||
validated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_uri(
|
|
||||||
ecx: &ExtCtxt,
|
|
||||||
string: &str,
|
|
||||||
sp: Span,
|
|
||||||
) -> (Spanned<Origin<'static>>, Option<Spanned<Ident>>) {
|
|
||||||
let query_param = string.find('?')
|
|
||||||
.map(|i| span(&string[(i + 1)..], sp.trim_left(i + 1)))
|
|
||||||
.and_then(|spanned_q_param| param_to_ident(ecx, spanned_q_param));
|
|
||||||
|
|
||||||
let dummy = (dummy_spanned(Origin::dummy()), query_param);
|
|
||||||
match Origin::parse_route(string) {
|
|
||||||
Ok(uri) => {
|
|
||||||
let uri = uri.into_owned();
|
|
||||||
if valid_segments(ecx, &uri, sp) && valid_path(ecx, &uri, sp) {
|
|
||||||
return (span(uri, sp), query_param)
|
|
||||||
}
|
|
||||||
|
|
||||||
dummy
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
ecx.struct_span_err(sp, &format!("invalid path URI: {}", e))
|
|
||||||
.note("expected path URI in origin form")
|
|
||||||
.help("example: /path/<route>")
|
|
||||||
.emit();
|
|
||||||
|
|
||||||
dummy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use syntax::ast::{Arg, PatKind, Ident, Name};
|
|
||||||
|
|
||||||
pub trait ArgExt {
|
|
||||||
fn ident(&self) -> Option<&Ident>;
|
|
||||||
|
|
||||||
fn name(&self) -> Option<&Name> {
|
|
||||||
self.ident().map(|ident| &ident.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn named(&self, name: &Name) -> bool {
|
|
||||||
self.name().map_or(false, |a| a == name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgExt for Arg {
|
|
||||||
fn ident(&self) -> Option<&Ident> {
|
|
||||||
match self.pat.node {
|
|
||||||
PatKind::Ident(_, ref ident, _) => Some(&ident),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use syntax::ast::{GenericParam, GenericParamKind};
|
|
||||||
|
|
||||||
pub trait GenericParamExt {
|
|
||||||
/// Returns `true` if `self` is of kind `lifetime`.
|
|
||||||
fn is_lifetime(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenericParamExt for GenericParam {
|
|
||||||
fn is_lifetime(&self) -> bool {
|
|
||||||
match self.kind {
|
|
||||||
GenericParamKind::Lifetime => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
use syntax::ast::{LitKind, NestedMetaItem, MetaItemKind, Lit};
|
|
||||||
use syntax::symbol::Symbol;
|
|
||||||
|
|
||||||
pub trait MetaItemExt {
|
|
||||||
fn name_value(&self) -> Option<(Symbol, &Lit)>;
|
|
||||||
fn str_lit(&self) -> Option<&Symbol>;
|
|
||||||
fn int_lit(&self) -> Option<u128>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaItemExt for NestedMetaItem {
|
|
||||||
fn name_value(&self) -> Option<(Symbol, &Lit)> {
|
|
||||||
self.meta_item().and_then(|mi| match mi.node {
|
|
||||||
MetaItemKind::NameValue(ref l) => Some((mi.name(), l)),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn str_lit(&self) -> Option<&Symbol> {
|
|
||||||
self.literal().and_then(|lit| match lit.node {
|
|
||||||
LitKind::Str(ref s, _) => Some(s),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn int_lit(&self) -> Option<u128> {
|
|
||||||
self.literal().and_then(|lit| match lit.node {
|
|
||||||
LitKind::Int(n, _) => Some(n),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
mod meta_item_ext;
|
|
||||||
mod arg_ext;
|
|
||||||
mod parser_ext;
|
|
||||||
mod ident_ext;
|
|
||||||
mod span_ext;
|
|
||||||
mod expr_ext;
|
|
||||||
mod generics_ext;
|
|
||||||
|
|
||||||
pub use self::arg_ext::ArgExt;
|
|
||||||
pub use self::meta_item_ext::MetaItemExt;
|
|
||||||
pub use self::parser_ext::ParserExt;
|
|
||||||
pub use self::ident_ext::IdentExt;
|
|
||||||
pub use self::span_ext::SpanExt;
|
|
||||||
pub use self::expr_ext::ExprExt;
|
|
||||||
pub use self::generics_ext::GenericParamExt;
|
|
||||||
|
|
||||||
use std::convert::AsRef;
|
|
||||||
|
|
||||||
use syntax;
|
|
||||||
use syntax::parse::token::Token;
|
|
||||||
use syntax::tokenstream::TokenTree;
|
|
||||||
use syntax::ast::{Item, Expr, Attribute, Ty};
|
|
||||||
use syntax::ext::base::{Annotatable, ExtCtxt};
|
|
||||||
use syntax::source_map::{Span, Spanned, DUMMY_SP};
|
|
||||||
use syntax::ext::quote::rt::ToTokens;
|
|
||||||
use syntax::print::pprust::item_to_string;
|
|
||||||
use syntax::symbol::{Ident, Symbol};
|
|
||||||
use syntax::fold::Folder;
|
|
||||||
use syntax::attr::HasAttrs;
|
|
||||||
use syntax::ptr::P;
|
|
||||||
|
|
||||||
macro_rules! debug {
|
|
||||||
($($t:tt)*) => (
|
|
||||||
// Enable debug logs if the DEBUG_ENV_VAR is set.
|
|
||||||
if ::std::env::var(::DEBUG_ENV_VAR).is_ok() {
|
|
||||||
eprintln!("--> {}:{} ({})", file!(), line!(), module_path!());
|
|
||||||
eprintln!($($t)*);
|
|
||||||
eprintln!();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn span<T>(t: T, span: Span) -> Spanned<T> {
|
|
||||||
Spanned { node: t, span }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sep_by_tok<T>(ecx: &ExtCtxt, things: &[T], token: Token) -> Vec<TokenTree>
|
|
||||||
where T: ToTokens
|
|
||||||
{
|
|
||||||
let mut output: Vec<TokenTree> = vec![];
|
|
||||||
for (i, thing) in things.iter().enumerate() {
|
|
||||||
output.extend(thing.to_tokens(ecx));
|
|
||||||
if i < things.len() - 1 {
|
|
||||||
output.push(TokenTree::Token(DUMMY_SP, token.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn option_as_expr<T: ToTokens>(ecx: &ExtCtxt, opt: &Option<T>) -> P<Expr> {
|
|
||||||
match *opt {
|
|
||||||
Some(ref item) => quote_expr!(ecx, Some($item)),
|
|
||||||
None => quote_expr!(ecx, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emit_item(items: &mut Vec<Annotatable>, item: P<Item>) {
|
|
||||||
debug!("Emitting item:\n{}", item_to_string(&item));
|
|
||||||
items.push(Annotatable::Item(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attach_and_emit(out: &mut Vec<Annotatable>, attr: Attribute, to: Annotatable) {
|
|
||||||
syntax::attr::mark_used(&attr);
|
|
||||||
syntax::attr::mark_known(&attr);
|
|
||||||
|
|
||||||
// Attach the attribute to the user's function and emit it.
|
|
||||||
if let Annotatable::Item(user_item) = to {
|
|
||||||
let item = user_item.map_attrs(|mut attrs| {
|
|
||||||
attrs.push(attr);
|
|
||||||
attrs
|
|
||||||
});
|
|
||||||
|
|
||||||
emit_item(out, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_as_tokens(ecx: &ExtCtxt, s: &str) -> Vec<TokenTree> {
|
|
||||||
use syntax_pos::FileName;
|
|
||||||
use syntax::parse::parse_stream_from_source_str as parse_stream;
|
|
||||||
|
|
||||||
parse_stream(FileName::ProcMacroSourceCode, s.into(), ecx.parse_sess, None)
|
|
||||||
.into_trees()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TyLifetimeRemover;
|
|
||||||
|
|
||||||
impl Folder for TyLifetimeRemover {
|
|
||||||
fn fold_ident(&mut self, ident: Ident) -> Ident {
|
|
||||||
if ident.as_str().starts_with('\'') {
|
|
||||||
Ident::new(Symbol::intern("'_"), ident.span)
|
|
||||||
} else {
|
|
||||||
ident
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn strip_ty_lifetimes(ty: P<Ty>) -> P<Ty> {
|
|
||||||
TyLifetimeRemover.fold_ty(ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifted from Rust's lexer, except this takes a `char`, not an `Option<char>`.
|
|
||||||
fn ident_start(c: char) -> bool {
|
|
||||||
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ||
|
|
||||||
(c > '\x7f' && c.is_xid_start())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifted from Rust's lexer, except this takes a `char`, not an `Option<char>`.
|
|
||||||
fn ident_continue(c: char) -> bool {
|
|
||||||
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
|
|
||||||
c == '_' || (c > '\x7f' && c.is_xid_continue())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_valid_ident<S: AsRef<str>>(s: S) -> bool {
|
|
||||||
let string = s.as_ref();
|
|
||||||
if string.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, c) in string.chars().enumerate() {
|
|
||||||
if i == 0 {
|
|
||||||
if !ident_start(c) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if !ident_continue(c) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! quote_enum {
|
|
||||||
($ecx:expr, $var:expr => $(::$from_root:ident)+ -> $(::$to_root:ident)+
|
|
||||||
{ $($variant:ident),+ ; $($extra:pat => $result:expr),* }) => ({
|
|
||||||
use syntax::source_map::DUMMY_SP;
|
|
||||||
use syntax::ast::Ident;
|
|
||||||
use $(::$from_root)+::*;
|
|
||||||
let root_idents = vec![$(Ident::from_str(stringify!($to_root))),+];
|
|
||||||
match $var {
|
|
||||||
$($variant => {
|
|
||||||
let variant = Ident::from_str(stringify!($variant));
|
|
||||||
let mut idents = root_idents.clone();
|
|
||||||
idents.push(variant);
|
|
||||||
$ecx.path_global(DUMMY_SP, idents)
|
|
||||||
})+
|
|
||||||
$($extra => $result),*
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
use rocket::http::{Cookies, RawStr};
|
|
||||||
use rocket::request::Form;
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
|
||||||
struct User<'a> {
|
|
||||||
name: &'a RawStr,
|
|
||||||
nickname: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
|
|
||||||
fn get(
|
|
||||||
_name: &RawStr,
|
|
||||||
_query: User,
|
|
||||||
user: Form<User>,
|
|
||||||
_cookies: Cookies
|
|
||||||
) -> String {
|
|
||||||
format!("{}:{}", user.name, user.nickname)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
let _ = routes![get];
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
#[post("/", format = "application/x-custom")]
|
|
||||||
fn get() -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() { }
|
|
|
@ -1,16 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/test/<one>/<two>/<three>")]
|
|
||||||
fn get(one: String, two: usize, three: isize) -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[get("/test/<_one>/<_two>/<__three>")]
|
|
||||||
fn ignored(_one: String, _two: usize, __three: isize) -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
let _ = routes![get, ignored];
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn get() -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn get_empty() { }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() { }
|
|
|
@ -1,40 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/one")]
|
|
||||||
fn one() { }
|
|
||||||
|
|
||||||
#[get("/two")]
|
|
||||||
fn two() { }
|
|
||||||
|
|
||||||
#[get("/three")]
|
|
||||||
fn three() { }
|
|
||||||
|
|
||||||
#[get("/four")]
|
|
||||||
fn four() { }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
let instance = rocket::ignite()
|
|
||||||
.mount("/", routes![one]);
|
|
||||||
|
|
||||||
let other = instance.mount("/", routes![two]);
|
|
||||||
other.mount("/", routes![three])
|
|
||||||
.mount("/", routes![four]);
|
|
||||||
|
|
||||||
rocket::ignite()
|
|
||||||
.mount("/", routes![one])
|
|
||||||
.mount("/", routes![two])
|
|
||||||
.mount("/", routes![three])
|
|
||||||
.mount("/", routes![four]);
|
|
||||||
|
|
||||||
let a = rocket::ignite()
|
|
||||||
.mount("/", routes![one])
|
|
||||||
.mount("/", routes![two]);
|
|
||||||
|
|
||||||
let b = a.mount("/", routes![three])
|
|
||||||
.mount("/", routes![four]);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/<todo>")]
|
|
||||||
fn todo(todo: String) -> String {
|
|
||||||
todo
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
let _ = routes![todo];
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/")] fn get() { }
|
|
||||||
#[route(GET, "/")] fn get_r() { }
|
|
||||||
|
|
||||||
#[put("/")] fn put() { }
|
|
||||||
#[route(PUT, "/")] fn put_r() { }
|
|
||||||
|
|
||||||
#[post("/")] fn post() { }
|
|
||||||
#[route(POST, "/")] fn post_r() { }
|
|
||||||
|
|
||||||
#[delete("/")] fn delete() { }
|
|
||||||
#[route(DELETE, "/")] fn delete_r() { }
|
|
||||||
|
|
||||||
#[head("/")] fn head() { }
|
|
||||||
#[route(HEAD, "/")] fn head_r() { }
|
|
||||||
|
|
||||||
#[patch("/")] fn patch() { }
|
|
||||||
#[route(PATCH, "/")] fn patch_r() { }
|
|
||||||
|
|
||||||
#[options("/")] fn options() { }
|
|
||||||
#[route(OPTIONS, "/")] fn options_r() { }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() { }
|
|
|
@ -1,16 +0,0 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
#[get("/", rank = 1)]
|
|
||||||
fn get1() -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[get("/", rank = 2)]
|
|
||||||
fn get2() -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[get("/", rank = 3)]
|
|
||||||
fn get3() -> &'static str { "hi" }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() { }
|
|
|
@ -1,28 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
use rocket::{Rocket, State};
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn index(state: State<u32>) { }
|
|
||||||
|
|
||||||
fn rocket() -> Rocket {
|
|
||||||
rocket::ignite()
|
|
||||||
.mount("/", routes![index])
|
|
||||||
.manage(100u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
if false {
|
|
||||||
rocket().launch();
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance = rocket();
|
|
||||||
if false {
|
|
||||||
instance.launch();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use rocket::http::uri::SegmentError;
|
|
||||||
|
|
||||||
#[post("/<a>/<b..>")]
|
|
||||||
fn get(a: String, b: PathBuf) -> String {
|
|
||||||
format!("{}/{}", a, b.to_string_lossy())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/<a>/<b..>")]
|
|
||||||
fn get2(a: String, b: Result<PathBuf, SegmentError>) -> String {
|
|
||||||
format!("{}/{}", a, b.unwrap().to_string_lossy())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() { }
|
|
|
@ -1,25 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code, unused_variables)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
use rocket::State;
|
|
||||||
|
|
||||||
type MyState<'r> = State<'r, usize>;
|
|
||||||
|
|
||||||
type MyVecState<'r, T> = State<'r, Vec<T>>;
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn index(state: MyState) { }
|
|
||||||
|
|
||||||
#[get("/a")]
|
|
||||||
fn another(state: MyVecState<usize>) { }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn main() {
|
|
||||||
rocket::ignite()
|
|
||||||
.manage(10usize)
|
|
||||||
.manage(vec![1usize, 2usize, 3usize])
|
|
||||||
.mount("/", routes![index, another]);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
@ -67,10 +67,10 @@ fn no_uri_display_okay(id: i32, form: Form<Second>) -> &'static str {
|
||||||
"Typed URI testing."
|
"Typed URI testing."
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<name>?<query>", data = "<user>", rank = 2)]
|
#[post("/<name>?<query..>", data = "<user>", rank = 2)]
|
||||||
fn complex<'r>(
|
fn complex<'r>(
|
||||||
name: &RawStr,
|
name: &RawStr,
|
||||||
query: User<'r>,
|
query: Form<User<'r>>,
|
||||||
user: Form<User<'r>>,
|
user: Form<User<'r>>,
|
||||||
cookies: Cookies
|
cookies: Cookies
|
||||||
) -> &'static str { "" }
|
) -> &'static str { "" }
|
||||||
|
|
|
@ -20,11 +20,14 @@ proc-macro = true
|
||||||
indexmap = "1.0"
|
indexmap = "1.0"
|
||||||
quote = "0.6.1"
|
quote = "0.6.1"
|
||||||
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
rocket_http = { version = "0.4.0-dev", path = "../http/" }
|
||||||
|
indexmap = "1"
|
||||||
|
|
||||||
[dependencies.derive_utils]
|
[dependencies.derive_utils]
|
||||||
git = "https://github.com/SergioBenitez/derive-utils"
|
path = "/Users/sbenitez/Sync/Data/Projects/Snippets/derive-utils/lib"
|
||||||
rev = "87ad56ba"
|
# git = "https://github.com/SergioBenitez/derive-utils"
|
||||||
|
# rev = "87ad56ba"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { version = "0.4.0-dev", path = "../lib" }
|
rocket = { version = "0.4.0-dev", path = "../lib" }
|
||||||
|
rocket_codegen = { version = "0.4.0-dev", path = "../codegen" }
|
||||||
compiletest_rs = "0.3.14"
|
compiletest_rs = "0.3.14"
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::{TokenStream, Span};
|
||||||
use derive_utils::{syn, Spanned, Result, FromMeta};
|
use derive_utils::{syn, Spanned, Result, FromMeta};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use proc_macro::Span;
|
|
||||||
|
|
||||||
use http_codegen::Status;
|
use http_codegen::Status;
|
||||||
use syn_ext::{syn_to_diag, IdentExt, ReturnTypeExt};
|
use syn_ext::{syn_to_diag, IdentExt, ReturnTypeExt};
|
||||||
use self::syn::{Attribute, parse::Parser};
|
use self::syn::{Attribute, parse::Parser};
|
||||||
|
use {CATCH_FN_PREFIX, CATCH_STRUCT_PREFIX};
|
||||||
crate const CATCH_FN_PREFIX: &str = "rocket_catch_fn_";
|
|
||||||
crate const CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
|
|
||||||
|
|
||||||
/// The raw, parsed `#[catch(code)]` attribute.
|
/// The raw, parsed `#[catch(code)]` attribute.
|
||||||
#[derive(Debug, FromMeta)]
|
#[derive(Debug, FromMeta)]
|
||||||
|
@ -72,9 +69,8 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||||
.unwrap_or(Span::call_site().into());
|
.unwrap_or(Span::call_site().into());
|
||||||
|
|
||||||
let catcher_response = quote_spanned!(return_type_span => {
|
let catcher_response = quote_spanned!(return_type_span => {
|
||||||
// Check the type signature.
|
// Emit this to force a type signature check.
|
||||||
let __catcher: #fn_sig = #user_catcher_fn_name;
|
let __catcher: #fn_sig = #user_catcher_fn_name;
|
||||||
// Generate the response.
|
|
||||||
::rocket::response::Responder::respond_to(__catcher(#inputs), __req)?
|
::rocket::response::Responder::respond_to(__catcher(#inputs), __req)?
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -82,6 +78,7 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#user_catcher_fn
|
#user_catcher_fn
|
||||||
|
|
||||||
|
/// Rocket code generated wrapping catch function.
|
||||||
#vis fn #generated_fn_name<'_b>(
|
#vis fn #generated_fn_name<'_b>(
|
||||||
__req: &'_b ::rocket::Request
|
__req: &'_b ::rocket::Request
|
||||||
) -> ::rocket::response::Result<'_b> {
|
) -> ::rocket::response::Result<'_b> {
|
||||||
|
@ -92,6 +89,7 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rocket code generated static catcher info.
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#vis static #generated_struct_name: ::rocket::StaticCatchInfo =
|
#vis static #generated_struct_name: ::rocket::StaticCatchInfo =
|
||||||
::rocket::StaticCatchInfo {
|
::rocket::StaticCatchInfo {
|
||||||
|
@ -102,11 +100,5 @@ pub fn _catch(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn catch_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn catch_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
match _catch(args, input) {
|
_catch(args, input).unwrap_or_else(|d| { d.emit(); TokenStream::new() })
|
||||||
Ok(tokens) => tokens,
|
|
||||||
Err(diag) => {
|
|
||||||
diag.emit();
|
|
||||||
TokenStream::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
pub mod catch;
|
pub mod catch;
|
||||||
|
pub mod route;
|
||||||
|
pub mod segments;
|
||||||
|
|
|
@ -0,0 +1,445 @@
|
||||||
|
use proc_macro::{TokenStream, Span};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use derive_utils::{syn, Spanned, Result, FromMeta, ext::TypeExt};
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
|
use proc_macro_ext::Diagnostics;
|
||||||
|
use syn_ext::{syn_to_diag, IdentExt};
|
||||||
|
use self::syn::{Attribute, parse::Parser};
|
||||||
|
|
||||||
|
use http_codegen::{Method, MediaType, RoutePath, DataSegment, Optional};
|
||||||
|
use attribute::segments::{Source, Kind, Segment};
|
||||||
|
use {ROUTE_FN_PREFIX, ROUTE_STRUCT_PREFIX, URI_MACRO_PREFIX, ROCKET_PARAM_PREFIX};
|
||||||
|
|
||||||
|
/// The raw, parsed `#[route]` attribute.
|
||||||
|
#[derive(Debug, FromMeta)]
|
||||||
|
struct RouteAttribute {
|
||||||
|
#[meta(naked)]
|
||||||
|
method: Method,
|
||||||
|
path: RoutePath,
|
||||||
|
data: Option<DataSegment>,
|
||||||
|
format: Option<MediaType>,
|
||||||
|
rank: Option<isize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The raw, parsed `#[method]` (e.g, `get`, `put`, `post`, etc.) attribute.
|
||||||
|
#[derive(Debug, FromMeta)]
|
||||||
|
struct MethodRouteAttribute {
|
||||||
|
#[meta(naked)]
|
||||||
|
path: RoutePath,
|
||||||
|
data: Option<DataSegment>,
|
||||||
|
format: Option<MediaType>,
|
||||||
|
rank: Option<isize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure represents the parsed `route` attribute and associated items.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Route {
|
||||||
|
/// The status associated with the code in the `#[route(code)]` attribute.
|
||||||
|
attribute: RouteAttribute,
|
||||||
|
/// The function that was decorated with the `route` attribute.
|
||||||
|
function: syn::ItemFn,
|
||||||
|
/// The non-static parameters declared in the route segments.
|
||||||
|
segments: IndexSet<Segment>,
|
||||||
|
/// The parsed inputs to the user's function. The first ident is the ident
|
||||||
|
/// as the user wrote it, while the second ident is the identifier that
|
||||||
|
/// should be used during code generation, the `rocket_ident`.
|
||||||
|
inputs: Vec<(syn::Ident, syn::Ident, syn::Type)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
|
||||||
|
// Gather diagnostics as we proceed.
|
||||||
|
let mut diags = Diagnostics::new();
|
||||||
|
|
||||||
|
// Collect all of the dynamic segments in an `IndexSet`, checking for dups.
|
||||||
|
let mut segments: IndexSet<Segment> = IndexSet::new();
|
||||||
|
fn dup_check<I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
|
||||||
|
where I: Iterator<Item = Segment>
|
||||||
|
{
|
||||||
|
for segment in iter.filter(|s| s.kind != Kind::Static) {
|
||||||
|
let span = segment.span;
|
||||||
|
if let Some(previous) = set.replace(segment) {
|
||||||
|
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
|
||||||
|
.span_note(previous.span, "previous parameter with the same name here"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dup_check(&mut segments, attr.path.path.iter().cloned(), &mut diags);
|
||||||
|
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter().cloned(), &mut diags));
|
||||||
|
dup_check(&mut segments, attr.data.clone().map(|s| s.0).into_iter(), &mut diags);
|
||||||
|
|
||||||
|
// Check the validity of function arguments.
|
||||||
|
let mut inputs = vec![];
|
||||||
|
let mut fn_segments: IndexSet<Segment> = IndexSet::new();
|
||||||
|
for input in &function.decl.inputs {
|
||||||
|
let help = "all handler arguments must be of the form: `ident: Type`";
|
||||||
|
let span = input.span();
|
||||||
|
let (ident, ty) = match input {
|
||||||
|
syn::FnArg::Captured(arg) => match arg.pat {
|
||||||
|
syn::Pat::Ident(ref pat) => (&pat.ident, &arg.ty),
|
||||||
|
syn::Pat::Wild(_) => {
|
||||||
|
diags.push(span.error("handler arguments cannot be ignored").help(help));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
diags.push(span.error("invalid use of pattern").help(help));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Other cases shouldn't happen since we parsed an `ItemFn`.
|
||||||
|
_ => {
|
||||||
|
diags.push(span.error("invalid handler argument").help(help));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rocket_ident = ident.prepend(ROCKET_PARAM_PREFIX);
|
||||||
|
inputs.push((ident.clone(), rocket_ident, ty.with_stripped_lifetimes()));
|
||||||
|
fn_segments.insert(ident.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all of the declared parameters are function inputs.
|
||||||
|
let span = function.decl.inputs.span();
|
||||||
|
for missing in segments.difference(&fn_segments) {
|
||||||
|
diags.push(missing.span.error("unused dynamic parameter")
|
||||||
|
.span_note(span, format!("expected argument named `{}` here", missing.name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
diags.head_err_or(Route { attribute: attr, function, inputs, segments })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_expr(seg: &Segment, ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 {
|
||||||
|
let i = seg.index.expect("dynamic parameters must be indexed");
|
||||||
|
let span = ident.span().unstable().join(ty.span()).unwrap().into();
|
||||||
|
let name = ident.to_string();
|
||||||
|
|
||||||
|
// All dynamic parameter should be found if this function is being called;
|
||||||
|
// that's the point of statically checking the URI parameters.
|
||||||
|
let internal_error = quote!({
|
||||||
|
log_error("Internal invariant error: expected dynamic parameter not found.");
|
||||||
|
log_error("Please report this error to the Rocket issue tracker.");
|
||||||
|
Outcome::Forward(__data)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Returned when a dynamic parameter fails to parse.
|
||||||
|
let parse_error = quote!({
|
||||||
|
log_warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
|
||||||
|
Outcome::Forward(__data)
|
||||||
|
});
|
||||||
|
|
||||||
|
let expr = match seg.kind {
|
||||||
|
Kind::Single => quote_spanned! { span =>
|
||||||
|
match __req.raw_segment_str(#i) {
|
||||||
|
Some(__s) => match <#ty as FromParam>::from_param(__s) {
|
||||||
|
Ok(__v) => __v,
|
||||||
|
Err(__e) => return #parse_error,
|
||||||
|
},
|
||||||
|
None => return #internal_error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kind::Multi => quote_spanned! { span =>
|
||||||
|
match __req.raw_segments(#i) {
|
||||||
|
Some(__s) => match <#ty as FromSegments>::from_segments(__s) {
|
||||||
|
Ok(__v) => __v,
|
||||||
|
Err(__e) => return #parse_error,
|
||||||
|
},
|
||||||
|
None => return #internal_error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kind::Static => return quote!()
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
|
||||||
|
let #ident: #ty = #expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 {
|
||||||
|
let span = ident.span().unstable().join(ty.span()).unwrap().into();
|
||||||
|
quote_spanned! { span =>
|
||||||
|
let __transform = <#ty as FromData>::transform(__req, __data);
|
||||||
|
|
||||||
|
#[allow(unreachable_patterns, unreachable_code)]
|
||||||
|
let __outcome = match __transform {
|
||||||
|
Owned(Outcome::Success(__v)) => Owned(Outcome::Success(__v)),
|
||||||
|
Borrowed(Outcome::Success(ref __v)) => {
|
||||||
|
Borrowed(Outcome::Success(::std::borrow::Borrow::borrow(__v)))
|
||||||
|
}
|
||||||
|
Borrowed(__o) => Borrowed(__o.map(|_| loop { /* unreachable */ })),
|
||||||
|
Owned(__o) => Owned(__o)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
|
||||||
|
let #ident: #ty = match <#ty as FromData>::from_data(__req, __outcome) {
|
||||||
|
Outcome::Success(__d) => __d,
|
||||||
|
Outcome::Forward(__d) => return Outcome::Forward(__d),
|
||||||
|
Outcome::Failure((__c, _)) => return Outcome::Failure(__c),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_exprs(route: &Route) -> Option<TokenStream2> {
|
||||||
|
let query_segments = route.attribute.path.query.as_ref()?;
|
||||||
|
let (mut decls, mut matchers, mut builders) = (vec![], vec![], vec![]);
|
||||||
|
for segment in query_segments {
|
||||||
|
let name = &segment.name;
|
||||||
|
let (ident, ty, span) = if segment.kind != Kind::Static {
|
||||||
|
let (ident, ty) = route.inputs.iter()
|
||||||
|
.find(|(ident, _, _)| ident == &segment.name)
|
||||||
|
.map(|(_, rocket_ident, ty)| (rocket_ident, ty))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let span = ident.span().unstable().join(ty.span()).unwrap();
|
||||||
|
(Some(ident), Some(ty), span.into())
|
||||||
|
} else {
|
||||||
|
(None, None, segment.span.into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let decl = match segment.kind {
|
||||||
|
Kind::Single => quote_spanned! { span =>
|
||||||
|
let mut #ident: Option<#ty> = None;
|
||||||
|
},
|
||||||
|
Kind::Multi => quote_spanned! { span =>
|
||||||
|
let mut __trail = SmallVec::<[FormItem; 8]>::new();
|
||||||
|
},
|
||||||
|
Kind::Static => quote!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let matcher = match segment.kind {
|
||||||
|
Kind::Single => quote_spanned! { span =>
|
||||||
|
(_, #name, __v) => {
|
||||||
|
#ident = Some(match <#ty as FromFormValue>::from_form_value(__v) {
|
||||||
|
Ok(__v) => __v,
|
||||||
|
Err(__e) => {
|
||||||
|
log_warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
|
||||||
|
return Outcome::Forward(__data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kind::Static => quote! {
|
||||||
|
(#name, _, _) => continue,
|
||||||
|
},
|
||||||
|
Kind::Multi => quote! {
|
||||||
|
_ => __trail.push(__i),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = match segment.kind {
|
||||||
|
Kind::Single => quote_spanned! { span =>
|
||||||
|
let #ident = match #ident.or_else(<#ty as FromFormValue>::default) {
|
||||||
|
Some(__v) => __v,
|
||||||
|
None => {
|
||||||
|
log_warn_(&format!("Missing required query parameter '{}'.", #name));
|
||||||
|
return Outcome::Forward(__data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Kind::Multi => quote_spanned! { span =>
|
||||||
|
let #ident = match <#ty as FromQuery>::from_query(Query(&__trail)) {
|
||||||
|
Ok(__v) => __v,
|
||||||
|
Err(__e) => {
|
||||||
|
log_warn_(&format!("Failed to parse '{}': {:?}", #name, __e));
|
||||||
|
return Outcome::Forward(__data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Kind::Static => quote!()
|
||||||
|
};
|
||||||
|
|
||||||
|
decls.push(decl);
|
||||||
|
matchers.push(matcher);
|
||||||
|
builders.push(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchers.push(quote!(_ => continue));
|
||||||
|
Some(quote! {
|
||||||
|
#(#decls)*
|
||||||
|
|
||||||
|
if let Some(__items) = __req.raw_query_items() {
|
||||||
|
for __i in __items {
|
||||||
|
match (__i.raw.as_str(), __i.key.as_str(), __i.value) {
|
||||||
|
#(
|
||||||
|
#[allow(unreachable_patterns, unreachable_code)]
|
||||||
|
#matchers
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#(
|
||||||
|
#[allow(unreachable_patterns, unreachable_code)]
|
||||||
|
#builders
|
||||||
|
)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_guard_expr(ident: &syn::Ident, ty: &syn::Type) -> TokenStream2 {
|
||||||
|
let span = ident.span().unstable().join(ty.span()).unwrap().into();
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#[allow(non_snake_case, unreachable_patterns, unreachable_code)]
|
||||||
|
let #ident: #ty = match <#ty as FromRequest>::from_request(__req) {
|
||||||
|
Outcome::Success(__v) => __v,
|
||||||
|
Outcome::Forward(_) => return Outcome::Forward(__data),
|
||||||
|
Outcome::Failure((__c, _)) => return Outcome::Failure(__c),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_internal_uri_macro(route: &Route) -> TokenStream2 {
|
||||||
|
let dynamic_args = route.segments.iter()
|
||||||
|
.filter(|seg| seg.source == Source::Path || seg.source == Source::Query)
|
||||||
|
.filter(|seg| seg.kind != Kind::Static)
|
||||||
|
.map(|seg| &seg.name)
|
||||||
|
.map(|name| route.inputs.iter().find(|(ident, ..)| ident == name).unwrap())
|
||||||
|
.map(|(ident, _, ty)| quote!(#ident: #ty));
|
||||||
|
|
||||||
|
let generated_macro_name = route.function.ident.prepend(URI_MACRO_PREFIX);
|
||||||
|
let route_uri = route.attribute.path.origin.0.to_string();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub macro #generated_macro_name($($token:tt)*) {
|
||||||
|
rocket_internal_uri!(#route_uri, (#(#dynamic_args),*), $($token)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn codegen_route(route: Route) -> Result<TokenStream> {
|
||||||
|
// Generate the declarations for path, data, and request guard parameters.
|
||||||
|
let mut data_stmt = None;
|
||||||
|
let mut parameter_definitions = vec![];
|
||||||
|
for (ident, rocket_ident, ty) in &route.inputs {
|
||||||
|
let fn_segment: Segment = ident.into();
|
||||||
|
let parameter_def = match route.segments.get(&fn_segment) {
|
||||||
|
Some(seg) if seg.source == Source::Path => {
|
||||||
|
param_expr(seg, rocket_ident, &ty)
|
||||||
|
}
|
||||||
|
Some(seg) if seg.source == Source::Data => {
|
||||||
|
// the data statement needs to come last, so record it specially
|
||||||
|
data_stmt = Some(data_expr(rocket_ident, &ty));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// handle query parameters later
|
||||||
|
Some(_) => continue,
|
||||||
|
None => request_guard_expr(rocket_ident, &ty),
|
||||||
|
};
|
||||||
|
|
||||||
|
parameter_definitions.push(parameter_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the declarations for query parameters.
|
||||||
|
if let Some(exprs) = query_exprs(&route) {
|
||||||
|
parameter_definitions.push(exprs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather everything we need.
|
||||||
|
let (vis, user_handler_fn) = (&route.function.vis, &route.function);
|
||||||
|
let user_handler_fn_name = &user_handler_fn.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 parameter_names = route.inputs.iter().map(|(_, rocket_ident, _)| rocket_ident);
|
||||||
|
let generated_internal_uri_macro = generate_internal_uri_macro(&route);
|
||||||
|
let method = route.attribute.method;
|
||||||
|
let path = route.attribute.path.origin.0.to_string();
|
||||||
|
let rank = Optional(route.attribute.rank);
|
||||||
|
let format = Optional(route.attribute.format);
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#user_handler_fn
|
||||||
|
|
||||||
|
/// Rocket code generated wrapping route function.
|
||||||
|
#vis fn #generated_fn_name<'_b>(
|
||||||
|
__req: &'_b ::rocket::Request,
|
||||||
|
__data: ::rocket::Data
|
||||||
|
) -> ::rocket::handler::Outcome<'_b> {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use rocket::{
|
||||||
|
handler, Outcome,
|
||||||
|
logger::{log_warn, log_error, log_warn_},
|
||||||
|
data::{FromData, Transform::*},
|
||||||
|
http::{SmallVec, RawStr},
|
||||||
|
request::{FromRequest, FromParam, FromFormValue, FromSegments},
|
||||||
|
request::{Query, FromQuery, FormItems, FormItem},
|
||||||
|
};
|
||||||
|
|
||||||
|
#(#parameter_definitions)*
|
||||||
|
#data_stmt
|
||||||
|
|
||||||
|
let ___responder = #user_handler_fn_name(#(#parameter_names),*);
|
||||||
|
handler::Outcome::from(__req, ___responder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rocket code generated wrapping URI macro.
|
||||||
|
#generated_internal_uri_macro
|
||||||
|
|
||||||
|
/// Rocket code generated static route info.
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#vis static #generated_struct_name: ::rocket::StaticRouteInfo =
|
||||||
|
::rocket::StaticRouteInfo {
|
||||||
|
name: stringify!(#user_handler_fn_name),
|
||||||
|
method: #method,
|
||||||
|
path: #path,
|
||||||
|
handler: #generated_fn_name,
|
||||||
|
format: #format,
|
||||||
|
rank: #rank,
|
||||||
|
};
|
||||||
|
}.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_route(args: TokenStream2, input: TokenStream) -> Result<TokenStream> {
|
||||||
|
let function: syn::ItemFn = syn::parse(input).map_err(syn_to_diag)
|
||||||
|
.map_err(|diag| diag.help("`#[route]` can only be used on functions"))?;
|
||||||
|
|
||||||
|
let full_attr = quote!(#[route(#args)]);
|
||||||
|
let attrs = Attribute::parse_outer.parse2(full_attr).map_err(syn_to_diag)?;
|
||||||
|
let attribute = match RouteAttribute::from_attrs("route", &attrs) {
|
||||||
|
Some(result) => result?,
|
||||||
|
None => return Err(Span::call_site().error("internal error: bad attribute"))
|
||||||
|
};
|
||||||
|
|
||||||
|
codegen_route(parse_route(attribute, function)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incomplete_route(
|
||||||
|
method: ::http::Method,
|
||||||
|
args: TokenStream2,
|
||||||
|
input: TokenStream
|
||||||
|
) -> Result<TokenStream> {
|
||||||
|
let method_str = method.to_string().to_lowercase();
|
||||||
|
let method_ident = syn::Ident::new(&method_str, args.span().into());
|
||||||
|
|
||||||
|
let function: syn::ItemFn = syn::parse(input).map_err(syn_to_diag)
|
||||||
|
.map_err(|d| d.help(format!("#[{}] can only be used on functions", method_str)))?;
|
||||||
|
|
||||||
|
let full_attr = quote!(#[#method_ident(#args)]);
|
||||||
|
let attrs = Attribute::parse_outer.parse2(full_attr).map_err(syn_to_diag)?;
|
||||||
|
let method_attribute = match MethodRouteAttribute::from_attrs(&method_str, &attrs) {
|
||||||
|
Some(result) => result?,
|
||||||
|
None => return Err(Span::call_site().error("internal error: bad attribute"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let attribute = RouteAttribute {
|
||||||
|
method: Method(method),
|
||||||
|
path: method_attribute.path,
|
||||||
|
data: method_attribute.data,
|
||||||
|
format: method_attribute.format,
|
||||||
|
rank: method_attribute.rank,
|
||||||
|
};
|
||||||
|
|
||||||
|
codegen_route(parse_route(attribute, function)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route_attribute<M: Into<Option<::http::Method>>>(
|
||||||
|
method: M,
|
||||||
|
args: TokenStream,
|
||||||
|
input: TokenStream
|
||||||
|
) -> TokenStream {
|
||||||
|
let result = match method.into() {
|
||||||
|
Some(method) => incomplete_route(method, args.into(), input),
|
||||||
|
None => complete_route(args.into(), input)
|
||||||
|
};
|
||||||
|
|
||||||
|
result.unwrap_or_else(|diag| { diag.emit(); TokenStream::new() })
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use derive_utils::syn;
|
||||||
|
use proc_macro::{Span, Diagnostic};
|
||||||
|
|
||||||
|
use http::route::RouteSegment;
|
||||||
|
use proc_macro_ext::{SpanExt, Diagnostics, PResult, DResult};
|
||||||
|
|
||||||
|
pub use http::route::{Error, Kind, Source};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Segment {
|
||||||
|
pub span: Span,
|
||||||
|
pub kind: Kind,
|
||||||
|
pub source: Source,
|
||||||
|
pub name: String,
|
||||||
|
pub index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Segment {
|
||||||
|
fn from(segment: RouteSegment, span: Span) -> Segment {
|
||||||
|
let (kind, source, index) = (segment.kind, segment.source, segment.index);
|
||||||
|
Segment { span, kind, source, index, name: segment.name.into_owned() }
|
||||||
|
}
|
||||||
|
|
||||||
|
crate fn to_route_segment<'a>(&'a self) -> String {
|
||||||
|
match (self.source, self.kind) {
|
||||||
|
(_, Kind::Single) => format!("<{}>", self.name),
|
||||||
|
(_, Kind::Multi) => format!("<{}..>", self.name),
|
||||||
|
(_, Kind::Static) => self.name.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a syn::Ident> for Segment {
|
||||||
|
fn from(ident: &syn::Ident) -> Segment {
|
||||||
|
Segment {
|
||||||
|
kind: Kind::Static,
|
||||||
|
source: Source::Unknown,
|
||||||
|
span: ident.span().unstable(),
|
||||||
|
name: ident.to_string(),
|
||||||
|
index: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Segment {
|
||||||
|
fn eq(&self, other: &Segment) -> bool {
|
||||||
|
self.name == other.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Segment { }
|
||||||
|
|
||||||
|
impl Hash for Segment {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
||||||
|
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
||||||
|
let remaining = haystack.len() - (index + needle.len());
|
||||||
|
span.trimmed(index, remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trailspan(needle: &str, haystack: &str, span: Span) -> Option<Span> {
|
||||||
|
let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
|
||||||
|
span.trimmed(index - 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_diagnostic(
|
||||||
|
segment: &str, // The segment that failed.
|
||||||
|
source: &str, // The haystack where `segment` can be found.
|
||||||
|
span: Span, // The `Span` of `Source`.
|
||||||
|
error: &Error, // The error.
|
||||||
|
) -> Diagnostic {
|
||||||
|
let seg_span = subspan(segment, source, span).unwrap();
|
||||||
|
match error {
|
||||||
|
Error::Empty => {
|
||||||
|
seg_span.error("parameter names cannot be empty")
|
||||||
|
}
|
||||||
|
Error::Ident(name) => {
|
||||||
|
seg_span.error(format!("`{}` is not a valid identifier", name))
|
||||||
|
.help("parameter names must be valid identifiers")
|
||||||
|
}
|
||||||
|
Error::Ignored => {
|
||||||
|
seg_span.error("parameters must be named")
|
||||||
|
.help("use a name such as `_guard` or `_param`")
|
||||||
|
}
|
||||||
|
Error::MissingClose => {
|
||||||
|
seg_span.error("parameter is missing a closing bracket")
|
||||||
|
.help(format!("did you mean '{}>'?", segment))
|
||||||
|
}
|
||||||
|
Error::Malformed => {
|
||||||
|
seg_span.error("malformed parameter or identifier")
|
||||||
|
.help("parameters must be of the form '<param>'")
|
||||||
|
.help("identifiers cannot contain '<' or '>'")
|
||||||
|
}
|
||||||
|
Error::Uri => {
|
||||||
|
seg_span.error("component contains invalid URI characters")
|
||||||
|
.note("components cannot contain '%' and '+' characters")
|
||||||
|
}
|
||||||
|
Error::Trailing(multi) => {
|
||||||
|
let multi_span = subspan(multi, source, span).unwrap();
|
||||||
|
trailspan(segment, source, span).unwrap()
|
||||||
|
.error("unexpected trailing text after a '..' param")
|
||||||
|
.help("a multi-segment param must be the final component")
|
||||||
|
.span_note(multi_span, "multi-segment param is here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_segment(segment: &str, span: Span) -> PResult<Segment> {
|
||||||
|
RouteSegment::parse_one(segment)
|
||||||
|
.map(|segment| Segment::from(segment, span))
|
||||||
|
.map_err(|e| into_diagnostic(segment, segment, span, &e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_segments(
|
||||||
|
string: &str,
|
||||||
|
sep: char,
|
||||||
|
source: Source,
|
||||||
|
span: Span
|
||||||
|
) -> DResult<Vec<Segment>> {
|
||||||
|
let mut segments = vec![];
|
||||||
|
let mut diags = Diagnostics::new();
|
||||||
|
|
||||||
|
for result in RouteSegment::parse_many(string, sep, source) {
|
||||||
|
if let Err((segment_string, error)) = result {
|
||||||
|
diags.push(into_diagnostic(segment_string, string, span, &error));
|
||||||
|
if let Error::Trailing(..) = error {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if let Ok(segment) = result {
|
||||||
|
let seg_span = subspan(&segment.string, string, span).unwrap();
|
||||||
|
segments.push(Segment::from(segment, seg_span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diags.err_or(segments)
|
||||||
|
}
|
|
@ -4,17 +4,21 @@ use proc_macro2::TokenStream as TokenStream2;
|
||||||
use derive_utils::{syn, Spanned, Result};
|
use derive_utils::{syn, Spanned, Result};
|
||||||
use self::syn::{Path, punctuated::Punctuated, parse::Parser, token::Comma};
|
use self::syn::{Path, punctuated::Punctuated, parse::Parser, token::Comma};
|
||||||
use syn_ext::{IdentExt, syn_to_diag};
|
use syn_ext::{IdentExt, syn_to_diag};
|
||||||
|
use {ROUTE_STRUCT_PREFIX, CATCH_STRUCT_PREFIX};
|
||||||
|
|
||||||
mod uri;
|
mod uri;
|
||||||
mod uri_parsing;
|
mod uri_parsing;
|
||||||
|
|
||||||
|
|
||||||
crate fn prefix_last_segment(path: &mut Path, prefix: &str) {
|
crate fn prefix_last_segment(path: &mut Path, prefix: &str) {
|
||||||
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
|
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
|
||||||
last_seg.value_mut().ident = last_seg.value().ident.prepend(prefix);
|
last_seg.value_mut().ident = last_seg.value().ident.prepend(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _prefixed_vec(prefix: &str, input: TokenStream, ty: &TokenStream2) -> Result<TokenStream2> {
|
fn _prefixed_vec(
|
||||||
|
prefix: &str,
|
||||||
|
input: TokenStream,
|
||||||
|
ty: &TokenStream2
|
||||||
|
) -> Result<TokenStream2> {
|
||||||
// Parse a comma-separated list of paths.
|
// Parse a comma-separated list of paths.
|
||||||
let mut paths = <Punctuated<Path, Comma>>::parse_terminated
|
let mut paths = <Punctuated<Path, Comma>>::parse_terminated
|
||||||
.parse(input)
|
.parse(input)
|
||||||
|
@ -41,14 +45,10 @@ fn prefixed_vec(prefix: &str, input: TokenStream, ty: TokenStream2) -> TokenStre
|
||||||
}).into()
|
}).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
|
|
||||||
|
|
||||||
pub fn routes_macro(input: TokenStream) -> TokenStream {
|
pub fn routes_macro(input: TokenStream) -> TokenStream {
|
||||||
prefixed_vec(ROUTE_STRUCT_PREFIX, input, quote!(::rocket::Route))
|
prefixed_vec(ROUTE_STRUCT_PREFIX, input, quote!(::rocket::Route))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
|
|
||||||
|
|
||||||
pub fn catchers_macro(input: TokenStream) -> TokenStream {
|
pub fn catchers_macro(input: TokenStream) -> TokenStream {
|
||||||
prefixed_vec(CATCH_STRUCT_PREFIX, input, quote!(::rocket::Catcher))
|
prefixed_vec(CATCH_STRUCT_PREFIX, input, quote!(::rocket::Catcher))
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ pub fn derive_from_form(input: TokenStream) -> TokenStream {
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#(#constructors)*
|
#(#constructors)*
|
||||||
|
|
||||||
for (__k, __v) in __items {
|
for (__k, __v) in __items.map(|item| item.key_value()) {
|
||||||
match __k.as_str() {
|
match __k.as_str() {
|
||||||
#(#matchers)*
|
#(#matchers)*
|
||||||
_ if __strict && __k != "_method" => {
|
_ if __strict && __k != "_method" => {
|
||||||
|
|
|
@ -1,16 +1,38 @@
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use derive_utils::{FromMeta, MetaItem, Result, ext::Split2};
|
use derive_utils::{FromMeta, MetaItem, Result, ext::Split2};
|
||||||
use rocket_http as http;
|
use http::{self, ext::IntoOwned};
|
||||||
|
use attribute::segments::{parse_segments, parse_segment, Segment, Kind, Source};
|
||||||
|
|
||||||
|
use proc_macro_ext::SpanExt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContentType(http::ContentType);
|
pub struct ContentType(pub http::ContentType);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Status(pub http::Status);
|
pub struct Status(pub http::Status);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MediaType(http::MediaType);
|
pub struct MediaType(pub http::MediaType);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Method(pub http::Method);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Origin(pub http::uri::Origin<'static>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DataSegment(pub Segment);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Optional<T>(pub Option<T>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RoutePath {
|
||||||
|
pub origin: Origin,
|
||||||
|
pub path: Vec<Segment>,
|
||||||
|
pub query: Option<Vec<Segment>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl FromMeta for Status {
|
impl FromMeta for Status {
|
||||||
fn from_meta(meta: MetaItem) -> Result<Self> {
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
@ -34,7 +56,7 @@ impl FromMeta for ContentType {
|
||||||
fn from_meta(meta: MetaItem) -> Result<Self> {
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
http::ContentType::parse_flexible(&String::from_meta(meta)?)
|
http::ContentType::parse_flexible(&String::from_meta(meta)?)
|
||||||
.map(ContentType)
|
.map(ContentType)
|
||||||
.ok_or(meta.value_span().error("invalid or unknown content-type"))
|
.ok_or(meta.value_span().error("invalid or unknown content type"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +68,14 @@ impl ToTokens for ContentType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromMeta for MediaType {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
http::MediaType::parse_flexible(&String::from_meta(meta)?)
|
||||||
|
.map(MediaType)
|
||||||
|
.ok_or(meta.value_span().error("invalid or unknown media type"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToTokens for MediaType {
|
impl ToTokens for MediaType {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
@ -70,3 +100,141 @@ impl ToTokens for MediaType {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VALID_METHODS_STR: &str = "`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, \
|
||||||
|
`PATCH`, `OPTIONS`";
|
||||||
|
|
||||||
|
const VALID_METHODS: &[http::Method] = &[
|
||||||
|
http::Method::Get, http::Method::Put, http::Method::Post,
|
||||||
|
http::Method::Delete, http::Method::Head, http::Method::Patch,
|
||||||
|
http::Method::Options,
|
||||||
|
];
|
||||||
|
|
||||||
|
impl FromMeta for Method {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
let span = meta.value_span();
|
||||||
|
let help_text = format!("method must be one of: {}", VALID_METHODS_STR);
|
||||||
|
|
||||||
|
if let MetaItem::Ident(ident) = meta {
|
||||||
|
let method = ident.to_string().parse()
|
||||||
|
.map_err(|_| span.error("invalid HTTP method").help(&*help_text))?;
|
||||||
|
|
||||||
|
if !VALID_METHODS.contains(&method) {
|
||||||
|
return Err(span.error("invalid HTTP method for route handlers")
|
||||||
|
.help(&*help_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Method(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(span.error(format!("expected identifier, found {}", meta.description()))
|
||||||
|
.help(&*help_text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Method {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let method_tokens = match self.0 {
|
||||||
|
http::Method::Get => quote!(::rocket::http::Method::Get),
|
||||||
|
http::Method::Put => quote!(::rocket::http::Method::Put),
|
||||||
|
http::Method::Post => quote!(::rocket::http::Method::Post),
|
||||||
|
http::Method::Delete => quote!(::rocket::http::Method::Delete),
|
||||||
|
http::Method::Options => quote!(::rocket::http::Method::Options),
|
||||||
|
http::Method::Head => quote!(::rocket::http::Method::Head),
|
||||||
|
http::Method::Trace => quote!(::rocket::http::Method::Trace),
|
||||||
|
http::Method::Connect => quote!(::rocket::http::Method::Connect),
|
||||||
|
http::Method::Patch => quote!(::rocket::http::Method::Patch),
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.extend(method_tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromMeta for Origin {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
let string = String::from_meta(meta)?;
|
||||||
|
let span = meta.value_span();
|
||||||
|
|
||||||
|
let uri = http::uri::Origin::parse_route(&string)
|
||||||
|
.map_err(|e| {
|
||||||
|
let span = e.index()
|
||||||
|
.map(|i| span.trimmed(i + 1, 0).unwrap())
|
||||||
|
.unwrap_or(span);
|
||||||
|
|
||||||
|
span.error(format!("invalid path URI: {}", e))
|
||||||
|
.help("expected path in origin form: \"/path/<param>\"")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !uri.is_normalized() {
|
||||||
|
let normalized = uri.to_normalized();
|
||||||
|
return Err(span.error("paths cannot contain empty segments")
|
||||||
|
.note(format!("expected '{}', found '{}'", normalized, uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Origin(uri.into_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromMeta for Segment {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
let string = String::from_meta(meta)?;
|
||||||
|
let span = meta.value_span().trimmed(1, 1).unwrap();
|
||||||
|
|
||||||
|
let segment = parse_segment(&string, span)?;
|
||||||
|
if segment.kind != Kind::Single {
|
||||||
|
return Err(span.error("malformed parameter")
|
||||||
|
.help("parameter must be of the form '<param>'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromMeta for DataSegment {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
let mut segment = Segment::from_meta(meta)?;
|
||||||
|
segment.source = Source::Data;
|
||||||
|
segment.index = Some(0);
|
||||||
|
Ok(DataSegment(segment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromMeta for RoutePath {
|
||||||
|
fn from_meta(meta: MetaItem) -> Result<Self> {
|
||||||
|
let origin = Origin::from_meta(meta)?;
|
||||||
|
|
||||||
|
let span = meta.value_span().trimmed(1, 1).unwrap();
|
||||||
|
let query_len = origin.0.query().map(|q| q.len() + 1).unwrap_or(0);
|
||||||
|
let path_span = span.trimmed(0, query_len).unwrap();
|
||||||
|
let path = parse_segments(origin.0.path(), '/', Source::Path, path_span);
|
||||||
|
|
||||||
|
let query = origin.0.query()
|
||||||
|
.map(|q| {
|
||||||
|
let len_to_q = origin.0.path().len() + 1;
|
||||||
|
let query_span = span.trimmed(len_to_q, 0).unwrap();
|
||||||
|
if q.starts_with('&') || q.contains("&&") || q.ends_with('&') {
|
||||||
|
// TODO: Show a help message with what's expected.
|
||||||
|
Err(query_span.error("query cannot contain empty components").into())
|
||||||
|
} else {
|
||||||
|
parse_segments(q, '&', Source::Query, query_span)
|
||||||
|
}
|
||||||
|
}).transpose();
|
||||||
|
|
||||||
|
match (path, query) {
|
||||||
|
(Ok(path), Ok(query)) => Ok(RoutePath { origin, path, query }),
|
||||||
|
(Err(diag), Ok(_)) | (Ok(_), Err(diag)) => Err(diag.emit_head()),
|
||||||
|
(Err(d1), Err(d2)) => Err(d1.join(d2).emit_head())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToTokens> ToTokens for Optional<T> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let opt_tokens = match self.0 {
|
||||||
|
Some(ref val) => quote!(Some(#val)),
|
||||||
|
None => quote!(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.extend(opt_tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +1,52 @@
|
||||||
#![feature(proc_macro_diagnostic, proc_macro_span)]
|
#![feature(proc_macro_diagnostic, proc_macro_span)]
|
||||||
#![feature(crate_visibility_modifier)]
|
#![feature(crate_visibility_modifier)]
|
||||||
|
#![feature(transpose_result)]
|
||||||
|
#![feature(rustc_private)]
|
||||||
#![recursion_limit="128"]
|
#![recursion_limit="128"]
|
||||||
|
|
||||||
#[macro_use] extern crate quote;
|
#[macro_use] extern crate quote;
|
||||||
#[macro_use] extern crate derive_utils;
|
#[macro_use] extern crate derive_utils;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
extern crate rocket_http;
|
extern crate rocket_http as http;
|
||||||
|
extern crate indexmap;
|
||||||
|
|
||||||
|
extern crate syntax_pos;
|
||||||
|
|
||||||
|
#[macro_use] mod proc_macro_ext;
|
||||||
mod derive;
|
mod derive;
|
||||||
mod attribute;
|
mod attribute;
|
||||||
mod bang;
|
mod bang;
|
||||||
mod http_codegen;
|
mod http_codegen;
|
||||||
mod syn_ext;
|
mod syn_ext;
|
||||||
|
|
||||||
|
use http::Method;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
crate use derive_utils::proc_macro2;
|
crate use derive_utils::proc_macro2;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
crate static ROUTE_STRUCT_PREFIX: &str = "static_rocket_route_info_for_";
|
||||||
|
crate static CATCH_STRUCT_PREFIX: &str = "static_rocket_catch_info_for_";
|
||||||
|
crate static CATCH_FN_PREFIX: &str = "rocket_catch_fn_";
|
||||||
|
crate static ROUTE_FN_PREFIX: &str = "rocket_route_fn_";
|
||||||
|
crate static URI_MACRO_PREFIX: &str = "rocket_uri_macro_";
|
||||||
|
crate static ROCKET_PARAM_PREFIX: &str = "__rocket_param_";
|
||||||
|
|
||||||
|
macro_rules! route_attribute {
|
||||||
|
($name:ident => $method:expr) => (
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn $name(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
attribute::route::route_attribute($method, args, input)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
route_attribute!(route => None);
|
||||||
|
route_attribute!(get => Method::Get);
|
||||||
|
route_attribute!(put => Method::Put);
|
||||||
|
route_attribute!(post => Method::Post);
|
||||||
|
route_attribute!(delete => Method::Delete);
|
||||||
|
route_attribute!(head => Method::Head);
|
||||||
|
route_attribute!(patch => Method::Patch);
|
||||||
|
route_attribute!(options => Method::Options);
|
||||||
|
|
||||||
#[proc_macro_derive(FromFormValue, attributes(form))]
|
#[proc_macro_derive(FromFormValue, attributes(form))]
|
||||||
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
|
pub fn derive_from_form_value(input: TokenStream) -> TokenStream {
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
use proc_macro::{Span, Diagnostic, /* MultiSpan */};
|
||||||
|
use syntax_pos::{Span as InnerSpan, Pos, BytePos};
|
||||||
|
|
||||||
|
pub type PResult<T> = ::std::result::Result<T, Diagnostic>;
|
||||||
|
|
||||||
|
pub type DResult<T> = ::std::result::Result<T, Diagnostics>;
|
||||||
|
|
||||||
|
// An experiment.
|
||||||
|
pub struct Diagnostics(Vec<Diagnostic>);
|
||||||
|
|
||||||
|
impl Diagnostics {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Diagnostics(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, diag: Diagnostic) {
|
||||||
|
self.0.push(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(mut self, mut diags: Diagnostics) -> Self {
|
||||||
|
self.0.append(&mut diags.0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_head(self) -> Diagnostic {
|
||||||
|
let mut iter = self.0.into_iter();
|
||||||
|
let mut last = iter.next().expect("Diagnostic::emit_head empty");
|
||||||
|
for diag in iter {
|
||||||
|
last.emit();
|
||||||
|
last = diag;
|
||||||
|
}
|
||||||
|
|
||||||
|
last
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn head_err_or<T>(self, ok: T) -> PResult<T> {
|
||||||
|
match self.0.is_empty() {
|
||||||
|
true => Ok(ok),
|
||||||
|
false => Err(self.emit_head())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err_or<T>(self, ok: T) -> DResult<T> {
|
||||||
|
match self.0.is_empty() {
|
||||||
|
true => Ok(ok),
|
||||||
|
false => Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Diagnostic> for Diagnostics {
|
||||||
|
fn from(diag: Diagnostic) -> Self {
|
||||||
|
Diagnostics(vec![diag])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<Diagnostic>> for Diagnostics {
|
||||||
|
fn from(diags: Vec<Diagnostic>) -> Self {
|
||||||
|
Diagnostics(diags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SpanExt {
|
||||||
|
fn trimmed(&self, left: usize, right: usize) -> Option<Span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpanExt for Span {
|
||||||
|
/// Trim the span on the left by `left` characters and on the right by
|
||||||
|
/// `right` characters.
|
||||||
|
fn trimmed(&self, left: usize, right: usize) -> Option<Span> {
|
||||||
|
let inner: InnerSpan = unsafe { ::std::mem::transmute(*self) };
|
||||||
|
if left > u32::max_value() as usize || right > u32::max_value() as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the addition won't overflow.
|
||||||
|
let (left, right) = (left as u32, right as u32);
|
||||||
|
if u32::max_value() - left < inner.lo().to_u32() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the subtraction won't underflow.
|
||||||
|
if right > inner.hi().to_u32() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_lo = inner.lo() + BytePos(left);
|
||||||
|
let new_hi = inner.hi() - BytePos(right);
|
||||||
|
|
||||||
|
// Ensure we're still inside the old `Span` and didn't cross paths.
|
||||||
|
if new_lo >= new_hi {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_inner = inner.with_lo(new_lo).with_hi(new_hi);
|
||||||
|
Some(unsafe { ::std::mem::transmute(new_inner) })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
// #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
|
|
||||||
|
// #[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
// #[get("/", rank = 1)]
|
||||||
|
// fn get1() -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/", rank = 2)]
|
||||||
|
// fn get2() -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/", rank = 3)]
|
||||||
|
// fn get3() -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/")] fn get() { }
|
||||||
|
// #[route(GET, "/")] fn get_r() { }
|
||||||
|
|
||||||
|
// #[put("/")] fn put() { }
|
||||||
|
// #[route(PUT, "/")] fn put_r() { }
|
||||||
|
|
||||||
|
// #[post("/")] fn post() { }
|
||||||
|
// #[route(POST, "/")] fn post_r() { }
|
||||||
|
|
||||||
|
// #[delete("/")] fn delete() { }
|
||||||
|
// #[route(DELETE, "/")] fn delete_r() { }
|
||||||
|
|
||||||
|
// #[head("/")] fn head() { }
|
||||||
|
// #[route(HEAD, "/")] fn head_r() { }
|
||||||
|
|
||||||
|
// #[patch("/")] fn patch() { }
|
||||||
|
// #[route(PATCH, "/")] fn patch_r() { }
|
||||||
|
|
||||||
|
// #[options("/")] fn options() { }
|
||||||
|
// #[route(OPTIONS, "/")] fn options_r() { }
|
||||||
|
|
||||||
|
// use rocket::http::{Cookies, RawStr};
|
||||||
|
// use rocket::request::Form;
|
||||||
|
|
||||||
|
// #[derive(FromForm)]
|
||||||
|
// struct User<'a> {
|
||||||
|
// name: &'a RawStr,
|
||||||
|
// nickname: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
|
||||||
|
// fn get(
|
||||||
|
// _name: &RawStr,
|
||||||
|
// _query: User,
|
||||||
|
// user: Form<User>,
|
||||||
|
// _cookies: Cookies
|
||||||
|
// ) -> String {
|
||||||
|
// format!("{}:{}", user.name, user.nickname)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[post("/", format = "application/x-custom")]
|
||||||
|
// fn get() -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/test/<one>/<two>/<three>")]
|
||||||
|
// fn get(one: String, two: usize, three: isize) -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/test/<_one>/<_two>/<__three>")]
|
||||||
|
// fn ignored(_one: String, _two: usize, __three: isize) -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/")]
|
||||||
|
// fn get() -> &'static str { "hi" }
|
||||||
|
|
||||||
|
// #[get("/")]
|
||||||
|
// fn get_empty() { }
|
||||||
|
|
||||||
|
// #[get("/one")]
|
||||||
|
// fn one() { }
|
||||||
|
|
||||||
|
// #[get("/two")]
|
||||||
|
// fn two() { }
|
||||||
|
|
||||||
|
// #[get("/three")]
|
||||||
|
// fn three() { }
|
||||||
|
|
||||||
|
// #[get("/four")]
|
||||||
|
// fn four() { }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn main() {
|
||||||
|
// let instance = rocket::ignite()
|
||||||
|
// .mount("/", routes![one]);
|
||||||
|
|
||||||
|
// let other = instance.mount("/", routes![two]);
|
||||||
|
// other.mount("/", routes![three])
|
||||||
|
// .mount("/", routes![four]);
|
||||||
|
|
||||||
|
// rocket::ignite()
|
||||||
|
// .mount("/", routes![one])
|
||||||
|
// .mount("/", routes![two])
|
||||||
|
// .mount("/", routes![three])
|
||||||
|
// .mount("/", routes![four]);
|
||||||
|
|
||||||
|
// let a = rocket::ignite()
|
||||||
|
// .mount("/", routes![one])
|
||||||
|
// .mount("/", routes![two]);
|
||||||
|
|
||||||
|
// let b = a.mount("/", routes![three])
|
||||||
|
// .mount("/", routes![four]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[get("/<todo>")]
|
||||||
|
// fn todo(todo: String) -> String {
|
||||||
|
// todo
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[post("/<a>/<b..>")]
|
||||||
|
// fn get(a: String, b: PathBuf) -> String {
|
||||||
|
// format!("{}/{}", a, b.to_string_lossy())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[post("/<a>/<b..>")]
|
||||||
|
// fn get2(a: String, b: Result<PathBuf, SegmentError>) -> String {
|
||||||
|
// format!("{}/{}", a, b.unwrap().to_string_lossy())
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn main() {
|
||||||
|
// let _ = routes![todo];
|
||||||
|
// }
|
|
@ -0,0 +1,58 @@
|
||||||
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use rocket::{Request, Data, Outcome::*};
|
||||||
|
use rocket::local::Client;
|
||||||
|
use rocket::request::Form;
|
||||||
|
use rocket::data::{self, FromDataSimple};
|
||||||
|
use rocket::http::{RawStr, ContentType, Status};
|
||||||
|
|
||||||
|
// Test that the data parameters works as expected.
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Inner<'r> {
|
||||||
|
field: &'r RawStr
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Simple(String);
|
||||||
|
|
||||||
|
impl FromDataSimple for Simple {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_data(_: &Request, data: Data) -> data::Outcome<Self, ()> {
|
||||||
|
let mut string = String::new();
|
||||||
|
if let Err(_) = data.open().take(64).read_to_string(&mut string) {
|
||||||
|
return Failure((Status::InternalServerError, ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Success(Simple(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/f", data = "<form>")]
|
||||||
|
fn form(form: Form<Inner>) -> String { form.field.url_decode_lossy() }
|
||||||
|
|
||||||
|
#[post("/s", data = "<simple>")]
|
||||||
|
fn simple(simple: Simple) -> String { simple.0 }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_data() {
|
||||||
|
let rocket = rocket::ignite().mount("/", routes![form, simple]);
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let mut response = client.post("/f")
|
||||||
|
.header(ContentType::Form)
|
||||||
|
.body("field=this%20is%20here")
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.body_string().unwrap(), "this is here");
|
||||||
|
|
||||||
|
let mut response = client.post("/s").body("this is here").dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "this is here");
|
||||||
|
|
||||||
|
let mut response = client.post("/s").body("this%20is%20here").dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "this%20is%20here");
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::local::Client;
|
||||||
|
use rocket::http::{ContentType, MediaType, Accept, Status};
|
||||||
|
|
||||||
|
// Test that known formats work as expected, including not colliding.
|
||||||
|
|
||||||
|
#[post("/", format = "json")]
|
||||||
|
fn json() -> &'static str { "json" }
|
||||||
|
|
||||||
|
#[post("/", format = "xml")]
|
||||||
|
fn xml() -> &'static str { "xml" }
|
||||||
|
|
||||||
|
// Unreachable. Written for codegen.
|
||||||
|
#[post("/", format = "application/json", rank = 2)]
|
||||||
|
fn json_long() -> &'static str { "json_long" }
|
||||||
|
|
||||||
|
#[post("/", format = "application/msgpack")]
|
||||||
|
fn msgpack_long() -> &'static str { "msgpack_long" }
|
||||||
|
|
||||||
|
// Unreachable. Written for codegen.
|
||||||
|
#[post("/", format = "msgpack", rank = 2)]
|
||||||
|
fn msgpack() -> &'static str { "msgpack" }
|
||||||
|
|
||||||
|
#[get("/", format = "plain")]
|
||||||
|
fn plain() -> &'static str { "plain" }
|
||||||
|
|
||||||
|
#[get("/", format = "binary")]
|
||||||
|
fn binary() -> &'static str { "binary" }
|
||||||
|
|
||||||
|
#[get("/", rank = 2)]
|
||||||
|
fn other() -> &'static str { "other" }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_formats() {
|
||||||
|
let rocket = rocket::ignite()
|
||||||
|
.mount("/", routes![json, xml, json_long, msgpack_long, msgpack,
|
||||||
|
plain, binary, other]);
|
||||||
|
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let mut response = client.post("/").header(ContentType::JSON).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "json");
|
||||||
|
|
||||||
|
let mut response = client.post("/").header(ContentType::MsgPack).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "msgpack_long");
|
||||||
|
|
||||||
|
let mut response = client.post("/").header(ContentType::XML).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "xml");
|
||||||
|
|
||||||
|
let mut response = client.get("/").header(Accept::Plain).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "plain");
|
||||||
|
|
||||||
|
let mut response = client.get("/").header(Accept::Binary).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "binary");
|
||||||
|
|
||||||
|
let mut response = client.get("/").header(ContentType::JSON).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "other");
|
||||||
|
|
||||||
|
let mut response = client.get("/").dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "other");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test custom formats.
|
||||||
|
|
||||||
|
#[get("/", format = "application/foo")]
|
||||||
|
fn get_foo() -> &'static str { "get_foo" }
|
||||||
|
|
||||||
|
#[post("/", format = "application/foo")]
|
||||||
|
fn post_foo() -> &'static str { "post_foo" }
|
||||||
|
|
||||||
|
#[get("/", format = "bar/baz")]
|
||||||
|
fn get_bar_baz() -> &'static str { "get_bar_baz" }
|
||||||
|
|
||||||
|
#[put("/", format = "bar/baz")]
|
||||||
|
fn put_bar_baz() -> &'static str { "put_bar_baz" }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_formats() {
|
||||||
|
let rocket = rocket::ignite()
|
||||||
|
.mount("/", routes![get_foo, post_foo, get_bar_baz, put_bar_baz]);
|
||||||
|
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let foo_a = Accept::new(&[MediaType::new("application", "foo").into()]);
|
||||||
|
let foo_ct = ContentType::new("application", "foo");
|
||||||
|
let bar_baz_ct = ContentType::new("bar", "baz");
|
||||||
|
let bar_baz_a = Accept::new(&[MediaType::new("bar", "baz").into()]);
|
||||||
|
|
||||||
|
let mut response = client.get("/").header(foo_a).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "get_foo");
|
||||||
|
|
||||||
|
let mut response = client.post("/").header(foo_ct).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "post_foo");
|
||||||
|
|
||||||
|
let mut response = client.get("/").header(bar_baz_a).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "get_bar_baz");
|
||||||
|
|
||||||
|
let mut response = client.put("/").header(bar_baz_ct).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "put_bar_baz");
|
||||||
|
|
||||||
|
let response = client.get("/").dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// #[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
|
||||||
|
// fn get(
|
||||||
|
// _name: &RawStr,
|
||||||
|
// _query: User,
|
||||||
|
// user: Form<User>,
|
||||||
|
// _cookies: Cookies
|
||||||
|
// ) -> String {
|
||||||
|
// format!("{}:{}", user.name, user.nickname)
|
||||||
|
// }
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
use rocket::local::Client;
|
||||||
|
|
||||||
|
// Test that manual/auto ranking works as expected.
|
||||||
|
|
||||||
|
#[get("/<_number>")]
|
||||||
|
fn get0(_number: u8) -> &'static str { "0" }
|
||||||
|
|
||||||
|
#[get("/<_number>", rank = 1)]
|
||||||
|
fn get1(_number: u16) -> &'static str { "1" }
|
||||||
|
|
||||||
|
#[get("/<_number>", rank = 2)]
|
||||||
|
fn get2(_number: u32) -> &'static str { "2" }
|
||||||
|
|
||||||
|
#[get("/<_number>", rank = 3)]
|
||||||
|
fn get3(_number: u64) -> &'static str { "3" }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ranking() {
|
||||||
|
let rocket = rocket::ignite().mount("/", routes![get0, get1, get2, get3]);
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let mut response = client.get("/0").dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "0");
|
||||||
|
|
||||||
|
let mut response = client.get(format!("/{}", 1 << 8)).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "1");
|
||||||
|
|
||||||
|
let mut response = client.get(format!("/{}", 1 << 16)).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "2");
|
||||||
|
|
||||||
|
let mut response = client.get(format!("/{}", 1u64 << 32)).dispatch();
|
||||||
|
assert_eq!(response.body_string().unwrap(), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a collision due to same auto rank.
|
||||||
|
|
||||||
|
#[get("/<_n>")]
|
||||||
|
fn get0b(_n: u8) { }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rank_collision() {
|
||||||
|
use rocket::error::LaunchErrorKind;
|
||||||
|
|
||||||
|
let rocket = rocket::ignite().mount("/", routes![get0, get0b]);
|
||||||
|
let client_result = Client::new(rocket);
|
||||||
|
match client_result.as_ref().map_err(|e| e.kind()) {
|
||||||
|
Err(LaunchErrorKind::Collision(..)) => { /* o.k. */ },
|
||||||
|
Ok(_) => panic!("client succeeded unexpectedly"),
|
||||||
|
Err(e) => panic!("expected collision, got {}", e)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
#![feature(plugin, proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use rocket::{Request, Outcome::*};
|
||||||
|
use rocket::local::Client;
|
||||||
|
use rocket::data::{self, Data, FromDataSimple};
|
||||||
|
use rocket::request::Form;
|
||||||
|
use rocket::http::{Status, RawStr, ContentType, uri::UriDisplay};
|
||||||
|
|
||||||
|
// Use all of the code generation avaiable at once.
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
struct Inner<'r> {
|
||||||
|
field: &'r RawStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this deriveable.
|
||||||
|
impl<'a> UriDisplay for Inner<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "field={}", self.field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Simple(String);
|
||||||
|
|
||||||
|
impl FromDataSimple for Simple {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_data(_: &Request, data: Data) -> data::Outcome<Self, ()> {
|
||||||
|
use std::io::Read;
|
||||||
|
let mut string = String::new();
|
||||||
|
data.open().take(64).read_to_string(&mut string).unwrap();
|
||||||
|
Success(Simple(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>", format = "json", data = "<simple>", rank = 138)]
|
||||||
|
fn post1(
|
||||||
|
sky: usize,
|
||||||
|
name: &RawStr,
|
||||||
|
a: String,
|
||||||
|
query: Form<Inner>,
|
||||||
|
path: PathBuf,
|
||||||
|
simple: Simple,
|
||||||
|
) -> String {
|
||||||
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||||
|
sky, name, a, query.field, path.display(), simple.0);
|
||||||
|
|
||||||
|
let uri = uri!(post2: a, name.url_decode_lossy(), path, sky, query.into_inner());
|
||||||
|
|
||||||
|
format!("({}) ({})", string, uri.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(POST, path = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>", format = "json", data = "<simple>", rank = 138)]
|
||||||
|
fn post2(
|
||||||
|
sky: usize,
|
||||||
|
name: &RawStr,
|
||||||
|
a: String,
|
||||||
|
query: Form<Inner>,
|
||||||
|
path: PathBuf,
|
||||||
|
simple: Simple,
|
||||||
|
) -> String {
|
||||||
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||||
|
sky, name, a, query.field, path.display(), simple.0);
|
||||||
|
|
||||||
|
let uri = uri!(post2: a, name.url_decode_lossy(), path, sky, query.into_inner());
|
||||||
|
|
||||||
|
format!("({}) ({})", string, uri.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_full_route() {
|
||||||
|
let rocket = rocket::ignite()
|
||||||
|
.mount("/1", routes![post1])
|
||||||
|
.mount("/2", routes![post2]);
|
||||||
|
|
||||||
|
let client = Client::new(rocket).unwrap();
|
||||||
|
|
||||||
|
let a = "A%20A";
|
||||||
|
let name = "Bob%20McDonald";
|
||||||
|
let path = "this/path/here";
|
||||||
|
let sky = 777;
|
||||||
|
let query = "field=inside";
|
||||||
|
let simple = "data internals";
|
||||||
|
|
||||||
|
let path_part = format!("/{}/{}/name/{}", a, name, path);
|
||||||
|
let query_part = format!("?sky={}&sky=blue&{}", sky, query);
|
||||||
|
let uri = format!("{}{}", path_part, query_part);
|
||||||
|
let expected_uri = format!("{}?sky=blue&sky={}&{}", path_part, sky, query);
|
||||||
|
|
||||||
|
let response = client.post(&uri).body(simple).dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
|
let response = client.post(format!("/1{}", uri)).body(simple).dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.post(format!("/1{}", uri))
|
||||||
|
.header(ContentType::JSON)
|
||||||
|
.body(simple)
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
|
||||||
|
sky, name, "A A", "inside", path, simple, expected_uri));
|
||||||
|
|
||||||
|
let response = client.post(format!("/2{}", uri)).body(simple).dispatch();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.post(format!("/2{}", uri))
|
||||||
|
.header(ContentType::JSON)
|
||||||
|
.body(simple)
|
||||||
|
.dispatch();
|
||||||
|
|
||||||
|
assert_eq!(response.body_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
|
||||||
|
sky, name, "A A", "inside", path, simple, expected_uri));
|
||||||
|
}
|
|
@ -16,11 +16,11 @@ const CATCH: &str = "Catcher";
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e1(_request: &Request) { }
|
fn e1(_request: &Request) { }
|
||||||
|
|
||||||
#[catch(code = "404")] //~ ERROR unexpected named parameter
|
#[catch(code = "404")] //~ ERROR unexpected parameter
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e2(_request: &Request) { }
|
fn e2(_request: &Request) { }
|
||||||
|
|
||||||
#[catch(code = 404)] //~ ERROR unexpected named parameter
|
#[catch(code = 404)] //~ ERROR unexpected parameter
|
||||||
//~^ HELP #[catch(404)]
|
//~^ HELP #[catch(404)]
|
||||||
fn e3(_request: &Request) { }
|
fn e3(_request: &Request) { }
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ error: invalid value: expected unsigned integer literal
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
||||||
|
|
||||||
error: unexpected named parameter: expected bare literal
|
error: unexpected parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:19:9
|
--> $DIR/catch.rs:19:9
|
||||||
|
|
|
|
||||||
19 | #[catch(code = "404")] //~ ERROR unexpected named parameter
|
19 | #[catch(code = "404")] //~ ERROR unexpected named parameter
|
||||||
|
@ -30,7 +30,7 @@ error: unexpected named parameter: expected bare literal
|
||||||
|
|
|
|
||||||
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
= help: `#[catch]` expects a single status integer, e.g.: #[catch(404)]
|
||||||
|
|
||||||
error: unexpected named parameter: expected bare literal
|
error: unexpected parameter: expected literal or identifier
|
||||||
--> $DIR/catch.rs:23:9
|
--> $DIR/catch.rs:23:9
|
||||||
|
|
|
|
||||||
23 | #[catch(code = 404)] //~ ERROR unexpected named parameter
|
23 | #[catch(code = 404)] //~ ERROR unexpected named parameter
|
||||||
|
|
|
@ -26,6 +26,7 @@ rustls = { version = "0.13", optional = true }
|
||||||
state = "0.4"
|
state = "0.4"
|
||||||
cookie = { version = "0.11", features = ["percent-encode", "secure"] }
|
cookie = { version = "0.11", features = ["percent-encode", "secure"] }
|
||||||
pear = { git = "http://github.com/SergioBenitez/Pear", rev = "b475140" }
|
pear = { git = "http://github.com/SergioBenitez/Pear", rev = "b475140" }
|
||||||
|
unicode-xid = "0.1"
|
||||||
|
|
||||||
[dependencies.hyper-sync-rustls]
|
[dependencies.hyper-sync-rustls]
|
||||||
version = "=0.3.0-rc.3"
|
version = "=0.3.0-rc.3"
|
||||||
|
|
|
@ -35,9 +35,8 @@ use Header;
|
||||||
/// a handler to retrieve the value of a "message" cookie.
|
/// a handler to retrieve the value of a "message" cookie.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::Cookies;
|
/// use rocket::http::Cookies;
|
||||||
///
|
///
|
||||||
/// #[get("/message")]
|
/// #[get("/message")]
|
||||||
|
@ -56,9 +55,8 @@ use Header;
|
||||||
/// [private cookie]: /rocket/http/enum.Cookies.html#private-cookies
|
/// [private cookie]: /rocket/http/enum.Cookies.html#private-cookies
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, never_type)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro, never_type)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #
|
/// #
|
||||||
/// use rocket::http::Status;
|
/// use rocket::http::Status;
|
||||||
/// use rocket::outcome::IntoOutcome;
|
/// use rocket::outcome::IntoOutcome;
|
||||||
|
|
|
@ -21,6 +21,7 @@ extern crate cookie;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
extern crate state;
|
extern crate state;
|
||||||
|
extern crate unicode_xid;
|
||||||
|
|
||||||
pub mod hyper;
|
pub mod hyper;
|
||||||
pub mod uri;
|
pub mod uri;
|
||||||
|
@ -30,6 +31,9 @@ pub mod ext;
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod route;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod docify;
|
mod docify;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -50,18 +54,19 @@ pub mod uncased;
|
||||||
// We need to export these for codegen, but otherwise it's unnecessary.
|
// We need to export these for codegen, but otherwise it's unnecessary.
|
||||||
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
||||||
// FIXME(rustc): These show up in the rexported module.
|
// FIXME(rustc): These show up in the rexported module.
|
||||||
#[doc(hidden)] pub use self::parse::Indexed;
|
#[doc(hidden)] pub use parse::Indexed;
|
||||||
#[doc(hidden)] pub use self::media_type::{MediaParams, Source};
|
#[doc(hidden)] pub use media_type::{MediaParams, Source};
|
||||||
|
#[doc(hidden)] pub use smallvec::{SmallVec, Array};
|
||||||
|
|
||||||
// This one we need to expose for core.
|
// This one we need to expose for core.
|
||||||
#[doc(hidden)] pub use self::cookies::{Key, CookieJar};
|
#[doc(hidden)] pub use cookies::{Key, CookieJar};
|
||||||
|
|
||||||
pub use self::method::Method;
|
pub use method::Method;
|
||||||
pub use self::content_type::ContentType;
|
pub use content_type::ContentType;
|
||||||
pub use self::accept::{Accept, QMediaType};
|
pub use accept::{Accept, QMediaType};
|
||||||
pub use self::status::{Status, StatusClass};
|
pub use status::{Status, StatusClass};
|
||||||
pub use self::header::{Header, HeaderMap};
|
pub use header::{Header, HeaderMap};
|
||||||
pub use self::raw_str::RawStr;
|
pub use raw_str::RawStr;
|
||||||
|
|
||||||
pub use self::media_type::MediaType;
|
pub use media_type::MediaType;
|
||||||
pub use self::cookies::{Cookie, SameSite, Cookies};
|
pub use cookies::{Cookie, SameSite, Cookies};
|
||||||
|
|
|
@ -45,6 +45,16 @@ impl<'a, T: ?Sized + ToOwned + 'a, C: Into<Cow<'a, T>>> From<C> for Indexed<'a,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
||||||
|
/// Panics if `self` is not an `Indexed`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn indices(self) -> (usize, usize) {
|
||||||
|
match self {
|
||||||
|
Indexed::Indexed(a, b) => (a, b),
|
||||||
|
_ => panic!("cannot convert indexed T to U unless indexed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Panics if `self` is not an `Indexed`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn coerce<U: ?Sized + ToOwned>(self) -> Indexed<'a, U> {
|
pub fn coerce<U: ?Sized + ToOwned>(self) -> Indexed<'a, U> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -53,6 +63,7 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Panics if `self` is not an `Indexed`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn coerce_lifetime<'b>(self) -> Indexed<'b, T> {
|
pub fn coerce_lifetime<'b>(self) -> Indexed<'b, T> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -39,6 +39,22 @@ impl<'a> Error<'a> {
|
||||||
|
|
||||||
Error { expected: new_expected, context: pear_error.context }
|
Error { expected: new_expected, context: pear_error.context }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the byte index into the text where the error occurred if it is
|
||||||
|
/// known.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let err = Origin::parse("/foo bar").unwrap_err();
|
||||||
|
/// assert_eq!(err.index(), Some(4));
|
||||||
|
/// ```
|
||||||
|
pub fn index(&self) -> Option<usize> {
|
||||||
|
self.context.as_ref().map(|c| c.offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Or<char, u8> {
|
impl fmt::Display for Or<char, u8> {
|
||||||
|
|
|
@ -44,7 +44,7 @@ use uncased::UncasedStr;
|
||||||
///
|
///
|
||||||
/// [`FromParam`]: /rocket/request/trait.FromParam.html
|
/// [`FromParam`]: /rocket/request/trait.FromParam.html
|
||||||
/// [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
/// [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
|
||||||
#[repr(C)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct RawStr(str);
|
pub struct RawStr(str);
|
||||||
|
|
||||||
|
@ -154,12 +154,51 @@ impl RawStr {
|
||||||
/// assert_eq!(decoded, Ok("Hello, world!".to_string()));
|
/// assert_eq!(decoded, Ok("Hello, world!".to_string()));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn url_decode(&self) -> Result<String, Utf8Error> {
|
pub fn url_decode(&self) -> Result<String, Utf8Error> {
|
||||||
|
// TODO: Make this more efficient!
|
||||||
let replaced = self.replace("+", " ");
|
let replaced = self.replace("+", " ");
|
||||||
RawStr::from_str(replaced.as_str())
|
RawStr::from_str(replaced.as_str())
|
||||||
.percent_decode()
|
.percent_decode()
|
||||||
.map(|cow| cow.into_owned())
|
.map(|cow| cow.into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a URL-decoded version of the string.
|
||||||
|
///
|
||||||
|
/// Any invalid UTF-8 percent-encoded byte sequences will be replaced <20>
|
||||||
|
/// U+FFFD, the replacement character. This is identical to lossy percent
|
||||||
|
/// decoding except that `+` characters are converted into spaces. This is
|
||||||
|
/// the encoding used by form values.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// With a valid string:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::RawStr;
|
||||||
|
///
|
||||||
|
/// let raw_str: &RawStr = "Hello%2C+world%21".into();
|
||||||
|
/// let decoded = raw_str.url_decode_lossy();
|
||||||
|
/// assert_eq!(decoded, "Hello, world!");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// With an invalid string:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::RawStr;
|
||||||
|
///
|
||||||
|
/// // Note: Rocket should never hand you a bad `&RawStr`.
|
||||||
|
/// let bad_str = unsafe { ::std::str::from_utf8_unchecked(b"a+b=\xff") };
|
||||||
|
/// let bad_raw_str = RawStr::from_str(bad_str);
|
||||||
|
/// assert_eq!(bad_raw_str.url_decode_lossy(), "a b=<3D>");
|
||||||
|
/// ```
|
||||||
|
pub fn url_decode_lossy(&self) -> String {
|
||||||
|
let replaced = self.replace("+", " ");
|
||||||
|
RawStr::from_str(replaced.as_str())
|
||||||
|
.percent_decode_lossy()
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an HTML escaped version of `self`. Allocates only when
|
/// Returns an HTML escaped version of `self`. Allocates only when
|
||||||
/// characters need to be escaped.
|
/// characters need to be escaped.
|
||||||
///
|
///
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use unicode_xid::UnicodeXID;
|
||||||
|
|
||||||
|
use ext::IntoOwned;
|
||||||
|
use uri::{Uri, Origin};
|
||||||
|
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Kind {
|
||||||
|
Static,
|
||||||
|
Single,
|
||||||
|
Multi,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Source {
|
||||||
|
Path,
|
||||||
|
Query,
|
||||||
|
Data,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RouteSegment<'a> {
|
||||||
|
pub string: Cow<'a, str>,
|
||||||
|
pub kind: Kind,
|
||||||
|
pub source: Source,
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoOwned for RouteSegment<'a> {
|
||||||
|
type Owned = RouteSegment<'static>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
RouteSegment {
|
||||||
|
string: IntoOwned::into_owned(self.string),
|
||||||
|
kind: self.kind,
|
||||||
|
source: self.source,
|
||||||
|
name: IntoOwned::into_owned(self.name),
|
||||||
|
index: self.index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Error<'a> {
|
||||||
|
Empty,
|
||||||
|
Ident(&'a str),
|
||||||
|
Ignored,
|
||||||
|
MissingClose,
|
||||||
|
Malformed,
|
||||||
|
Uri,
|
||||||
|
Trailing(&'a str)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SResult<'a> = Result<RouteSegment<'a>, (&'a str, Error<'a>)>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_ident_start(c: char) -> bool {
|
||||||
|
('a' <= c && c <= 'z')
|
||||||
|
|| ('A' <= c && c <= 'Z')
|
||||||
|
|| c == '_'
|
||||||
|
|| (c > '\x7f' && UnicodeXID::is_xid_start(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_ident_continue(c: char) -> bool {
|
||||||
|
('a' <= c && c <= 'z')
|
||||||
|
|| ('A' <= c && c <= 'Z')
|
||||||
|
|| c == '_'
|
||||||
|
|| ('0' <= c && c <= '9')
|
||||||
|
|| (c > '\x7f' && UnicodeXID::is_xid_continue(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_ident(string: &str) -> bool {
|
||||||
|
let mut chars = string.chars();
|
||||||
|
match chars.next() {
|
||||||
|
Some(c) => is_ident_start(c) && chars.all(is_ident_continue),
|
||||||
|
None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RouteSegment<'a> {
|
||||||
|
pub fn parse_one(segment: &str) -> Result<RouteSegment, Error> {
|
||||||
|
let (string, source, index) = (segment.into(), Source::Unknown, None);
|
||||||
|
|
||||||
|
// Check if this is a dynamic param. If so, check its well-formedness.
|
||||||
|
if segment.starts_with('<') && segment.ends_with('>') {
|
||||||
|
let mut kind = Kind::Single;
|
||||||
|
let mut name = &segment[1..(segment.len() - 1)];
|
||||||
|
if name.ends_with("..") {
|
||||||
|
kind = Kind::Multi;
|
||||||
|
name = &name[..(name.len() - 2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.is_empty() {
|
||||||
|
return Err(Empty);
|
||||||
|
} else if !is_valid_ident(name) {
|
||||||
|
return Err(Ident(name));
|
||||||
|
} else if name == "_" {
|
||||||
|
return Err(Ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = name.into();
|
||||||
|
return Ok(RouteSegment { string, source, name, kind, index });
|
||||||
|
} else if segment.is_empty() {
|
||||||
|
return Err(Empty);
|
||||||
|
} else if segment.starts_with('<') && segment.len() > 1 {
|
||||||
|
return Err(MissingClose);
|
||||||
|
} else if segment.contains('>') || segment.contains('<') {
|
||||||
|
return Err(Malformed);
|
||||||
|
} else if Uri::percent_encode(segment) != segment
|
||||||
|
|| Uri::percent_decode_lossy(segment.as_bytes()) != segment
|
||||||
|
|| segment.contains('+') {
|
||||||
|
return Err(Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RouteSegment {
|
||||||
|
string, source, index,
|
||||||
|
name: segment.into(),
|
||||||
|
kind: Kind::Static,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_many(
|
||||||
|
string: &str,
|
||||||
|
sep: char,
|
||||||
|
source: Source,
|
||||||
|
) -> impl Iterator<Item = SResult> {
|
||||||
|
let mut last_multi_seg: Option<&str> = None;
|
||||||
|
string.split(sep).filter(|s| !s.is_empty()).enumerate().map(move |(i, seg)| {
|
||||||
|
if let Some(multi_seg) = last_multi_seg {
|
||||||
|
return Err((seg, Trailing(multi_seg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parsed = Self::parse_one(seg).map_err(|e| (seg, e))?;
|
||||||
|
if parsed.kind == Kind::Multi {
|
||||||
|
last_multi_seg = Some(seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.index = Some(i);
|
||||||
|
parsed.source = source;
|
||||||
|
Ok(parsed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_path(uri: &'a Origin) -> impl Iterator<Item = SResult<'a>> {
|
||||||
|
Self::parse_many(uri.path(), '/', Source::Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_query(uri: &'a Origin) -> Option<impl Iterator<Item = SResult<'a>>> {
|
||||||
|
uri.query().map(|q| Self::parse_many(q, '&', Source::Query))
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,7 +98,7 @@ use {RawStr, uri::UriDisplay};
|
||||||
///
|
///
|
||||||
/// [`uri!`]: /rocket_codegen/#typed-uris-uri
|
/// [`uri!`]: /rocket_codegen/#typed-uris-uri
|
||||||
/// [`UriDisplay`]: /rocket/http/uri/trait.UriDisplay.html
|
/// [`UriDisplay`]: /rocket/http/uri/trait.UriDisplay.html
|
||||||
pub trait FromUriParam<T>: UriDisplay {
|
pub trait FromUriParam<T> {
|
||||||
/// The resulting type of this conversion.
|
/// The resulting type of this conversion.
|
||||||
type Target: UriDisplay;
|
type Target: UriDisplay;
|
||||||
|
|
||||||
|
@ -141,6 +141,20 @@ impl<'a, 'b> FromUriParam<&'a str> for &'b RawStr {
|
||||||
fn from_uri_param(param: &'a str) -> &'a str { param }
|
fn from_uri_param(param: &'a str) -> &'a str { param }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A no cost conversion allowing a `String` to be used in place of an `&RawStr`.
|
||||||
|
impl<'a> FromUriParam<String> for &'a RawStr {
|
||||||
|
type Target = String;
|
||||||
|
#[inline(always)]
|
||||||
|
fn from_uri_param(param: String) -> String { param }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A no cost conversion allowing a `String` to be used in place of an `&str`.
|
||||||
|
impl<'a> FromUriParam<String> for &'a str {
|
||||||
|
type Target = String;
|
||||||
|
#[inline(always)]
|
||||||
|
fn from_uri_param(param: String) -> String { param }
|
||||||
|
}
|
||||||
|
|
||||||
/// A no cost conversion allowing an `&Path` to be used in place of a `PathBuf`.
|
/// A no cost conversion allowing an `&Path` to be used in place of a `PathBuf`.
|
||||||
impl<'a> FromUriParam<&'a Path> for PathBuf {
|
impl<'a> FromUriParam<&'a Path> for PathBuf {
|
||||||
type Target = &'a Path;
|
type Target = &'a Path;
|
||||||
|
@ -151,6 +165,7 @@ impl<'a> FromUriParam<&'a Path> for PathBuf {
|
||||||
/// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`.
|
/// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`.
|
||||||
impl<'a> FromUriParam<&'a str> for PathBuf {
|
impl<'a> FromUriParam<&'a str> for PathBuf {
|
||||||
type Target = &'a Path;
|
type Target = &'a Path;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from_uri_param(param: &'a str) -> &'a Path {
|
fn from_uri_param(param: &'a str) -> &'a Path {
|
||||||
Path::new(param)
|
Path::new(param)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![feature(test, plugin, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::config::{Environment, Config, LoggingLevel};
|
use rocket::config::{Environment, Config, LoggingLevel};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![feature(test, plugin, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::config::{Environment, Config, LoggingLevel};
|
use rocket::config::{Environment, Config, LoggingLevel};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#![feature(test, plugin, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
// #![feature(alloc_system)]
|
// #![feature(alloc_system)]
|
||||||
// extern crate alloc_system;
|
// extern crate alloc_system;
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::config::{Environment, Config, LoggingLevel};
|
use rocket::config::{Environment, Config, LoggingLevel};
|
||||||
use rocket::http::RawStr;
|
use rocket::http::RawStr;
|
||||||
|
|
|
@ -33,8 +33,7 @@ use yansi::Color::*;
|
||||||
/// declared using the `catch` decorator, as follows:
|
/// declared using the `catch` decorator, as follows:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// #![plugin(rocket_codegen)]
|
|
||||||
///
|
///
|
||||||
/// #[macro_use] extern crate rocket;
|
/// #[macro_use] extern crate rocket;
|
||||||
///
|
///
|
||||||
|
|
|
@ -31,8 +31,7 @@ const PEEK_BYTES: usize = 512;
|
||||||
/// route parameter as follows:
|
/// route parameter as follows:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # type DataGuard = ::rocket::data::Data;
|
/// # type DataGuard = ::rocket::data::Data;
|
||||||
/// #[post("/submit", data = "<var>")]
|
/// #[post("/submit", data = "<var>")]
|
||||||
|
|
|
@ -130,8 +130,7 @@ pub type Transformed<'a, T> =
|
||||||
/// if the guard returns successfully.
|
/// if the guard returns successfully.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # type DataGuard = ::rocket::data::Data;
|
/// # type DataGuard = ::rocket::data::Data;
|
||||||
/// #[post("/submit", data = "<var>")]
|
/// #[post("/submit", data = "<var>")]
|
||||||
|
@ -177,9 +176,8 @@ pub type Transformed<'a, T> =
|
||||||
/// `String` (an `&str`).
|
/// `String` (an `&str`).
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// # #[derive(Debug)]
|
/// # #[derive(Debug)]
|
||||||
/// # struct Name<'a> { first: &'a str, last: &'a str, }
|
/// # struct Name<'a> { first: &'a str, last: &'a str, }
|
||||||
/// use std::io::{self, Read};
|
/// use std::io::{self, Read};
|
||||||
|
@ -424,8 +422,7 @@ impl<'f> FromData<'f> for Data {
|
||||||
/// that you can retrieve it directly from a client's request body:
|
/// that you can retrieve it directly from a client's request body:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # type Person = ::rocket::data::Data;
|
/// # type Person = ::rocket::data::Data;
|
||||||
/// #[post("/person", data = "<person>")]
|
/// #[post("/person", data = "<person>")]
|
||||||
|
@ -437,11 +434,8 @@ impl<'f> FromData<'f> for Data {
|
||||||
/// A `FromDataSimple` implementation allowing this looks like:
|
/// A `FromDataSimple` implementation allowing this looks like:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![allow(unused_attributes)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![allow(unused_variables)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #![feature(plugin, decl_macro)]
|
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(Debug)]
|
/// # #[derive(Debug)]
|
||||||
/// # struct Person { name: String, age: u16 }
|
/// # struct Person { name: String, age: u16 }
|
||||||
|
|
|
@ -86,8 +86,7 @@ pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
|
||||||
/// managed state and a static route, as follows:
|
/// managed state and a static route, as follows:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(Copy, Clone)]
|
/// # #[derive(Copy, Clone)]
|
||||||
|
|
|
@ -57,19 +57,20 @@
|
||||||
//! Then, add the following to the top of your `main.rs` file:
|
//! Then, add the following to the top of your `main.rs` file:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! #![feature(plugin, decl_macro)]
|
//! #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
//! # #![allow(unused_attributes)]
|
|
||||||
//! #![plugin(rocket_codegen)]
|
|
||||||
//!
|
//!
|
||||||
//! extern crate rocket;
|
//! #[macro_use] extern crate rocket;
|
||||||
|
//! #
|
||||||
|
//! # #[get("/")]
|
||||||
|
//! # fn hello() { }
|
||||||
|
//! # fn main() { rocket::ignite().mount("/", routes![hello]); }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See the [guide](https://rocket.rs/guide) for more information on how to
|
//! See the [guide](https://rocket.rs/guide) for more information on how to
|
||||||
//! write Rocket applications. Here's a simple example to get you started:
|
//! write Rocket applications. Here's a simple example to get you started:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! #![feature(plugin, decl_macro, proc_macro_non_items)]
|
//! #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
//! #![plugin(rocket_codegen)]
|
|
||||||
//!
|
//!
|
||||||
//! #[macro_use] extern crate rocket;
|
//! #[macro_use] extern crate rocket;
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -67,10 +67,9 @@
|
||||||
//! consider the following complete "Hello, world!" application, with testing.
|
//! consider the following complete "Hello, world!" application, with testing.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! #![feature(plugin, decl_macro)]
|
//! #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
//! #![plugin(rocket_codegen)]
|
|
||||||
//!
|
//!
|
||||||
//! extern crate rocket;
|
//! #[macro_use] extern crate rocket;
|
||||||
//!
|
//!
|
||||||
//! #[get("/")]
|
//! #[get("/")]
|
||||||
//! fn hello() -> &'static str {
|
//! fn hello() -> &'static str {
|
||||||
|
|
|
@ -209,13 +209,15 @@ pub fn init(level: LoggingLevel) -> bool {
|
||||||
try_init(level, true)
|
try_init(level, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method exists as a shim for the log macros that need to be called from
|
// Expose logging macros as (hidden) funcions for use by core/contrib codegen.
|
||||||
// an end user's code. It was added as part of the work to support database
|
macro_rules! external_log_function {
|
||||||
// connection pools via procedural macros.
|
($fn_name:ident: $macro_name:ident) => (
|
||||||
#[doc(hidden)]
|
#[doc(hidden)] #[inline(always)]
|
||||||
pub fn log_err(indented: bool, msg: &str) {
|
pub fn $fn_name(msg: &str) { $macro_name!("{}", msg); }
|
||||||
match indented {
|
)
|
||||||
true => error_!("{}", msg),
|
|
||||||
false => error!("{}", msg),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
external_log_function!(log_error: error);
|
||||||
|
external_log_function!(log_error_: error_);
|
||||||
|
external_log_function!(log_warn: warn);
|
||||||
|
external_log_function!(log_warn_: warn_);
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||||
use outcome::Outcome::*;
|
use outcome::Outcome::*;
|
||||||
use request::{Request, form::{FromForm, FormItems, FormDataError}};
|
use request::{Request, form::{FromForm, FormItems, FormDataError}};
|
||||||
use data::{Outcome, Transform, Transformed, Data, FromData};
|
use data::{Outcome, Transform, Transformed, Data, FromData};
|
||||||
use http::Status;
|
use http::{Status, uri::FromUriParam};
|
||||||
|
|
||||||
/// A data guard for parsing [`FromForm`] types strictly.
|
/// A data guard for parsing [`FromForm`] types strictly.
|
||||||
///
|
///
|
||||||
|
@ -42,9 +42,8 @@ use http::Status;
|
||||||
/// can access fields of `T` transparently through a `Form<T>`:
|
/// can access fields of `T` transparently through a `Form<T>`:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![allow(deprecated, unused_attributes)]
|
/// # #![allow(deprecated, unused_attributes)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::request::Form;
|
/// use rocket::request::Form;
|
||||||
/// use rocket::http::RawStr;
|
/// use rocket::http::RawStr;
|
||||||
|
@ -72,9 +71,8 @@ use http::Status;
|
||||||
/// A handler that handles a form of this type can similarly by written:
|
/// A handler that handles a form of this type can similarly by written:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![allow(deprecated, unused_attributes)]
|
/// # #![allow(deprecated, unused_attributes)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # use rocket::request::Form;
|
/// # use rocket::request::Form;
|
||||||
/// # #[derive(FromForm)]
|
/// # #[derive(FromForm)]
|
||||||
|
@ -119,7 +117,7 @@ use http::Status;
|
||||||
/// forms = 524288
|
/// forms = 524288
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Form<T>(T);
|
pub struct Form<T>(crate T);
|
||||||
|
|
||||||
impl<T> Form<T> {
|
impl<T> Form<T> {
|
||||||
/// Consumes `self` and returns the parsed value.
|
/// Consumes `self` and returns the parsed value.
|
||||||
|
@ -127,8 +125,7 @@ impl<T> Form<T> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::request::Form;
|
/// use rocket::request::Form;
|
||||||
///
|
///
|
||||||
|
@ -227,3 +224,12 @@ impl<'f, T: FromForm<'f>> FromData<'f> for Form<T> {
|
||||||
<Form<T>>::from_data(o.borrowed()?, true).map(Form)
|
<Form<T>>::from_data(o.borrowed()?, true).map(Form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'f, A, T: FromUriParam<A> + FromForm<'f>> FromUriParam<A> for Form<T> {
|
||||||
|
type Target = T::Target;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn from_uri_param(param: A) -> Self::Target {
|
||||||
|
T::from_uri_param(param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,24 +4,46 @@ use http::RawStr;
|
||||||
|
|
||||||
/// Iterator over the key/value pairs of a given HTTP form string.
|
/// Iterator over the key/value pairs of a given HTTP form string.
|
||||||
///
|
///
|
||||||
/// **Note:** The returned key/value pairs are _not_ URL decoded. To URL decode
|
|
||||||
/// the raw strings, use the
|
|
||||||
/// [`url_decode`](/rocket/http/struct.RawStr.html#method.url_decode) method:
|
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::request::{FormItems, FromFormValue};
|
/// use rocket::request::{FormItems, FromFormValue};
|
||||||
///
|
///
|
||||||
|
/// // Using the `key_value_decoded` method of `FormItem`.
|
||||||
/// let form_string = "greeting=Hello%2C+Mark%21&username=jake%2Fother";
|
/// let form_string = "greeting=Hello%2C+Mark%21&username=jake%2Fother";
|
||||||
/// for (key, value) in FormItems::from(form_string) {
|
/// for (key, value) in FormItems::from(form_string).map(|i| i.key_value_decoded()) {
|
||||||
/// let decoded_value = value.url_decode();
|
/// match &*key {
|
||||||
/// match key.as_str() {
|
/// "greeting" => assert_eq!(value, "Hello, Mark!".to_string()),
|
||||||
/// "greeting" => assert_eq!(decoded_value, Ok("Hello, Mark!".into())),
|
/// "username" => assert_eq!(value, "jake/other".to_string()),
|
||||||
/// "username" => assert_eq!(decoded_value, Ok("jake/other".into())),
|
/// _ => unreachable!()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Accessing the fields of `FormItem` directly, including `raw`.
|
||||||
|
/// for item in FormItems::from(form_string) {
|
||||||
|
/// match item.key.as_str() {
|
||||||
|
/// "greeting" => {
|
||||||
|
/// assert_eq!(item.raw, "greeting=Hello%2C+Mark%21");
|
||||||
|
/// assert_eq!(item.value, "Hello%2C+Mark%21");
|
||||||
|
/// assert_eq!(item.value.url_decode(), Ok("Hello, Mark!".into()));
|
||||||
|
/// }
|
||||||
|
/// "username" => {
|
||||||
|
/// assert_eq!(item.raw, "username=jake%2Fother");
|
||||||
|
/// assert_eq!(item.value, "jake%2Fother");
|
||||||
|
/// assert_eq!(item.value.url_decode(), Ok("jake/other".into()));
|
||||||
|
/// }
|
||||||
/// _ => unreachable!()
|
/// _ => unreachable!()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// # Form Items via. `FormItem`
|
||||||
|
///
|
||||||
|
/// This iterator returns values of the type [`FormItem`]. To access the
|
||||||
|
/// associated key/value pairs of the form item, either directly access them via
|
||||||
|
/// the [`key`](FormItem.key) and [`value`](FormItem.value) fields, use the
|
||||||
|
/// [`FormItem::key_value()`] method to get a tuple of the _raw_ `(key, value)`,
|
||||||
|
/// or use the [`FormItems::key_value_decoded()`] method to get a tuple of the
|
||||||
|
/// decoded (`key`, `value`).
|
||||||
|
///
|
||||||
/// # Completion
|
/// # Completion
|
||||||
///
|
///
|
||||||
/// The iterator keeps track of whether the form string was parsed to completion
|
/// The iterator keeps track of whether the form string was parsed to completion
|
||||||
|
@ -52,7 +74,7 @@ use http::RawStr;
|
||||||
///
|
///
|
||||||
/// // prints "greeting = hello", "username = jake", and "done = "
|
/// // prints "greeting = hello", "username = jake", and "done = "
|
||||||
/// let form_string = "greeting=hello&username=jake&done";
|
/// let form_string = "greeting=hello&username=jake&done";
|
||||||
/// for (key, value) in FormItems::from(form_string) {
|
/// for (key, value) in FormItems::from(form_string).map(|item| item.key_value()) {
|
||||||
/// println!("{} = {}", key, value);
|
/// println!("{} = {}", key, value);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -66,23 +88,66 @@ use http::RawStr;
|
||||||
/// let mut items = FormItems::from(form_string);
|
/// let mut items = FormItems::from(form_string);
|
||||||
///
|
///
|
||||||
/// let next = items.next().unwrap();
|
/// let next = items.next().unwrap();
|
||||||
/// assert_eq!(next.0, "greeting");
|
/// assert_eq!(next.key, "greeting");
|
||||||
/// assert_eq!(next.1, "hello");
|
/// assert_eq!(next.value, "hello");
|
||||||
///
|
///
|
||||||
/// let next = items.next().unwrap();
|
/// let next = items.next().unwrap();
|
||||||
/// assert_eq!(next.0, "username");
|
/// assert_eq!(next.key, "username");
|
||||||
/// assert_eq!(next.1, "jake");
|
/// assert_eq!(next.value, "jake");
|
||||||
///
|
///
|
||||||
/// let next = items.next().unwrap();
|
/// let next = items.next().unwrap();
|
||||||
/// assert_eq!(next.0, "done");
|
/// assert_eq!(next.key, "done");
|
||||||
/// assert_eq!(next.1, "");
|
/// assert_eq!(next.value, "");
|
||||||
///
|
///
|
||||||
/// assert_eq!(items.next(), None);
|
/// assert_eq!(items.next(), None);
|
||||||
/// assert!(items.completed());
|
/// assert!(items.completed());
|
||||||
/// ```
|
/// ```
|
||||||
pub struct FormItems<'f> {
|
#[derive(Debug)]
|
||||||
|
pub enum FormItems<'f> {
|
||||||
|
Raw {
|
||||||
string: &'f RawStr,
|
string: &'f RawStr,
|
||||||
next_index: usize
|
next_index: usize
|
||||||
|
},
|
||||||
|
Cooked {
|
||||||
|
items: &'f [FormItem<'f>],
|
||||||
|
next_index: usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A form items returned by the [`FormItems`] iterator.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FormItem<'f> {
|
||||||
|
/// The full, nonempty string for the item, not including delimiters.
|
||||||
|
pub raw: &'f RawStr,
|
||||||
|
/// The key for the item, which may be empty if `value` is nonempty.
|
||||||
|
///
|
||||||
|
/// **Note:** The key is _not_ URL decoded. To URL decode the raw strings,
|
||||||
|
/// use the [`RawStr::url_decode()`] method or access key-value pairs with
|
||||||
|
/// [`FromItem::key_value_decoded()`].
|
||||||
|
pub key: &'f RawStr,
|
||||||
|
/// The value for the item, which may be empty if `key` is nonempty.
|
||||||
|
///
|
||||||
|
/// **Note:** The value is _not_ URL decoded. To URL decode the raw strings,
|
||||||
|
/// use the [`RawStr::url_decode()`] method or access key-value pairs with
|
||||||
|
/// [`FromItem::key_value_decoded()`].
|
||||||
|
pub value: &'f RawStr
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'f> FormItem<'f> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn key_value(&self) -> (&'f RawStr, &'f RawStr) {
|
||||||
|
(self.key, self.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn key_value_decoded(&self) -> (String, String) {
|
||||||
|
(self.key.url_decode_lossy(), self.value.url_decode_lossy())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn explode(&self) -> (&'f RawStr, &'f RawStr, &'f RawStr) {
|
||||||
|
(self.raw, self.key, self.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'f> FormItems<'f> {
|
impl<'f> FormItems<'f> {
|
||||||
|
@ -117,7 +182,10 @@ impl<'f> FormItems<'f> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn completed(&self) -> bool {
|
pub fn completed(&self) -> bool {
|
||||||
self.next_index >= self.string.len()
|
match self {
|
||||||
|
FormItems::Raw { string, next_index } => *next_index >= string.len(),
|
||||||
|
FormItems::Cooked { items, next_index } => *next_index >= items.len(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses all remaining key/value pairs and returns `true` if parsing ran
|
/// Parses all remaining key/value pairs and returns `true` if parsing ran
|
||||||
|
@ -161,61 +229,38 @@ impl<'f> FormItems<'f> {
|
||||||
#[inline]
|
#[inline]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn mark_complete(&mut self) {
|
pub fn mark_complete(&mut self) {
|
||||||
self.next_index = self.string.len()
|
match self {
|
||||||
|
FormItems::Raw { string, ref mut next_index } => *next_index = string.len(),
|
||||||
|
FormItems::Cooked { items, ref mut next_index } => *next_index = items.len(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the original string being parsed by this iterator. The string
|
|
||||||
/// returned by this method does not change, regardless of the status of the
|
|
||||||
/// iterator.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::request::FormItems;
|
|
||||||
///
|
|
||||||
/// let form_string = "a=b&c=d";
|
|
||||||
/// let mut items = FormItems::from(form_string);
|
|
||||||
/// assert_eq!(items.inner_str(), form_string);
|
|
||||||
///
|
|
||||||
/// assert!(items.next().is_some());
|
|
||||||
/// assert_eq!(items.inner_str(), form_string);
|
|
||||||
///
|
|
||||||
/// assert!(items.next().is_some());
|
|
||||||
/// assert_eq!(items.inner_str(), form_string);
|
|
||||||
///
|
|
||||||
/// assert!(items.next().is_none());
|
|
||||||
/// assert_eq!(items.inner_str(), form_string);
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_str(&self) -> &'f RawStr {
|
|
||||||
self.string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'f> From<&'f RawStr> for FormItems<'f> {
|
impl<'f> From<&'f RawStr> for FormItems<'f> {
|
||||||
/// Returns an iterator over the key/value pairs in the
|
|
||||||
/// `x-www-form-urlencoded` form `string`.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(string: &'f RawStr) -> FormItems<'f> {
|
fn from(string: &'f RawStr) -> FormItems<'f> {
|
||||||
FormItems { string, next_index: 0 }
|
FormItems::Raw { string, next_index: 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'f> From<&'f str> for FormItems<'f> {
|
impl<'f> From<&'f str> for FormItems<'f> {
|
||||||
/// Returns an iterator over the key/value pairs in the
|
|
||||||
/// `x-www-form-urlencoded` form `string`.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(string: &'f str) -> FormItems<'f> {
|
fn from(string: &'f str) -> FormItems<'f> {
|
||||||
FormItems::from(RawStr::from_str(string))
|
FormItems::from(RawStr::from_str(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'f> Iterator for FormItems<'f> {
|
impl<'f> From<&'f [FormItem<'f>]> for FormItems<'f> {
|
||||||
type Item = (&'f RawStr, &'f RawStr);
|
#[inline(always)]
|
||||||
|
fn from(items: &'f [FormItem<'f>]) -> FormItems<'f> {
|
||||||
|
FormItems::Cooked { items, next_index: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn raw<'f>(string: &mut &'f RawStr, index: &mut usize) -> Option<FormItem<'f>> {
|
||||||
loop {
|
loop {
|
||||||
let s = &self.string[self.next_index..];
|
let start = *index;
|
||||||
|
let s = &string[start..];
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -232,85 +277,147 @@ impl<'f> Iterator for FormItems<'f> {
|
||||||
None => (rest, rest.len())
|
None => (rest, rest.len())
|
||||||
};
|
};
|
||||||
|
|
||||||
self.next_index += key_consumed + val_consumed;
|
*index += key_consumed + val_consumed;
|
||||||
|
let raw = &string[start..(start + key_consumed + value.len())];
|
||||||
match (key.is_empty(), value.is_empty()) {
|
match (key.is_empty(), value.is_empty()) {
|
||||||
(true, true) => continue,
|
(true, true) => continue,
|
||||||
_ => return Some((key.into(), value.into()))
|
_ => return Some(FormItem {
|
||||||
}
|
raw: raw.into(),
|
||||||
|
key: key.into(),
|
||||||
|
value: value.into()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'f> Iterator for FormItems<'f> {
|
||||||
mod test {
|
type Item = FormItem<'f>;
|
||||||
use super::FormItems;
|
|
||||||
|
|
||||||
macro_rules! check_form {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
(@bad $string:expr) => (check_form($string, None));
|
match self {
|
||||||
($string:expr, $expected:expr) => (check_form($string, Some($expected)));
|
FormItems::Raw { ref mut string, ref mut next_index } => {
|
||||||
}
|
raw(string, next_index)
|
||||||
|
|
||||||
fn check_form(string: &str, expected: Option<&[(&str, &str)]>) {
|
|
||||||
let mut items = FormItems::from(string);
|
|
||||||
let results: Vec<_> = items.by_ref().collect();
|
|
||||||
if let Some(expected) = expected {
|
|
||||||
assert_eq!(expected.len(), results.len(),
|
|
||||||
"expected {:?}, got {:?} for {:?}", expected, results, string);
|
|
||||||
|
|
||||||
for i in 0..results.len() {
|
|
||||||
let (expected_key, actual_key) = (expected[i].0, results[i].0);
|
|
||||||
let (expected_val, actual_val) = (expected[i].1, results[i].1);
|
|
||||||
|
|
||||||
assert!(actual_key == expected_key,
|
|
||||||
"key [{}] mismatch for {}: expected {}, got {}",
|
|
||||||
i, string, expected_key, actual_key);
|
|
||||||
|
|
||||||
assert!(actual_val == expected_val,
|
|
||||||
"val [{}] mismatch for {}: expected {}, got {}",
|
|
||||||
i, string, expected_val, actual_val);
|
|
||||||
}
|
}
|
||||||
|
FormItems::Cooked { items, ref mut next_index } => {
|
||||||
|
if *next_index < items.len() {
|
||||||
|
let item = items[*next_index];
|
||||||
|
*next_index += 1;
|
||||||
|
Some(item)
|
||||||
} else {
|
} else {
|
||||||
assert!(!items.exhaust(), "{} unexpectedly parsed successfully", string);
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_form_string() {
|
|
||||||
check_form!("username=user&password=pass",
|
|
||||||
&[("username", "user"), ("password", "pass")]);
|
|
||||||
|
|
||||||
check_form!("user=user&user=pass", &[("user", "user"), ("user", "pass")]);
|
|
||||||
check_form!("user=&password=pass", &[("user", ""), ("password", "pass")]);
|
|
||||||
check_form!("user&password=pass", &[("user", ""), ("password", "pass")]);
|
|
||||||
check_form!("foo&bar", &[("foo", ""), ("bar", "")]);
|
|
||||||
|
|
||||||
check_form!("a=b", &[("a", "b")]);
|
|
||||||
check_form!("value=Hello+World", &[("value", "Hello+World")]);
|
|
||||||
|
|
||||||
check_form!("user=", &[("user", "")]);
|
|
||||||
check_form!("user=&", &[("user", "")]);
|
|
||||||
check_form!("a=b&a=", &[("a", "b"), ("a", "")]);
|
|
||||||
check_form!("user=&password", &[("user", ""), ("password", "")]);
|
|
||||||
check_form!("a=b&a", &[("a", "b"), ("a", "")]);
|
|
||||||
|
|
||||||
check_form!("user=x&&", &[("user", "x")]);
|
|
||||||
check_form!("user=x&&&&pass=word", &[("user", "x"), ("pass", "word")]);
|
|
||||||
check_form!("user=x&&&&pass=word&&&x=z&d&&&e",
|
|
||||||
&[("user", "x"), ("pass", "word"), ("x", "z"), ("d", ""), ("e", "")]);
|
|
||||||
|
|
||||||
check_form!("=&a=b&&=", &[("a", "b")]);
|
|
||||||
check_form!("=b", &[("", "b")]);
|
|
||||||
check_form!("=b&=c", &[("", "b"), ("", "c")]);
|
|
||||||
|
|
||||||
check_form!("=", &[]);
|
|
||||||
check_form!("&=&", &[]);
|
|
||||||
check_form!("&", &[]);
|
|
||||||
check_form!("=&=", &[]);
|
|
||||||
|
|
||||||
check_form!(@bad "=b&==");
|
|
||||||
check_form!(@bad "==");
|
|
||||||
check_form!(@bad "=k=");
|
|
||||||
check_form!(@bad "=abc=");
|
|
||||||
check_form!(@bad "=abc=cd");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod test {
|
||||||
|
// use super::FormItems;
|
||||||
|
|
||||||
|
// impl<'f> From<&'f [(&'f str, &'f str, &'f str)]> for FormItems<'f> {
|
||||||
|
// #[inline(always)]
|
||||||
|
// fn from(triples: &'f [(&'f str, &'f str, &'f str)]) -> FormItems<'f> {
|
||||||
|
// // Safe because RawStr(str) is repr(transparent).
|
||||||
|
// let triples = unsafe { ::std::mem::transmute(triples) };
|
||||||
|
// FormItems::Cooked { triples, next_index: 0 }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// macro_rules! check_form {
|
||||||
|
// (@bad $string:expr) => (check_form($string, None));
|
||||||
|
// ($string:expr, $expected:expr) => (check_form(&$string[..], Some($expected)));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn check_form<'a, T>(items: T, expected: Option<&[(&str, &str, &str)]>)
|
||||||
|
// where T: Into<FormItems<'a>> + ::std::fmt::Debug
|
||||||
|
// {
|
||||||
|
// let string = format!("{:?}", items);
|
||||||
|
// let mut items = items.into();
|
||||||
|
// let results: Vec<_> = items.by_ref().map(|item| item.explode()).collect();
|
||||||
|
// if let Some(expected) = expected {
|
||||||
|
// assert_eq!(expected.len(), results.len(),
|
||||||
|
// "expected {:?}, got {:?} for {:?}", expected, results, string);
|
||||||
|
|
||||||
|
// for i in 0..results.len() {
|
||||||
|
// let (expected_raw, expected_key, expected_val) = expected[i];
|
||||||
|
// let (actual_raw, actual_key, actual_val) = results[i];
|
||||||
|
|
||||||
|
// assert!(actual_raw == expected_raw,
|
||||||
|
// "raw [{}] mismatch for {}: expected {}, got {}",
|
||||||
|
// i, string, expected_raw, actual_raw);
|
||||||
|
|
||||||
|
// assert!(actual_key == expected_key,
|
||||||
|
// "key [{}] mismatch for {}: expected {}, got {}",
|
||||||
|
// i, string, expected_key, actual_key);
|
||||||
|
|
||||||
|
// assert!(actual_val == expected_val,
|
||||||
|
// "val [{}] mismatch for {}: expected {}, got {}",
|
||||||
|
// i, string, expected_val, actual_val);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// assert!(!items.exhaust(), "{} unexpectedly parsed successfully", string);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn test_cooked_items() {
|
||||||
|
// check_form!(
|
||||||
|
// &[("username=user", "username", "user"), ("password=pass", "password", "pass")],
|
||||||
|
// &[("username=user", "username", "user"), ("password=pass", "password", "pass")]
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let empty: &[(&str, &str, &str)] = &[];
|
||||||
|
// check_form!(empty, &[]);
|
||||||
|
|
||||||
|
// check_form!(&[("a=b", "a", "b")], &[("a=b", "a", "b")]);
|
||||||
|
|
||||||
|
// check_form!(
|
||||||
|
// &[("user=x", "user", "x"), ("pass=word", "pass", "word"),
|
||||||
|
// ("x=z", "x", "z"), ("d=", "d", ""), ("e=", "e", "")],
|
||||||
|
|
||||||
|
// &[("user=x", "user", "x"), ("pass=word", "pass", "word"),
|
||||||
|
// ("x=z", "x", "z"), ("d=", "d", ""), ("e=", "e", "")]
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // #[test]
|
||||||
|
// // fn test_form_string() {
|
||||||
|
// // check_form!("username=user&password=pass",
|
||||||
|
// // &[("username", "user"), ("password", "pass")]);
|
||||||
|
|
||||||
|
// // check_form!("user=user&user=pass", &[("user", "user"), ("user", "pass")]);
|
||||||
|
// // check_form!("user=&password=pass", &[("user", ""), ("password", "pass")]);
|
||||||
|
// // check_form!("user&password=pass", &[("user", ""), ("password", "pass")]);
|
||||||
|
// // check_form!("foo&bar", &[("foo", ""), ("bar", "")]);
|
||||||
|
|
||||||
|
// // check_form!("a=b", &[("a", "b")]);
|
||||||
|
// // check_form!("value=Hello+World", &[("value", "Hello+World")]);
|
||||||
|
|
||||||
|
// // check_form!("user=", &[("user", "")]);
|
||||||
|
// // check_form!("user=&", &[("user", "")]);
|
||||||
|
// // check_form!("a=b&a=", &[("a", "b"), ("a", "")]);
|
||||||
|
// // check_form!("user=&password", &[("user", ""), ("password", "")]);
|
||||||
|
// // check_form!("a=b&a", &[("a", "b"), ("a", "")]);
|
||||||
|
|
||||||
|
// // check_form!("user=x&&", &[("user", "x")]);
|
||||||
|
// // check_form!("user=x&&&&pass=word", &[("user", "x"), ("pass", "word")]);
|
||||||
|
// // check_form!("user=x&&&&pass=word&&&x=z&d&&&e",
|
||||||
|
// // &[("user", "x"), ("pass", "word"), ("x", "z"), ("d", ""), ("e", "")]);
|
||||||
|
|
||||||
|
// // check_form!("=&a=b&&=", &[("a", "b")]);
|
||||||
|
// // check_form!("=b", &[("", "b")]);
|
||||||
|
// // check_form!("=b&=c", &[("", "b"), ("", "c")]);
|
||||||
|
|
||||||
|
// // check_form!("=", &[]);
|
||||||
|
// // check_form!("&=&", &[]);
|
||||||
|
// // check_form!("&", &[]);
|
||||||
|
// // check_form!("=&=", &[]);
|
||||||
|
|
||||||
|
// // check_form!(@bad "=b&==");
|
||||||
|
// // check_form!(@bad "==");
|
||||||
|
// // check_form!(@bad "=k=");
|
||||||
|
// // check_form!(@bad "=abc=");
|
||||||
|
// // check_form!(@bad "=abc=cd");
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
|
|
@ -14,8 +14,7 @@ use request::FormItems;
|
||||||
/// validation.
|
/// validation.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #![feature(plugin, decl_macro)]
|
/// #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// #![plugin(rocket_codegen)]
|
|
||||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||||
///
|
///
|
||||||
/// #[macro_use] extern crate rocket;
|
/// #[macro_use] extern crate rocket;
|
||||||
|
@ -34,9 +33,8 @@ use request::FormItems;
|
||||||
/// data via the `data` parameter and `Form` type.
|
/// data via the `data` parameter and `Form` type.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
/// # #![allow(deprecated, dead_code, unused_attributes)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # use rocket::request::Form;
|
/// # use rocket::request::Form;
|
||||||
/// # #[derive(FromForm)]
|
/// # #[derive(FromForm)]
|
||||||
|
@ -81,10 +79,10 @@ use request::FormItems;
|
||||||
/// fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<Item, ()> {
|
/// fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<Item, ()> {
|
||||||
/// let mut field = None;
|
/// let mut field = None;
|
||||||
///
|
///
|
||||||
/// for (key, value) in items {
|
/// for item in items {
|
||||||
/// match key.as_str() {
|
/// match item.key.as_str() {
|
||||||
/// "balloon" | "space" if field.is_none() => {
|
/// "balloon" | "space" if field.is_none() => {
|
||||||
/// let decoded = value.url_decode().map_err(|_| ())?;
|
/// let decoded = item.value.url_decode().map_err(|_| ())?;
|
||||||
/// field = Some(decoded);
|
/// field = Some(decoded);
|
||||||
/// }
|
/// }
|
||||||
/// _ if strict => return Err(()),
|
/// _ if strict => return Err(()),
|
||||||
|
@ -115,16 +113,6 @@ pub trait FromForm<'f>: Sized {
|
||||||
fn from_form(it: &mut FormItems<'f>, strict: bool) -> Result<Self, Self::Error>;
|
fn from_form(it: &mut FormItems<'f>, strict: bool) -> Result<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This implementation should only be used during debugging!
|
|
||||||
impl<'f> FromForm<'f> for &'f str {
|
|
||||||
type Error = !;
|
|
||||||
|
|
||||||
fn from_form(items: &mut FormItems<'f>, _: bool) -> Result<Self, !> {
|
|
||||||
items.mark_complete();
|
|
||||||
Ok(items.inner_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'f, T: FromForm<'f>> FromForm<'f> for Option<T> {
|
impl<'f, T: FromForm<'f>> FromForm<'f> for Option<T> {
|
||||||
type Error = !;
|
type Error = !;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use request::{Request, form::{Form, FormDataError, FromForm}};
|
use request::{Request, form::{Form, FormDataError, FromForm}};
|
||||||
use data::{Data, Transform, Transformed, FromData, Outcome};
|
use data::{Data, Transform, Transformed, FromData, Outcome};
|
||||||
|
use http::uri::FromUriParam;
|
||||||
|
|
||||||
/// A data gaurd for parsing [`FromForm`] types leniently.
|
/// A data gaurd for parsing [`FromForm`] types leniently.
|
||||||
///
|
///
|
||||||
|
@ -30,9 +31,8 @@ use data::{Data, Transform, Transformed, FromData, Outcome};
|
||||||
/// handler:
|
/// handler:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![allow(deprecated, unused_attributes)]
|
/// # #![allow(deprecated, unused_attributes)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::request::LenientForm;
|
/// use rocket::request::LenientForm;
|
||||||
///
|
///
|
||||||
|
@ -60,7 +60,7 @@ use data::{Data, Transform, Transformed, FromData, Outcome};
|
||||||
/// forms = 524288
|
/// forms = 524288
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LenientForm<T>(T);
|
pub struct LenientForm<T>(crate T);
|
||||||
|
|
||||||
impl<T> LenientForm<T> {
|
impl<T> LenientForm<T> {
|
||||||
/// Consumes `self` and returns the parsed value.
|
/// Consumes `self` and returns the parsed value.
|
||||||
|
@ -68,8 +68,7 @@ impl<T> LenientForm<T> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::request::LenientForm;
|
/// use rocket::request::LenientForm;
|
||||||
///
|
///
|
||||||
|
@ -110,3 +109,12 @@ impl<'f, T: FromForm<'f>> FromData<'f> for LenientForm<T> {
|
||||||
<Form<T>>::from_data(o.borrowed()?, false).map(LenientForm)
|
<Form<T>>::from_data(o.borrowed()?, false).map(LenientForm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'f, A, T: FromUriParam<A> + FromForm<'f>> FromUriParam<A> for LenientForm<T> {
|
||||||
|
type Target = T::Target;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn from_uri_param(param: A) -> Self::Target {
|
||||||
|
T::from_uri_param(param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod lenient;
|
||||||
mod error;
|
mod error;
|
||||||
mod form;
|
mod form;
|
||||||
|
|
||||||
pub use self::form_items::FormItems;
|
pub use self::form_items::{FormItems, FormItem};
|
||||||
pub use self::from_form::FromForm;
|
pub use self::from_form::FromForm;
|
||||||
pub use self::from_form_value::FromFormValue;
|
pub use self::from_form_value::FromFormValue;
|
||||||
pub use self::form::Form;
|
pub use self::form::Form;
|
||||||
|
|
|
@ -166,9 +166,8 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// `sensitive` handler.
|
/// `sensitive` handler.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #
|
/// #
|
||||||
/// use rocket::Outcome;
|
/// use rocket::Outcome;
|
||||||
/// use rocket::http::Status;
|
/// use rocket::http::Status;
|
||||||
|
@ -222,9 +221,8 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// routes (`admin_dashboard` and `user_dashboard`):
|
/// routes (`admin_dashboard` and `user_dashboard`):
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, never_type)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #
|
/// #
|
||||||
/// # use rocket::outcome::{IntoOutcome, Outcome};
|
/// # use rocket::outcome::{IntoOutcome, Outcome};
|
||||||
/// # use rocket::request::{self, FromRequest, Request};
|
/// # use rocket::request::{self, FromRequest, Request};
|
||||||
|
@ -285,9 +283,9 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// used, as illustrated below:
|
/// used, as illustrated below:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, never_type)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #![feature(never_type)]
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
/// #
|
||||||
/// # use rocket::outcome::{IntoOutcome, Outcome};
|
/// # use rocket::outcome::{IntoOutcome, Outcome};
|
||||||
/// # use rocket::request::{self, FromRequest, Request};
|
/// # use rocket::request::{self, FromRequest, Request};
|
||||||
|
|
|
@ -5,6 +5,7 @@ mod param;
|
||||||
mod form;
|
mod form;
|
||||||
mod from_request;
|
mod from_request;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod query;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -12,9 +13,10 @@ mod tests;
|
||||||
pub use self::request::Request;
|
pub use self::request::Request;
|
||||||
pub use self::from_request::{FromRequest, Outcome};
|
pub use self::from_request::{FromRequest, Outcome};
|
||||||
pub use self::param::{FromParam, FromSegments};
|
pub use self::param::{FromParam, FromSegments};
|
||||||
pub use self::form::{Form, LenientForm, FormItems};
|
pub use self::form::{Form, LenientForm, FormItems, FormItem};
|
||||||
pub use self::form::{FromForm, FormError, FromFormValue, FormParseError, FormDataError};
|
pub use self::form::{FromForm, FormError, FromFormValue, FormParseError, FormDataError};
|
||||||
pub use self::state::State;
|
pub use self::state::State;
|
||||||
|
pub use self::query::{Query, FromQuery};
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use response::flash::FlashMessage;
|
pub use response::flash::FlashMessage;
|
||||||
|
|
|
@ -19,9 +19,8 @@ use http::{RawStr, uri::{Segments, SegmentError}};
|
||||||
/// handler for the dynamic `"/<id>"` path:
|
/// handler for the dynamic `"/<id>"` path:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #[get("/<id>")]
|
/// #[get("/<id>")]
|
||||||
/// fn hello(id: usize) -> String {
|
/// fn hello(id: usize) -> String {
|
||||||
/// # let _id = id;
|
/// # let _id = id;
|
||||||
|
@ -55,9 +54,8 @@ use http::{RawStr, uri::{Segments, SegmentError}};
|
||||||
/// parameter as follows:
|
/// parameter as follows:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// # use rocket::http::RawStr;
|
/// # use rocket::http::RawStr;
|
||||||
/// #[get("/<id>")]
|
/// #[get("/<id>")]
|
||||||
/// fn hello(id: Result<usize, &RawStr>) -> String {
|
/// fn hello(id: Result<usize, &RawStr>) -> String {
|
||||||
|
@ -168,9 +166,8 @@ use http::{RawStr, uri::{Segments, SegmentError}};
|
||||||
/// dynamic path segment:
|
/// dynamic path segment:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// # use rocket::request::FromParam;
|
/// # use rocket::request::FromParam;
|
||||||
/// # use rocket::http::RawStr;
|
/// # use rocket::http::RawStr;
|
||||||
/// # #[allow(dead_code)]
|
/// # #[allow(dead_code)]
|
||||||
|
@ -284,12 +281,14 @@ impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> {
|
||||||
///
|
///
|
||||||
/// # Provided Implementations
|
/// # Provided Implementations
|
||||||
///
|
///
|
||||||
/// Rocket implements `FromParam` for `PathBuf`. The `PathBuf` implementation
|
/// **`PathBuf`**
|
||||||
/// constructs a path from the segments iterator. Each segment is
|
///
|
||||||
/// percent-decoded. If a segment equals ".." before or after decoding, the
|
/// The `PathBuf` implementation constructs a path from the segments iterator.
|
||||||
/// previous segment (if any) is omitted. For security purposes, any other
|
/// Each segment is percent-decoded. If a segment equals ".." before or after
|
||||||
/// segments that begin with "*" or "." are ignored. If a percent-decoded
|
/// decoding, the previous segment (if any) is omitted. For security purposes,
|
||||||
/// segment results in invalid UTF8, an `Err` is returned with the `Utf8Error`.
|
/// any other segments that begin with "*" or "." are ignored. If a
|
||||||
|
/// percent-decoded segment results in invalid UTF8, an `Err` is returned with
|
||||||
|
/// the `Utf8Error`.
|
||||||
pub trait FromSegments<'a>: Sized {
|
pub trait FromSegments<'a>: Sized {
|
||||||
/// The associated error to be returned when parsing fails.
|
/// The associated error to be returned when parsing fails.
|
||||||
type Error: Debug;
|
type Error: Debug;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
use std::{slice::Iter, iter::Cloned};
|
||||||
|
|
||||||
|
use request::{FormItems, FormItem, Form, LenientForm, FromForm};
|
||||||
|
|
||||||
|
pub struct Query<'q>(pub &'q [FormItem<'q>]);
|
||||||
|
|
||||||
|
impl<'q> IntoIterator for Query<'q> {
|
||||||
|
type Item = FormItem<'q>;
|
||||||
|
type IntoIter = Cloned<Iter<'q, FormItem<'q>>>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter().cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromQuery<'q>: Sized {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
fn from_query(q: Query<'q>) -> Result<Self, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q, T: FromForm<'q>> FromQuery<'q> for Form<T> {
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_query(q: Query<'q>) -> Result<Self, Self::Error> {
|
||||||
|
T::from_form(&mut FormItems::from(q.0), true).map(Form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q, T: FromForm<'q>> FromQuery<'q> for LenientForm<T> {
|
||||||
|
type Error = <T as FromForm<'q>>::Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_query(q: Query<'q>) -> Result<Self, Self::Error> {
|
||||||
|
T::from_form(&mut FormItems::from(q.0), false).map(LenientForm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q, T: FromQuery<'q>> FromQuery<'q> for Option<T> {
|
||||||
|
type Error = !;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_query(q: Query<'q>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(T::from_query(q).ok())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q, T: FromQuery<'q>> FromQuery<'q> for Result<T, T::Error> {
|
||||||
|
type Error = !;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_query(q: Query<'q>) -> Result<Self, Self::Error> {
|
||||||
|
Ok(T::from_query(q))
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,27 +7,18 @@ use std::str;
|
||||||
use yansi::Paint;
|
use yansi::Paint;
|
||||||
use state::{Container, Storage};
|
use state::{Container, Storage};
|
||||||
|
|
||||||
use super::{FromParam, FromSegments, FromRequest, Outcome};
|
use request::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
|
use request::{FromFormValue, FormItems, FormItem};
|
||||||
|
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
use router::Route;
|
use router::Route;
|
||||||
use config::{Config, Limits};
|
use config::{Config, Limits};
|
||||||
use http::uri::{Origin, Segments};
|
use http::uri::{Origin, Segments};
|
||||||
use http::{Method, Header, HeaderMap, Cookies, CookieJar};
|
use http::{Method, Header, HeaderMap, Cookies, CookieJar};
|
||||||
use http::{RawStr, ContentType, Accept, MediaType};
|
use http::{RawStr, ContentType, Accept, MediaType, Indexed, SmallVec};
|
||||||
use http::hyper;
|
use http::hyper;
|
||||||
|
|
||||||
#[derive(Clone)]
|
type Indices = (usize, usize);
|
||||||
struct RequestState<'r> {
|
|
||||||
config: &'r Config,
|
|
||||||
managed: &'r Container,
|
|
||||||
params: RefCell<Vec<(usize, usize)>>,
|
|
||||||
route: Cell<Option<&'r Route>>,
|
|
||||||
cookies: RefCell<CookieJar>,
|
|
||||||
accept: Storage<Option<Accept>>,
|
|
||||||
content_type: Storage<Option<ContentType>>,
|
|
||||||
cache: Rc<Container>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of an incoming web request.
|
/// The type of an incoming web request.
|
||||||
///
|
///
|
||||||
|
@ -42,7 +33,27 @@ pub struct Request<'r> {
|
||||||
uri: Origin<'r>,
|
uri: Origin<'r>,
|
||||||
headers: HeaderMap<'r>,
|
headers: HeaderMap<'r>,
|
||||||
remote: Option<SocketAddr>,
|
remote: Option<SocketAddr>,
|
||||||
state: RequestState<'r>,
|
crate state: RequestState<'r>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
crate struct RequestState<'r> {
|
||||||
|
crate config: &'r Config,
|
||||||
|
crate managed: &'r Container,
|
||||||
|
crate path_segments: SmallVec<[Indices; 12]>,
|
||||||
|
crate query_items: Option<SmallVec<[IndexedFormItem; 6]>>,
|
||||||
|
crate route: Cell<Option<&'r Route>>,
|
||||||
|
crate cookies: RefCell<CookieJar>,
|
||||||
|
crate accept: Storage<Option<Accept>>,
|
||||||
|
crate content_type: Storage<Option<ContentType>>,
|
||||||
|
crate cache: Rc<Container>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
crate struct IndexedFormItem {
|
||||||
|
raw: Indices,
|
||||||
|
key: Indices,
|
||||||
|
value: Indices
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> Request<'r> {
|
impl<'r> Request<'r> {
|
||||||
|
@ -53,31 +64,26 @@ impl<'r> Request<'r> {
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: Origin<'s>
|
uri: Origin<'s>
|
||||||
) -> Request<'r> {
|
) -> Request<'r> {
|
||||||
Request {
|
let mut request = Request {
|
||||||
method: Cell::new(method),
|
method: Cell::new(method),
|
||||||
uri: uri,
|
uri: uri,
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
remote: None,
|
remote: None,
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
|
path_segments: SmallVec::new(),
|
||||||
|
query_items: None,
|
||||||
config: &rocket.config,
|
config: &rocket.config,
|
||||||
managed: &rocket.state,
|
managed: &rocket.state,
|
||||||
route: Cell::new(None),
|
route: Cell::new(None),
|
||||||
params: RefCell::new(Vec::new()),
|
|
||||||
cookies: RefCell::new(CookieJar::new()),
|
cookies: RefCell::new(CookieJar::new()),
|
||||||
accept: Storage::new(),
|
accept: Storage::new(),
|
||||||
content_type: Storage::new(),
|
content_type: Storage::new(),
|
||||||
cache: Rc::new(Container::new()),
|
cache: Rc::new(Container::new()),
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Only used by doc-tests!
|
request.update_cached_uri_info();
|
||||||
#[doc(hidden)]
|
request
|
||||||
pub fn example<F: Fn(&mut Request)>(method: Method, uri: &str, f: F) {
|
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
|
||||||
let uri = Origin::parse(uri).expect("invalid URI in example");
|
|
||||||
let mut request = Request::new(&rocket, method, uri);
|
|
||||||
f(&mut request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the method from `self`.
|
/// Retrieve the method from `self`.
|
||||||
|
@ -145,16 +151,14 @@ impl<'r> Request<'r> {
|
||||||
/// # use rocket::http::Method;
|
/// # use rocket::http::Method;
|
||||||
/// # Request::example(Method::Get, "/uri", |mut request| {
|
/// # Request::example(Method::Get, "/uri", |mut request| {
|
||||||
/// let uri = Origin::parse("/hello/Sergio?type=greeting").unwrap();
|
/// let uri = Origin::parse("/hello/Sergio?type=greeting").unwrap();
|
||||||
///
|
|
||||||
/// request.set_uri(uri);
|
/// request.set_uri(uri);
|
||||||
/// assert_eq!(request.uri().path(), "/hello/Sergio");
|
/// assert_eq!(request.uri().path(), "/hello/Sergio");
|
||||||
/// assert_eq!(request.uri().query(), Some("type=greeting"));
|
/// assert_eq!(request.uri().query(), Some("type=greeting"));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
|
||||||
pub fn set_uri<'u: 'r>(&mut self, uri: Origin<'u>) {
|
pub fn set_uri<'u: 'r>(&mut self, uri: Origin<'u>) {
|
||||||
self.uri = uri;
|
self.uri = uri;
|
||||||
*self.state.params.borrow_mut() = Vec::new();
|
self.update_cached_uri_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the address of the remote connection that initiated this
|
/// Returns the address of the remote connection that initiated this
|
||||||
|
@ -265,6 +269,40 @@ impl<'r> Request<'r> {
|
||||||
self.real_ip().or_else(|| self.remote().map(|r| r.ip()))
|
self.real_ip().or_else(|| self.remote().map(|r| r.ip()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a wrapped borrow to the cookies in `self`.
|
||||||
|
///
|
||||||
|
/// [`Cookies`](/rocket/http/enum.Cookies.html) implements internal
|
||||||
|
/// mutability, so this method allows you to get _and_ add/remove cookies in
|
||||||
|
/// `self`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Add a new cookie to a request's cookies:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::Request;
|
||||||
|
/// # use rocket::http::Method;
|
||||||
|
/// use rocket::http::Cookie;
|
||||||
|
///
|
||||||
|
/// # Request::example(Method::Get, "/uri", |mut request| {
|
||||||
|
/// request.cookies().add(Cookie::new("key", "val"));
|
||||||
|
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
pub fn cookies(&self) -> Cookies {
|
||||||
|
// FIXME: Can we do better? This is disappointing.
|
||||||
|
match self.state.cookies.try_borrow_mut() {
|
||||||
|
Ok(jar) => Cookies::new(jar, self.state.config.secret_key()),
|
||||||
|
Err(_) => {
|
||||||
|
error_!("Multiple `Cookies` instances are active at once.");
|
||||||
|
info_!("An instance of `Cookies` must be dropped before another \
|
||||||
|
can be retrieved.");
|
||||||
|
warn_!("The retrieved `Cookies` instance will be empty.");
|
||||||
|
Cookies::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a [`HeaderMap`](/rocket/http/struct.HeaderMap.html) of all of
|
/// Returns a [`HeaderMap`](/rocket/http/struct.HeaderMap.html) of all of
|
||||||
/// the headers in `self`.
|
/// the headers in `self`.
|
||||||
///
|
///
|
||||||
|
@ -334,40 +372,6 @@ impl<'r> Request<'r> {
|
||||||
self.headers.replace(header.into());
|
self.headers.replace(header.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a wrapped borrow to the cookies in `self`.
|
|
||||||
///
|
|
||||||
/// [`Cookies`](/rocket/http/enum.Cookies.html) implements internal
|
|
||||||
/// mutability, so this method allows you to get _and_ add/remove cookies in
|
|
||||||
/// `self`.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Add a new cookie to a request's cookies:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use rocket::Request;
|
|
||||||
/// # use rocket::http::Method;
|
|
||||||
/// use rocket::http::Cookie;
|
|
||||||
///
|
|
||||||
/// # Request::example(Method::Get, "/uri", |mut request| {
|
|
||||||
/// request.cookies().add(Cookie::new("key", "val"));
|
|
||||||
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
|
|
||||||
/// # });
|
|
||||||
/// ```
|
|
||||||
pub fn cookies(&self) -> Cookies {
|
|
||||||
// FIXME: Can we do better? This is disappointing.
|
|
||||||
match self.state.cookies.try_borrow_mut() {
|
|
||||||
Ok(jar) => Cookies::new(jar, self.state.config.secret_key()),
|
|
||||||
Err(_) => {
|
|
||||||
error_!("Multiple `Cookies` instances are active at once.");
|
|
||||||
info_!("An instance of `Cookies` must be dropped before another \
|
|
||||||
can be retrieved.");
|
|
||||||
warn_!("The retrieved `Cookies` instance will be empty.");
|
|
||||||
Cookies::empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the Content-Type header of `self`. If the header is not present,
|
/// Returns the Content-Type header of `self`. If the header is not present,
|
||||||
/// returns `None`. The Content-Type header is cached after the first call
|
/// returns `None`. The Content-Type header is cached after the first call
|
||||||
/// to this function. As a result, subsequent calls will always return the
|
/// to this function. As a result, subsequent calls will always return the
|
||||||
|
@ -424,12 +428,11 @@ impl<'r> Request<'r> {
|
||||||
}).as_ref()
|
}).as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the media type "format" of the request.
|
/// Returns the media type "format" of the request if it is present.
|
||||||
///
|
///
|
||||||
/// The "format" of a request is either the Content-Type, if the request
|
/// The "format" of a request is either the Content-Type, if the request
|
||||||
/// methods indicates support for a payload, or the preferred media type in
|
/// methods indicates support for a payload, or the preferred media type in
|
||||||
/// the Accept header otherwise. If the method indicates no payload and no
|
/// the Accept header otherwise.
|
||||||
/// Accept header is specified, a media type of `Any` is returned.
|
|
||||||
///
|
///
|
||||||
/// The media type returned from this method is used to match against the
|
/// The media type returned from this method is used to match against the
|
||||||
/// `format` route attribute.
|
/// `format` route attribute.
|
||||||
|
@ -452,16 +455,13 @@ impl<'r> Request<'r> {
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn format(&self) -> Option<&MediaType> {
|
pub fn format(&self) -> Option<&MediaType> {
|
||||||
static ANY: MediaType = MediaType::Any;
|
|
||||||
if self.method().supports_payload() {
|
if self.method().supports_payload() {
|
||||||
self.content_type().map(|ct| ct.media_type())
|
self.content_type().map(|ct| ct.media_type())
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Should we be using `accept_first` or `preferred`? Or
|
// FIXME: Should we be using `accept_first` or `preferred`? Or
|
||||||
// should we be checking neither and instead pass things through
|
// should we be checking neither and instead pass things through
|
||||||
// where the client accepts the thing at all?
|
// where the client accepts the thing at all?
|
||||||
self.accept()
|
self.accept().map(|accept| accept.preferred().media_type())
|
||||||
.map(|accept| accept.preferred().media_type())
|
|
||||||
.or(Some(&ANY))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,8 +552,8 @@ impl<'r> Request<'r> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and parses into `T` the 0-indexed `n`th dynamic parameter from
|
/// Retrieves and parses into `T` the 0-indexed `n`th segment from the
|
||||||
/// the request. Returns `None` if `n` is greater than the number of params.
|
/// request. Returns `None` if `n` is greater than the number of segments.
|
||||||
/// Returns `Some(Err(T::Error))` if the parameter type `T` failed to be
|
/// Returns `Some(Err(T::Error))` if the parameter type `T` failed to be
|
||||||
/// parsed from the `n`th dynamic parameter.
|
/// parsed from the `n`th dynamic parameter.
|
||||||
///
|
///
|
||||||
|
@ -562,121 +562,221 @@ impl<'r> Request<'r> {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// Retrieve parameter `0`, which is expected to be a `String`, in a manual
|
|
||||||
/// route:
|
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Data};
|
/// # use rocket::{Request, http::Method};
|
||||||
/// use rocket::handler::Outcome;
|
/// use rocket::http::{RawStr, uri::Origin};
|
||||||
///
|
///
|
||||||
/// # #[allow(dead_code)]
|
/// # Request::example(Method::Get, "/", |req| {
|
||||||
/// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
|
/// fn string<'s>(req: &'s mut Request, uri: &'static str, n: usize) -> &'s RawStr {
|
||||||
/// let string = req.get_param::<String>(0)
|
/// req.set_uri(Origin::parse(uri).unwrap());
|
||||||
/// .and_then(|res| res.ok())
|
|
||||||
/// .unwrap_or_else(|| "unnamed".into());
|
|
||||||
///
|
///
|
||||||
/// Outcome::from(req, string)
|
/// req.get_param(n)
|
||||||
|
/// .and_then(|r| r.ok())
|
||||||
|
/// .unwrap_or("unnamed".into())
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(string(req, "/", 0).as_str(), "unnamed");
|
||||||
|
/// assert_eq!(string(req, "/a/b/this_one", 0).as_str(), "a");
|
||||||
|
/// assert_eq!(string(req, "/a/b/this_one", 1).as_str(), "b");
|
||||||
|
/// assert_eq!(string(req, "/a/b/this_one", 2).as_str(), "this_one");
|
||||||
|
/// assert_eq!(string(req, "/a/b/this_one", 3).as_str(), "unnamed");
|
||||||
|
/// assert_eq!(string(req, "/a/b/c/d/e/f/g/h", 7).as_str(), "h");
|
||||||
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn get_param<'a, T>(&'a self, n: usize) -> Option<Result<T, T::Error>>
|
pub fn get_param<'a, T>(&'a self, n: usize) -> Option<Result<T, T::Error>>
|
||||||
where T: FromParam<'a>
|
where T: FromParam<'a>
|
||||||
{
|
{
|
||||||
Some(T::from_param(self.get_param_str(n)?))
|
Some(T::from_param(self.raw_segment_str(n)?))
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the `n`th path parameter as a string, if it exists. This is used by
|
|
||||||
/// codegen.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn get_param_str(&self, n: usize) -> Option<&RawStr> {
|
|
||||||
let params = self.state.params.borrow();
|
|
||||||
if n >= params.len() {
|
|
||||||
debug!("{} is >= param count {}", n, params.len());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (i, j) = params[n];
|
|
||||||
let path = self.uri.path();
|
|
||||||
if j > path.len() {
|
|
||||||
error!("Couldn't retrieve parameter: internal count incorrect.");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(path[i..j].into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and parses into `T` all of the path segments in the request
|
/// Retrieves and parses into `T` all of the path segments in the request
|
||||||
/// URI beginning at the 0-indexed `n`th dynamic parameter. `T` must
|
/// URI beginning and including the 0-indexed `n`th non-empty segment. `T`
|
||||||
/// implement [FromSegments](/rocket/request/trait.FromSegments.html), which
|
/// must implement [FromSegments](/rocket/request/trait.FromSegments.html),
|
||||||
/// is used to parse the segments.
|
/// which is used to parse the segments.
|
||||||
///
|
///
|
||||||
/// This method exists only to be used by manual routing. To retrieve
|
/// This method exists only to be used by manual routing. To retrieve
|
||||||
/// segments from a request, use Rocket's code generation facilities.
|
/// segments from a request, use Rocket's code generation facilities.
|
||||||
///
|
///
|
||||||
/// # Error
|
/// # Error
|
||||||
///
|
///
|
||||||
/// If there are less than `n` segments, returns `None`. If parsing the
|
/// If there are fewer than `n` non-empty segments, returns `None`. If
|
||||||
/// segments failed, returns `Some(Err(T:Error))`.
|
/// parsing the segments failed, returns `Some(Err(T:Error))`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// If the request URI is `"/hello/there/i/am/here"`, and the matched route
|
/// ```rust
|
||||||
/// path for this request is `"/hello/<name>/i/<segs..>"`, then
|
/// # use rocket::{Request, http::Method};
|
||||||
/// `request.get_segments::<T>(1)` will attempt to parse the segments
|
/// use std::path::PathBuf;
|
||||||
/// `"am/here"` as type `T`.
|
///
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// # Request::example(Method::Get, "/", |req| {
|
||||||
|
/// fn path<'s>(req: &'s mut Request, uri: &'static str, n: usize) -> PathBuf {
|
||||||
|
/// req.set_uri(Origin::parse(uri).unwrap());
|
||||||
|
///
|
||||||
|
/// req.get_segments(n)
|
||||||
|
/// .and_then(|r| r.ok())
|
||||||
|
/// .unwrap_or_else(|| "whoops".into())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(path(req, "/", 0), PathBuf::from("whoops"));
|
||||||
|
/// assert_eq!(path(req, "/a/", 0), PathBuf::from("a"));
|
||||||
|
/// assert_eq!(path(req, "/a/b/c", 0), PathBuf::from("a/b/c"));
|
||||||
|
/// assert_eq!(path(req, "/a/b/c", 1), PathBuf::from("b/c"));
|
||||||
|
/// assert_eq!(path(req, "/a/b/c", 2), PathBuf::from("c"));
|
||||||
|
/// assert_eq!(path(req, "/a/b/c", 6), PathBuf::from("whoops"));
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
pub fn get_segments<'a, T>(&'a self, n: usize) -> Option<Result<T, T::Error>>
|
pub fn get_segments<'a, T>(&'a self, n: usize) -> Option<Result<T, T::Error>>
|
||||||
where T: FromSegments<'a>
|
where T: FromSegments<'a>
|
||||||
{
|
{
|
||||||
Some(T::from_segments(self.get_raw_segments(n)?))
|
Some(T::from_segments(self.raw_segments(n)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the segments beginning at the `n`th dynamic parameter, if they
|
/// Retrieves and parses into `T` the query value with key `key`. `T` must
|
||||||
/// exist. Used by codegen.
|
/// implement [`FromFormValue`], which is used to parse the query's value.
|
||||||
#[doc(hidden)]
|
/// Key matching is performed case-sensitively. If there are multiple pairs
|
||||||
pub fn get_raw_segments(&self, n: usize) -> Option<Segments> {
|
/// with key `key`, the _last_ one is returned.
|
||||||
let params = self.state.params.borrow();
|
///
|
||||||
if n >= params.len() {
|
/// This method exists only to be used by manual routing. To retrieve
|
||||||
debug!("{} is >= param (segments) count {}", n, params.len());
|
/// query values from a request, use Rocket's code generation facilities.
|
||||||
return None;
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// If a query segment with key `key` isn't present, returns `None`. If
|
||||||
|
/// parsing the value fails, returns `Some(Err(T:Error))`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket::{Request, http::Method};
|
||||||
|
/// use std::path::PathBuf;
|
||||||
|
/// use rocket::http::{RawStr, uri::Origin};
|
||||||
|
///
|
||||||
|
/// # Request::example(Method::Get, "/", |req| {
|
||||||
|
/// fn value<'s>(req: &'s mut Request, uri: &'static str, key: &str) -> &'s RawStr {
|
||||||
|
/// req.set_uri(Origin::parse(uri).unwrap());
|
||||||
|
///
|
||||||
|
/// req.get_query_value(key)
|
||||||
|
/// .and_then(|r| r.ok())
|
||||||
|
/// .unwrap_or("n/a".into())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(value(req, "/?a=apple&z=zebra", "a").as_str(), "apple");
|
||||||
|
/// assert_eq!(value(req, "/?a=apple&z=zebra", "z").as_str(), "zebra");
|
||||||
|
/// assert_eq!(value(req, "/?a=apple&z=zebra", "A").as_str(), "n/a");
|
||||||
|
/// assert_eq!(value(req, "/?a=apple&z=zebra&a=argon", "a").as_str(), "argon");
|
||||||
|
/// assert_eq!(value(req, "/?a=1&a=2&a=3&b=4", "a").as_str(), "3");
|
||||||
|
/// assert_eq!(value(req, "/?a=apple&z=zebra", "apple").as_str(), "n/a");
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// If the request query is `"/?apple=value_for_a&z=zebra"`, then
|
||||||
|
/// `request.get_query_value::<T>("z")` will attempt to parse `"zebra"` as
|
||||||
|
/// type `T`.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_query_value<'a, T>(&'a self, key: &str) -> Option<Result<T, T::Error>>
|
||||||
|
where T: FromFormValue<'a>
|
||||||
|
{
|
||||||
|
self.raw_query_items()?
|
||||||
|
.rev()
|
||||||
|
.find(|item| item.key.as_str() == key)
|
||||||
|
.map(|item| T::from_form_value(item.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All of these methods only exist for internal, including codegen, purposes.
|
||||||
|
// They _are not_ part of the stable API.
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<'r> Request<'r> {
|
||||||
|
// Only used by doc-tests! Needs to be `pub` because doc-test are external.
|
||||||
|
pub fn example<F: Fn(&mut Request)>(method: Method, uri: &str, f: F) {
|
||||||
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
|
let uri = Origin::parse(uri).expect("invalid URI in example");
|
||||||
|
let mut request = Request::new(&rocket, method, uri);
|
||||||
|
f(&mut request);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (i, j) = params[n];
|
// Updates the cached `path_segments` and `query_items` in `self.state`.
|
||||||
|
// MUST be called whenever a new URI is set or updated.
|
||||||
|
#[inline]
|
||||||
|
fn update_cached_uri_info(&mut self) {
|
||||||
|
let path_segments = Segments(self.uri.path())
|
||||||
|
.map(|s| indices(s, self.uri.path()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let query_items = self.uri.query()
|
||||||
|
.map(|query_str| FormItems::from(query_str)
|
||||||
|
.map(|item| IndexedFormItem::from(query_str, item))
|
||||||
|
.collect()
|
||||||
|
);
|
||||||
|
|
||||||
|
self.state.path_segments = path_segments;
|
||||||
|
self.state.query_items = query_items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the `n`th path segment, 0-indexed, after the mount point for the
|
||||||
|
/// currently matched route, as a string, if it exists. Used by codegen.
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_segment_str(&self, n: usize) -> Option<&RawStr> {
|
||||||
|
self.routed_path_segment(n)
|
||||||
|
.map(|(i, j)| self.uri.path()[i..j].into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the segments beginning at the `n`th, 0-indexed, after the mount
|
||||||
|
/// point for the currently matched route, if they exist. Used by codegen.
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_segments(&self, n: usize) -> Option<Segments> {
|
||||||
|
self.routed_path_segment(n)
|
||||||
|
.map(|(i, _)| Segments(&self.uri.path()[i..]) )
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an iterator over the raw segments of the path URI. Does not take
|
||||||
|
// into account the current route. This is used during routing.
|
||||||
|
#[inline]
|
||||||
|
crate fn raw_path_segments(&self) -> impl Iterator<Item = &RawStr> {
|
||||||
let path = self.uri.path();
|
let path = self.uri.path();
|
||||||
if j > path.len() {
|
self.state.path_segments.iter().cloned()
|
||||||
error!("Couldn't retrieve segments: internal count incorrect.");
|
.map(move |(i, j)| path[i..j].into())
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Segments(&path[i..j]))
|
#[inline]
|
||||||
|
fn routed_path_segment(&self, n: usize) -> Option<(usize, usize)> {
|
||||||
|
let mount_segments = self.route()
|
||||||
|
.map(|r| r.base.segment_count())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
self.state.path_segments.get(mount_segments + n).map(|(i, j)| (*i, *j))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the pre-parsed query items. Used by matching and codegen.
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_query_items(
|
||||||
|
&self
|
||||||
|
) -> Option<impl Iterator<Item = FormItem> + DoubleEndedIterator + Clone> {
|
||||||
|
let query = self.uri.query()?;
|
||||||
|
self.state.query_items.as_ref().map(move |items| {
|
||||||
|
items.iter().map(move |item| item.convert(query))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set `self`'s parameters given that the route used to reach this request
|
/// Set `self`'s parameters given that the route used to reach this request
|
||||||
/// was `route`. This should only be used internally by `Rocket` as improper
|
/// was `route`. Use during routing when attempting a given route.
|
||||||
/// use may result in out of bounds indexing.
|
#[inline(always)]
|
||||||
/// TODO: Figure out the mount path from here.
|
|
||||||
#[inline]
|
|
||||||
crate fn set_route(&self, route: &'r Route) {
|
crate fn set_route(&self, route: &'r Route) {
|
||||||
self.state.route.set(Some(route));
|
self.state.route.set(Some(route));
|
||||||
*self.state.params.borrow_mut() = route.get_param_indexes(self.uri());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the method of `self`, even when `self` is a shared reference.
|
/// Set the method of `self`, even when `self` is a shared reference. Used
|
||||||
|
/// during routing to override methods for re-routing.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
crate fn _set_method(&self, method: Method) {
|
crate fn _set_method(&self, method: Method) {
|
||||||
self.method.set(method);
|
self.method.set(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace all of the cookies in `self` with those in `jar`.
|
|
||||||
#[inline]
|
|
||||||
crate fn set_cookies(&mut self, jar: CookieJar) {
|
|
||||||
self.state.cookies = RefCell::new(jar);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the managed state T, if it exists. For internal use only!
|
|
||||||
#[inline(always)]
|
|
||||||
crate fn get_state<T: Send + Sync + 'static>(&self) -> Option<&'r T> {
|
|
||||||
self.state.managed.try_get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert from Hyper types into a Rocket Request.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
crate fn from_hyp(
|
crate fn from_hyp(
|
||||||
rocket: &'r Rocket,
|
rocket: &'r Rocket,
|
||||||
|
@ -720,7 +820,7 @@ impl<'r> Request<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.set_cookies(cookie_jar);
|
request.state.cookies = RefCell::new(cookie_jar);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the rest of the headers.
|
// Set the rest of the headers.
|
||||||
|
@ -766,3 +866,26 @@ impl<'r> fmt::Display for Request<'r> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IndexedFormItem {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(s: &str, i: FormItem) -> Self {
|
||||||
|
let (r, k, v) = (indices(i.raw, s), indices(i.key, s), indices(i.value, s));
|
||||||
|
IndexedFormItem { raw: r, key: k, value: v }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn convert<'s>(&self, source: &'s str) -> FormItem<'s> {
|
||||||
|
FormItem {
|
||||||
|
raw: source[self.raw.0..self.raw.1].into(),
|
||||||
|
key: source[self.key.0..self.key.1].into(),
|
||||||
|
value: source[self.value.0..self.value.1].into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn indices(needle: &str, haystack: &str) -> (usize, usize) {
|
||||||
|
Indexed::checked_from(needle, haystack)
|
||||||
|
.expect("segments inside of path/query")
|
||||||
|
.indices()
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,7 @@ use http::Status;
|
||||||
/// following example does just this:
|
/// following example does just this:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::State;
|
/// use rocket::State;
|
||||||
///
|
///
|
||||||
|
@ -123,7 +122,7 @@ impl<'a, 'r, T: Send + Sync + 'static> FromRequest<'a, 'r> for State<'r, T> {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from_request(req: &'a Request<'r>) -> request::Outcome<State<'r, T>, ()> {
|
fn from_request(req: &'a Request<'r>) -> request::Outcome<State<'r, T>, ()> {
|
||||||
match req.get_state::<T>() {
|
match req.state.managed.try_get::<T>() {
|
||||||
Some(state) => Outcome::Success(State(state)),
|
Some(state) => Outcome::Success(State(state)),
|
||||||
None => {
|
None => {
|
||||||
error_!("Attempted to retrieve unmanaged state!");
|
error_!("Attempted to retrieve unmanaged state!");
|
||||||
|
|
|
@ -47,11 +47,8 @@ const FLASH_COOKIE_NAME: &str = "_flash";
|
||||||
/// message on both the request and response sides.
|
/// message on both the request and response sides.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// #
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
|
||||||
/// use rocket::response::{Flash, Redirect};
|
/// use rocket::response::{Flash, Redirect};
|
||||||
/// use rocket::request::FlashMessage;
|
/// use rocket::request::FlashMessage;
|
||||||
/// use rocket::http::RawStr;
|
/// use rocket::http::RawStr;
|
||||||
|
|
|
@ -151,9 +151,8 @@ use request::Request;
|
||||||
/// following `Responder` implementation accomplishes this:
|
/// following `Responder` implementation accomplishes this:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(Debug)]
|
/// # #[derive(Debug)]
|
||||||
/// # struct Person { name: String, age: u16 }
|
/// # struct Person { name: String, age: u16 }
|
||||||
|
|
|
@ -185,8 +185,8 @@ impl Rocket {
|
||||||
if is_form && req.method() == Method::Post && data_len >= min_len {
|
if is_form && req.method() == Method::Post && data_len >= min_len {
|
||||||
if let Ok(form) = from_utf8(&data.peek()[..min(data_len, max_len)]) {
|
if let Ok(form) = from_utf8(&data.peek()[..min(data_len, max_len)]) {
|
||||||
let method: Option<Result<Method, _>> = FormItems::from(form)
|
let method: Option<Result<Method, _>> = FormItems::from(form)
|
||||||
.filter(|&(key, _)| key.as_str() == "_method")
|
.filter(|item| item.key.as_str() == "_method")
|
||||||
.map(|(_, value)| value.parse())
|
.map(|item| item.value.parse())
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
if let Some(Ok(method)) = method {
|
if let Some(Ok(method)) = method {
|
||||||
|
@ -455,8 +455,7 @@ impl Rocket {
|
||||||
/// dispatched to the `hi` route.
|
/// dispatched to the `hi` route.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
/// #
|
||||||
/// #[get("/world")]
|
/// #[get("/world")]
|
||||||
|
@ -497,11 +496,6 @@ impl Rocket {
|
||||||
Paint::purple("Mounting"),
|
Paint::purple("Mounting"),
|
||||||
Paint::blue(base));
|
Paint::blue(base));
|
||||||
|
|
||||||
if base.contains('<') || base.contains('>') {
|
|
||||||
error_!("Mount point '{}' contains dynamic paramters.", base);
|
|
||||||
panic!("Invalid mount point.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let base_uri = Origin::parse(base)
|
let base_uri = Origin::parse(base)
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
error_!("Invalid origin URI '{}' used as mount point.", base);
|
error_!("Invalid origin URI '{}' used as mount point.", base);
|
||||||
|
@ -513,22 +507,12 @@ impl Rocket {
|
||||||
panic!("Invalid mount point.");
|
panic!("Invalid mount point.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !base_uri.is_normalized() {
|
|
||||||
error_!("Mount point '{}' is not normalized.", base_uri);
|
|
||||||
info_!("Expected: '{}'.", base_uri.to_normalized());
|
|
||||||
panic!("Invalid mount point.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for mut route in routes.into() {
|
for mut route in routes.into() {
|
||||||
let complete_uri = format!("{}/{}", base_uri, route.uri);
|
let path = route.uri.clone();
|
||||||
let uri = Origin::parse_route(&complete_uri)
|
if let Err(e) = route.set_uri(base_uri.clone(), path) {
|
||||||
.unwrap_or_else(|e| {
|
error_!("{}", e);
|
||||||
error_!("Invalid route URI: {}", base);
|
panic!("Invalid route URI.");
|
||||||
panic!("Error: {}", e)
|
}
|
||||||
});
|
|
||||||
|
|
||||||
route.set_base(base_uri.clone());
|
|
||||||
route.set_uri(uri.to_normalized());
|
|
||||||
|
|
||||||
info_!("{}", route);
|
info_!("{}", route);
|
||||||
self.router.add(route);
|
self.router.add(route);
|
||||||
|
@ -542,11 +526,8 @@ impl Rocket {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
///
|
|
||||||
/// #[macro_use] extern crate rocket;
|
|
||||||
///
|
|
||||||
/// use rocket::Request;
|
/// use rocket::Request;
|
||||||
///
|
///
|
||||||
/// #[catch(500)]
|
/// #[catch(500)]
|
||||||
|
@ -601,8 +582,7 @@ impl Rocket {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::State;
|
/// use rocket::State;
|
||||||
///
|
///
|
||||||
|
@ -639,9 +619,8 @@ impl Rocket {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::Rocket;
|
/// use rocket::Rocket;
|
||||||
/// use rocket::fairing::AdHoc;
|
/// use rocket::fairing::AdHoc;
|
||||||
///
|
///
|
||||||
|
@ -756,8 +735,7 @@ impl Rocket {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro, proc_macro_non_items)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::Rocket;
|
/// use rocket::Rocket;
|
||||||
/// use rocket::fairing::AdHoc;
|
/// use rocket::fairing::AdHoc;
|
||||||
|
@ -813,9 +791,8 @@ impl Rocket {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #![feature(plugin, decl_macro)]
|
/// # #![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
/// # #![plugin(rocket_codegen)]
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::Rocket;
|
/// use rocket::Rocket;
|
||||||
/// use rocket::fairing::AdHoc;
|
/// use rocket::fairing::AdHoc;
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::Route;
|
use super::Route;
|
||||||
|
|
||||||
use http::uri::Origin;
|
|
||||||
use http::MediaType;
|
use http::MediaType;
|
||||||
|
use http::route::Kind;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
|
|
||||||
impl Route {
|
impl Route {
|
||||||
|
@ -15,22 +15,15 @@ impl Route {
|
||||||
/// * If route specifies a format, it only gets requests for that format.
|
/// * If route specifies a format, it only gets requests for that format.
|
||||||
/// * If route doesn't specify a format, it gets requests for any format.
|
/// * If route doesn't specify a format, it gets requests for any format.
|
||||||
///
|
///
|
||||||
/// Query collisions work like this:
|
/// Because query parsing is lenient, and dynamic query parameters can be
|
||||||
///
|
/// missing, queries do not impact whether two routes collide.
|
||||||
/// * If routes specify a query, they only gets request that have queries.
|
|
||||||
/// * If routes don't specify a query, requests with queries also match.
|
|
||||||
///
|
|
||||||
/// As a result, as long as everything else collides, whether a route has a
|
|
||||||
/// query or not is irrelevant: it will collide.
|
|
||||||
pub fn collides_with(&self, other: &Route) -> bool {
|
pub fn collides_with(&self, other: &Route) -> bool {
|
||||||
self.method == other.method
|
self.method == other.method
|
||||||
&& self.rank == other.rank
|
&& self.rank == other.rank
|
||||||
&& paths_collide(&self.uri, &other.uri)
|
&& paths_collide(self, other)
|
||||||
&& match (self.format.as_ref(), other.format.as_ref()) {
|
&& match (self.format.as_ref(), other.format.as_ref()) {
|
||||||
(Some(a), Some(b)) => media_types_collide(a, b),
|
(Some(a), Some(b)) => media_types_collide(a, b),
|
||||||
(Some(_), None) => true,
|
_ => true
|
||||||
(None, Some(_)) => true,
|
|
||||||
(None, None) => true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,21 +36,13 @@ impl Route {
|
||||||
/// - If route doesn't specify format, it gets requests for any format.
|
/// - If route doesn't specify format, it gets requests for any format.
|
||||||
/// * All static components in the route's path match the corresponding
|
/// * All static components in the route's path match the corresponding
|
||||||
/// components in the same position in the incoming request.
|
/// components in the same position in the incoming request.
|
||||||
/// * If the route specifies a query, the request must have a query as
|
|
||||||
/// well. If the route doesn't specify a query, requests with and
|
|
||||||
/// without queries match.
|
|
||||||
///
|
|
||||||
/// In the future, query handling will work as follows:
|
|
||||||
///
|
|
||||||
/// * All static components in the route's query string are also in the
|
/// * All static components in the route's query string are also in the
|
||||||
/// request query string, though in any position, and there exists a
|
/// request query string, though in any position.
|
||||||
/// query parameter named exactly like each non-multi dynamic component
|
|
||||||
/// in the route's query that wasn't matched against a static component.
|
|
||||||
/// - If no query in route, requests with/without queries match.
|
/// - If no query in route, requests with/without queries match.
|
||||||
pub fn matches(&self, req: &Request) -> bool {
|
pub fn matches(&self, req: &Request) -> bool {
|
||||||
self.method == req.method()
|
self.method == req.method()
|
||||||
&& paths_collide(&self.uri, req.uri())
|
&& paths_match(self, req)
|
||||||
&& queries_collide(self, req)
|
&& queries_match(self, req)
|
||||||
&& match self.format {
|
&& match self.format {
|
||||||
Some(ref a) => match req.format() {
|
Some(ref a) => match req.format() {
|
||||||
Some(ref b) => media_types_collide(a, b),
|
Some(ref b) => media_types_collide(a, b),
|
||||||
|
@ -68,51 +53,69 @@ impl Route {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn paths_collide(route: &Route, other: &Route) -> bool {
|
||||||
fn iters_match_until<A, B>(break_c: u8, mut a: A, mut b: B) -> bool
|
let a_segments = &route.metadata.path_segments;
|
||||||
where A: Iterator<Item = u8>, B: Iterator<Item = u8>
|
let b_segments = &other.metadata.path_segments;
|
||||||
{
|
for (seg_a, seg_b) in a_segments.iter().zip(b_segments.iter()) {
|
||||||
loop {
|
if seg_a.kind == Kind::Multi || seg_b.kind == Kind::Multi {
|
||||||
match (a.next(), b.next()) {
|
|
||||||
(None, Some(_)) => return false,
|
|
||||||
(Some(_), None) => return false,
|
|
||||||
(None, None) => return true,
|
|
||||||
(Some(c1), Some(c2)) if c1 == break_c || c2 == break_c => return true,
|
|
||||||
(Some(c1), Some(c2)) if c1 != c2 => return false,
|
|
||||||
(Some(_), Some(_)) => continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn segments_collide(first: &str, other: &str) -> bool {
|
|
||||||
let a_iter = first.as_bytes().iter().cloned();
|
|
||||||
let b_iter = other.as_bytes().iter().cloned();
|
|
||||||
iters_match_until(b'<', a_iter.clone(), b_iter.clone())
|
|
||||||
&& iters_match_until(b'>', a_iter.rev(), b_iter.rev())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paths_collide(first: &Origin, other: &Origin) -> bool {
|
|
||||||
for (seg_a, seg_b) in first.segments().zip(other.segments()) {
|
|
||||||
if seg_a.ends_with("..>") || seg_b.ends_with("..>") {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !segments_collide(seg_a, seg_b) {
|
if seg_a.kind == Kind::Static && seg_b.kind == Kind::Static {
|
||||||
|
if seg_a.string != seg_b.string {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if first.segment_count() != other.segment_count() {
|
a_segments.len() == b_segments.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paths_match(route: &Route, request: &Request) -> bool {
|
||||||
|
let route_segments = &route.metadata.path_segments;
|
||||||
|
if route_segments.len() > request.state.path_segments.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let request_segments = request.raw_path_segments();
|
||||||
|
for (route_seg, req_seg) in route_segments.iter().zip(request_segments) {
|
||||||
|
match route_seg.kind {
|
||||||
|
Kind::Multi => return true,
|
||||||
|
Kind::Static if &*route_seg.string != req_seg.as_str() => return false,
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route_segments.len() == request.state.path_segments.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queries_match(route: &Route, request: &Request) -> bool {
|
||||||
|
if route.metadata.fully_dynamic_query {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let route_query_segments = match route.metadata.query_segments {
|
||||||
|
Some(ref segments) => segments,
|
||||||
|
None => return true
|
||||||
|
};
|
||||||
|
|
||||||
|
let req_query_segments = match request.raw_query_items() {
|
||||||
|
Some(iter) => iter.map(|item| item.raw.as_str()),
|
||||||
|
None => return route.metadata.fully_dynamic_query
|
||||||
|
};
|
||||||
|
|
||||||
|
for seg in route_query_segments.iter() {
|
||||||
|
if seg.kind == Kind::Static {
|
||||||
|
// it's okay; this clones the iterator
|
||||||
|
if !req_query_segments.clone().any(|r| r == seg.string) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queries_collide(route: &Route, req: &Request) -> bool {
|
|
||||||
route.uri.query().map_or(true, |_| req.uri().query().is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn media_types_collide(first: &MediaType, other: &MediaType) -> bool {
|
fn media_types_collide(first: &MediaType, other: &MediaType) -> bool {
|
||||||
let collide = |a, b| a == "*" || b == "*" || a == b;
|
let collide = |a, b| a == "*" || b == "*" || a == b;
|
||||||
collide(first.top(), other.top()) && collide(first.sub(), other.sub())
|
collide(first.top(), other.top()) && collide(first.sub(), other.sub())
|
||||||
|
@ -134,20 +137,20 @@ mod tests {
|
||||||
type SimpleRoute = (Method, &'static str);
|
type SimpleRoute = (Method, &'static str);
|
||||||
|
|
||||||
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {
|
||||||
let route_a = Route::new(a.0, a.1.to_string(), dummy_handler);
|
let route_a = Route::new(a.0, a.1, dummy_handler);
|
||||||
route_a.collides_with(&Route::new(b.0, b.1.to_string(), dummy_handler))
|
route_a.collides_with(&Route::new(b.0, b.1, dummy_handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
fn unranked_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
let route_a = Route::ranked(0, Get, a.to_string(), dummy_handler);
|
let route_a = Route::ranked(0, Get, a, dummy_handler);
|
||||||
let route_b = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
let route_b = Route::ranked(0, Get, b, dummy_handler);
|
||||||
eprintln!("Checking {} against {}.", route_a, route_b);
|
eprintln!("Checking {} against {}.", route_a, route_b);
|
||||||
route_a.collides_with(&route_b)
|
route_a.collides_with(&route_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
fn s_s_collide(a: &'static str, b: &'static str) -> bool {
|
||||||
let a = Origin::parse_route(a).unwrap();
|
let a = Route::new(Get, a, dummy_handler);
|
||||||
let b = Origin::parse_route(b).unwrap();
|
let b = Route::new(Get, b, dummy_handler);
|
||||||
paths_collide(&a, &b)
|
paths_collide(&a, &b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,15 +190,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hard_param_collisions() {
|
fn hard_param_collisions() {
|
||||||
assert!(unranked_collide("/<name>bob", "/<name>b"));
|
|
||||||
assert!(unranked_collide("/a<b>c", "/abc"));
|
|
||||||
assert!(unranked_collide("/a<b>c", "/azooc"));
|
|
||||||
assert!(unranked_collide("/a<b>", "/ab"));
|
|
||||||
assert!(unranked_collide("/<b>", "/a"));
|
|
||||||
assert!(unranked_collide("/<a>/<b>", "/a/b<c>"));
|
|
||||||
assert!(unranked_collide("/<a>/bc<b>", "/a/b<c>"));
|
|
||||||
assert!(unranked_collide("/<a>/bc<b>d", "/a/b<c>"));
|
|
||||||
assert!(unranked_collide("/<a..>", "///a///"));
|
assert!(unranked_collide("/<a..>", "///a///"));
|
||||||
|
assert!(unranked_collide("/<a..>", "//a/bcjdklfj//<c>"));
|
||||||
|
assert!(unranked_collide("/a/<a..>", "//a/bcjdklfj//<c>"));
|
||||||
|
assert!(unranked_collide("/a/<b>/<c..>", "//a/bcjdklfj//<c>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -222,12 +220,7 @@ mod tests {
|
||||||
assert!(!unranked_collide("/a/hello", "/a/c"));
|
assert!(!unranked_collide("/a/hello", "/a/c"));
|
||||||
assert!(!unranked_collide("/hello", "/a/c"));
|
assert!(!unranked_collide("/hello", "/a/c"));
|
||||||
assert!(!unranked_collide("/hello/there", "/hello/there/guy"));
|
assert!(!unranked_collide("/hello/there", "/hello/there/guy"));
|
||||||
assert!(!unranked_collide("/b<a>/there", "/hi/there"));
|
|
||||||
assert!(!unranked_collide("/<a>/<b>c", "/hi/person"));
|
|
||||||
assert!(!unranked_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
||||||
assert!(!unranked_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
||||||
assert!(!unranked_collide("/a/<b>", "/b/<b>"));
|
assert!(!unranked_collide("/a/<b>", "/b/<b>"));
|
||||||
assert!(!unranked_collide("/a<a>/<b>", "/b/<b>"));
|
|
||||||
assert!(!unranked_collide("/<a..>", "/"));
|
assert!(!unranked_collide("/<a..>", "/"));
|
||||||
assert!(!unranked_collide("/hi/<a..>", "/hi"));
|
assert!(!unranked_collide("/hi/<a..>", "/hi"));
|
||||||
assert!(!unranked_collide("/hi/<a..>", "/hi/"));
|
assert!(!unranked_collide("/hi/<a..>", "/hi/"));
|
||||||
|
@ -272,36 +265,21 @@ mod tests {
|
||||||
assert!(!s_s_collide("/a/hello", "/a/c"));
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello", "/a/c"));
|
assert!(!s_s_collide("/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
||||||
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
||||||
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
|
||||||
assert!(!s_s_collide("/a", "/b"));
|
assert!(!s_s_collide("/a", "/b"));
|
||||||
assert!(!s_s_collide("/a/b", "/a"));
|
assert!(!s_s_collide("/a/b", "/a"));
|
||||||
assert!(!s_s_collide("/a/b", "/a/c"));
|
assert!(!s_s_collide("/a/b", "/a/c"));
|
||||||
assert!(!s_s_collide("/a/hello", "/a/c"));
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello", "/a/c"));
|
assert!(!s_s_collide("/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
||||||
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
||||||
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
|
||||||
assert!(!s_s_collide("/a", "/b"));
|
assert!(!s_s_collide("/a", "/b"));
|
||||||
assert!(!s_s_collide("/a/b", "/a"));
|
assert!(!s_s_collide("/a/b", "/a"));
|
||||||
assert!(!s_s_collide("/a/b", "/a/c"));
|
assert!(!s_s_collide("/a/b", "/a/c"));
|
||||||
assert!(!s_s_collide("/a/hello", "/a/c"));
|
assert!(!s_s_collide("/a/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello", "/a/c"));
|
assert!(!s_s_collide("/hello", "/a/c"));
|
||||||
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
assert!(!s_s_collide("/hello/there", "/hello/there/guy"));
|
||||||
assert!(!s_s_collide("/b<a>/there", "/hi/there"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>c", "/hi/person"));
|
|
||||||
assert!(!s_s_collide("/<a>/<b>cd", "/hi/<a>e"));
|
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b<b>/<a>"));
|
|
||||||
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
assert!(!s_s_collide("/a/<b>", "/b/<b>"));
|
||||||
assert!(!s_s_collide("/a<a>/<b>", "/b/<b>"));
|
|
||||||
assert!(!s_s_collide("/<a..>", "/"));
|
assert!(!s_s_collide("/<a..>", "/"));
|
||||||
assert!(!s_s_collide("/hi/<a..>", "/hi/"));
|
assert!(!s_s_collide("/hi/<a..>", "/hi/"));
|
||||||
assert!(!s_s_collide("/a/hi/<a..>", "/a/hi/"));
|
assert!(!s_s_collide("/a/hi/<a..>", "/a/hi/"));
|
||||||
|
@ -432,7 +410,7 @@ mod tests {
|
||||||
assert!(!req_route_mt_collide(Post, None, "application/json"));
|
assert!(!req_route_mt_collide(Post, None, "application/json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn req_route_path_collide(a: &'static str, b: &'static str) -> bool {
|
fn req_route_path_match(a: &'static str, b: &'static str) -> bool {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
|
||||||
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
|
||||||
|
@ -441,21 +419,37 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_req_route_query_collisions() {
|
fn test_req_route_query_collisions() {
|
||||||
assert!(req_route_path_collide("/a/b?a=b", "/a/b?<c>"));
|
assert!(req_route_path_match("/a/b?a=b", "/a/b?<c>"));
|
||||||
assert!(req_route_path_collide("/a/b?a=b", "/<a>/b?<c>"));
|
assert!(req_route_path_match("/a/b?a=b", "/<a>/b?<c>"));
|
||||||
assert!(req_route_path_collide("/a/b?a=b", "/<a>/<b>?<c>"));
|
assert!(req_route_path_match("/a/b?a=b", "/<a>/<b>?<c>"));
|
||||||
assert!(req_route_path_collide("/a/b?a=b", "/a/<b>?<c>"));
|
assert!(req_route_path_match("/a/b?a=b", "/a/<b>?<c>"));
|
||||||
assert!(req_route_path_collide("/?b=c", "/?<b>"));
|
assert!(req_route_path_match("/?b=c", "/?<b>"));
|
||||||
|
|
||||||
assert!(req_route_path_collide("/a/b?a=b", "/a/b"));
|
assert!(req_route_path_match("/a/b?a=b", "/a/b"));
|
||||||
assert!(req_route_path_collide("/a/b", "/a/b"));
|
assert!(req_route_path_match("/a/b", "/a/b"));
|
||||||
assert!(req_route_path_collide("/a/b/c/d?", "/a/b/c/d"));
|
assert!(req_route_path_match("/a/b/c/d?", "/a/b/c/d"));
|
||||||
assert!(req_route_path_collide("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
|
assert!(req_route_path_match("/a/b/c/d?v=1&v=2", "/a/b/c/d"));
|
||||||
|
|
||||||
assert!(!req_route_path_collide("/a/b", "/a/b?<c>"));
|
assert!(req_route_path_match("/a/b", "/a/b?<c>"));
|
||||||
assert!(!req_route_path_collide("/a/b/c", "/a/b?<c>"));
|
assert!(req_route_path_match("/a/b", "/a/b?<c..>"));
|
||||||
assert!(!req_route_path_collide("/a?b=c", "/a/b?<c>"));
|
assert!(req_route_path_match("/a/b?c", "/a/b?c"));
|
||||||
assert!(!req_route_path_collide("/?b=c", "/a/b?<c>"));
|
assert!(req_route_path_match("/a/b?c", "/a/b?<c>"));
|
||||||
assert!(!req_route_path_collide("/?b=c", "/a?<c>"));
|
assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?<c>"));
|
||||||
|
assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?<c..>"));
|
||||||
|
|
||||||
|
assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?c=foo&<c..>"));
|
||||||
|
assert!(req_route_path_match("/a/b?c=foo&d=z", "/a/b?d=z&<c..>"));
|
||||||
|
|
||||||
|
assert!(!req_route_path_match("/a/b/c", "/a/b?<c>"));
|
||||||
|
assert!(!req_route_path_match("/a?b=c", "/a/b?<c>"));
|
||||||
|
assert!(!req_route_path_match("/?b=c", "/a/b?<c>"));
|
||||||
|
assert!(!req_route_path_match("/?b=c", "/a?<c>"));
|
||||||
|
|
||||||
|
assert!(!req_route_path_match("/a/b?c=foo&d=z", "/a/b?a=b&<c..>"));
|
||||||
|
assert!(!req_route_path_match("/a/b?c=foo&d=z", "/a/b?d=b&<c..>"));
|
||||||
|
assert!(!req_route_path_match("/a/b", "/a/b?c"));
|
||||||
|
assert!(!req_route_path_match("/a/b", "/a/b?foo"));
|
||||||
|
assert!(!req_route_path_match("/a/b", "/a/b?foo&<rest..>"));
|
||||||
|
assert!(!req_route_path_match("/a/b", "/a/b?<a>&b&<rest..>"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ crate fn dummy_handler<'r>(r: &'r ::Request, _: ::Data) -> ::handler::Outcome<'r
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Router {
|
pub struct Router {
|
||||||
routes: HashMap<Selector, Vec<Route>>, // using 'selector' for now
|
routes: HashMap<Selector, Vec<Route>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Router {
|
impl Router {
|
||||||
|
@ -29,7 +29,9 @@ impl Router {
|
||||||
pub fn add(&mut self, route: Route) {
|
pub fn add(&mut self, route: Route) {
|
||||||
let selector = route.method;
|
let selector = route.method;
|
||||||
let entries = self.routes.entry(selector).or_insert_with(|| vec![]);
|
let entries = self.routes.entry(selector).or_insert_with(|| vec![]);
|
||||||
let i = entries.binary_search_by_key(&route.rank, |r| r.rank).unwrap_or_else(|i| i);
|
let i = entries.binary_search_by_key(&route.rank, |r| r.rank)
|
||||||
|
.unwrap_or_else(|i| i);
|
||||||
|
|
||||||
entries.insert(i, route);
|
entries.insert(i, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +181,19 @@ mod test {
|
||||||
assert!(unranked_route_collisions(&["/a//<a..>//", "/a/b//c//d/e/"]));
|
assert!(unranked_route_collisions(&["/a//<a..>//", "/a/b//c//d/e/"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collisions_query() {
|
||||||
|
// Query shouldn't affect things when unranked.
|
||||||
|
assert!(unranked_route_collisions(&["/hello?<foo>", "/hello"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>?foo=bar", "/hello?foo=bar&cat=fat"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>?foo=bar", "/hello?foo=bar&cat=fat"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<a>", "/<b>?<foo>"]));
|
||||||
|
assert!(unranked_route_collisions(&["/hello/bob?a=b", "/hello/<b>?d=e"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<foo>?a=b", "/foo?d=e"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<foo>?a=b&<c>", "/<foo>?d=e&<c>"]));
|
||||||
|
assert!(unranked_route_collisions(&["/<foo>?a=b&<c>", "/<foo>?d=e"]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_collisions() {
|
fn test_no_collisions() {
|
||||||
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
|
assert!(!unranked_route_collisions(&["/<a>", "/a/<a..>"]));
|
||||||
|
@ -198,6 +213,20 @@ mod test {
|
||||||
assert!(!default_rank_route_collisions(&["/a/b", "/a/b/<c..>"]));
|
assert!(!default_rank_route_collisions(&["/a/b", "/a/b/<c..>"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collision_when_ranked_query() {
|
||||||
|
assert!(default_rank_route_collisions(&["/a?a=b", "/a?c=d"]));
|
||||||
|
assert!(default_rank_route_collisions(&["/<foo>?a=b", "/<foo>?c=d&<d>"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_collision_when_ranked_query() {
|
||||||
|
assert!(!default_rank_route_collisions(&["/", "/?<c..>"]));
|
||||||
|
assert!(!default_rank_route_collisions(&["/hi", "/hi?<c>"]));
|
||||||
|
assert!(!default_rank_route_collisions(&["/hi", "/hi?c"]));
|
||||||
|
assert!(!default_rank_route_collisions(&["/hi?<c>", "/hi?c"]));
|
||||||
|
}
|
||||||
|
|
||||||
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
|
||||||
let rocket = Rocket::custom(Config::development().unwrap());
|
let rocket = Rocket::custom(Config::development().unwrap());
|
||||||
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
|
||||||
|
@ -293,13 +322,17 @@ mod test {
|
||||||
assert_ranked_routes!(&["/<a>/<b>", "/hi/a"], "/hi/c", "/<a>/<b>");
|
assert_ranked_routes!(&["/<a>/<b>", "/hi/a"], "/hi/c", "/<a>/<b>");
|
||||||
assert_ranked_routes!(&["/hi/a", "/hi/<c>"], "/hi/c", "/hi/<c>");
|
assert_ranked_routes!(&["/hi/a", "/hi/<c>"], "/hi/c", "/hi/<c>");
|
||||||
assert_ranked_routes!(&["/a", "/a?<b>"], "/a?b=c", "/a?<b>");
|
assert_ranked_routes!(&["/a", "/a?<b>"], "/a?b=c", "/a?<b>");
|
||||||
assert_ranked_routes!(&["/a", "/a?<b>"], "/a", "/a");
|
assert_ranked_routes!(&["/a", "/a?<b>"], "/a", "/a?<b>");
|
||||||
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/a", "/a");
|
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/a", "/a?<b>");
|
||||||
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/b", "/<a>");
|
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/b", "/<a>?<b>");
|
||||||
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"],
|
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/b?v=1", "/<a>?<b>");
|
||||||
"/b?v=1", "/<a>?<b>");
|
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"], "/a?b=c", "/a?<b>");
|
||||||
assert_ranked_routes!(&["/a", "/<a>", "/a?<b>", "/<a>?<b>"],
|
assert_ranked_routes!(&["/a", "/a?b"], "/a?b", "/a?b");
|
||||||
"/a?b=c", "/a?<b>");
|
assert_ranked_routes!(&["/<a>", "/a?b"], "/a?b", "/a?b");
|
||||||
|
assert_ranked_routes!(&["/a", "/<a>?b"], "/a?b", "/a");
|
||||||
|
assert_ranked_routes!(&["/a?<c>&b", "/a?<b>"], "/a", "/a?<b>");
|
||||||
|
assert_ranked_routes!(&["/a?<c>&b", "/a?<b>"], "/a?b", "/a?<c>&b");
|
||||||
|
assert_ranked_routes!(&["/a?<c>&b", "/a?<b>"], "/a?c", "/a?<b>");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ranked_collisions(routes: &[(isize, &'static str)]) -> bool {
|
fn ranked_collisions(routes: &[(isize, &'static str)]) -> bool {
|
||||||
|
@ -431,50 +464,13 @@ mod test {
|
||||||
assert_default_ranked_routing!(
|
assert_default_ranked_routing!(
|
||||||
to: "/a/b",
|
to: "/a/b",
|
||||||
with: ["/a/<b>", "/a/b", "/a/b?<v>", "/a/<b>?<v>"],
|
with: ["/a/<b>", "/a/b", "/a/b?<v>", "/a/<b>?<v>"],
|
||||||
expect: "/a/b", "/a/<b>"
|
expect: "/a/b?<v>", "/a/b", "/a/<b>?<v>", "/a/<b>"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_default_ranked_routing!(
|
||||||
|
to: "/a/b?c",
|
||||||
|
with: ["/a/b", "/a/b?<c>", "/a/b?c", "/a/<b>?c", "/a/<b>?<c>", "/<a>/<b>"],
|
||||||
|
expect: "/a/b?c", "/a/b?<c>", "/a/b", "/a/<b>?c", "/a/<b>?<c>", "/<a>/<b>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_params(router: &Router, path: &str, expected: &[&str]) -> bool {
|
|
||||||
println!("Testing: {} (expect: {:?})", path, expected);
|
|
||||||
route(router, Get, path).map_or(false, |route| {
|
|
||||||
let uri = Origin::parse_route(path).unwrap();
|
|
||||||
let params = route.get_param_indexes(&uri);
|
|
||||||
if params.len() != expected.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k, (i, j)) in params.into_iter().enumerate() {
|
|
||||||
if &path[i..j] != expected[k] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_params() {
|
|
||||||
let router = router_with_routes(&["/<a>"]);
|
|
||||||
assert!(match_params(&router, "/hello", &["hello"]));
|
|
||||||
assert!(match_params(&router, "/hi", &["hi"]));
|
|
||||||
assert!(match_params(&router, "/bob", &["bob"]));
|
|
||||||
assert!(match_params(&router, "/i", &["i"]));
|
|
||||||
|
|
||||||
let router = router_with_routes(&["/hello"]);
|
|
||||||
assert!(match_params(&router, "/hello", &[]));
|
|
||||||
|
|
||||||
let router = router_with_routes(&["/<a>/<b>"]);
|
|
||||||
assert!(match_params(&router, "/a/b", &["a", "b"]));
|
|
||||||
assert!(match_params(&router, "/912/sas", &["912", "sas"]));
|
|
||||||
|
|
||||||
let router = router_with_routes(&["/hello/<b>"]);
|
|
||||||
assert!(match_params(&router, "/hello/b", &["b"]));
|
|
||||||
assert!(match_params(&router, "/hello/sergio", &["sergio"]));
|
|
||||||
|
|
||||||
let router = router_with_routes(&["/hello/<b>/age"]);
|
|
||||||
assert!(match_params(&router, "/hello/sergio/age", &["sergio"]));
|
|
||||||
assert!(match_params(&router, "/hello/you/age", &["you"]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt;
|
use std::fmt::{self, Display};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
|
||||||
use yansi::Color::*;
|
use yansi::Color::*;
|
||||||
|
@ -6,8 +6,9 @@ use yansi::Color::*;
|
||||||
use codegen::StaticRouteInfo;
|
use codegen::StaticRouteInfo;
|
||||||
use handler::Handler;
|
use handler::Handler;
|
||||||
use http::{Method, MediaType};
|
use http::{Method, MediaType};
|
||||||
|
use http::route::{RouteSegment, Kind, Error as SegmentError};
|
||||||
use http::ext::IntoOwned;
|
use http::ext::IntoOwned;
|
||||||
use http::uri::Origin;
|
use http::uri::{self, Origin};
|
||||||
|
|
||||||
/// A route: a method, its handler, path, rank, and format/media type.
|
/// A route: a method, its handler, path, rank, and format/media type.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -27,71 +28,124 @@ pub struct Route {
|
||||||
pub rank: isize,
|
pub rank: isize,
|
||||||
/// The media type this route matches against, if any.
|
/// The media type this route matches against, if any.
|
||||||
pub format: Option<MediaType>,
|
pub format: Option<MediaType>,
|
||||||
|
/// Cached metadata that aids in routing later.
|
||||||
|
crate metadata: Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
crate struct Metadata {
|
||||||
|
crate path_segments: Vec<RouteSegment<'static>>,
|
||||||
|
crate query_segments: Option<Vec<RouteSegment<'static>>>,
|
||||||
|
crate fully_dynamic_query: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
fn from(route: &Route) -> Result<Metadata, RouteUriError> {
|
||||||
|
let path_segments = RouteSegment::parse_path(&route.uri)
|
||||||
|
.map(|res| res.map(|s| s.into_owned()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let (query_segments, dyn) = match RouteSegment::parse_query(&route.uri) {
|
||||||
|
Some(results) => {
|
||||||
|
let segments = results.map(|res| res.map(|s| s.into_owned()))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let dynamic = !segments.iter().any(|s| s.kind == Kind::Static);
|
||||||
|
|
||||||
|
(Some(segments), dynamic)
|
||||||
|
}
|
||||||
|
None => (None, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Metadata { path_segments, query_segments, fully_dynamic_query: dyn })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_rank(uri: &Origin) -> isize {
|
fn default_rank(route: &Route) -> isize {
|
||||||
// static path, query = -4; static path, no query = -3
|
let static_path = route.metadata.path_segments.iter().all(|s| s.kind == Kind::Static);
|
||||||
// dynamic path, query = -2; dynamic path, no query = -1
|
let partly_static_query = route.uri.query().map(|_| !route.metadata.fully_dynamic_query);
|
||||||
match (!uri.path().contains('<'), uri.query().is_some()) {
|
match (static_path, partly_static_query) {
|
||||||
(true, true) => -4,
|
(true, Some(true)) => -6, // static path, partly static query
|
||||||
(true, false) => -3,
|
(true, Some(false)) => -5, // static path, fully dynamic query
|
||||||
(false, true) => -2,
|
(true, None) => -4, // static path, no query
|
||||||
(false, false) => -1,
|
(false, Some(true)) => -3, // dynamic path, partly static query
|
||||||
|
(false, Some(false)) => -2, // dynamic path, fully dynamic query
|
||||||
|
(false, None) => -1, // dynamic path, no query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn panic<U: Display, E: Display, T>(uri: U, e: E) -> T {
|
||||||
|
panic!("invalid URI '{}' used to construct route: {}", uri, e)
|
||||||
|
}
|
||||||
|
|
||||||
impl Route {
|
impl Route {
|
||||||
/// Creates a new route with the given method, path, and handler with a base
|
/// Creates a new route with the given method, path, and handler with a base
|
||||||
/// of `/`.
|
/// of `/`.
|
||||||
///
|
///
|
||||||
/// # Ranking
|
/// # Ranking
|
||||||
///
|
///
|
||||||
/// The route's rank is set so that routes with static paths are ranked
|
/// The route's rank is set so that routes with static paths (no dynamic
|
||||||
/// higher than routes with dynamic paths, and routes with query strings
|
/// parameters) are ranked higher than routes with dynamic paths, routes
|
||||||
/// are ranked higher than routes without query strings. This default ranking
|
/// with query strings with static segments are ranked higher than routes
|
||||||
/// is summarized by the table below:
|
/// with fully dynamic queries, and routes with queries are ranked higher
|
||||||
|
/// than routes without queries. This default ranking is summarized by the
|
||||||
|
/// table below:
|
||||||
///
|
///
|
||||||
/// | static path | query | rank |
|
/// | static path | query | rank |
|
||||||
/// |-------------|-------|------|
|
/// |-------------|---------------|------|
|
||||||
/// | yes | yes | -4 |
|
/// | yes | partly static | -6 |
|
||||||
/// | yes | no | -3 |
|
/// | yes | fully dynamic | -5 |
|
||||||
/// | no | yes | -2 |
|
/// | yes | none | -4 |
|
||||||
/// | no | no | -1 |
|
/// | no | partly static | -3 |
|
||||||
|
/// | no | fully dynamic | -2 |
|
||||||
|
/// | no | none | -1 |
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::Route;
|
||||||
/// use rocket::handler::Outcome;
|
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
|
/// # use rocket::{Request, Data};
|
||||||
|
/// # use rocket::handler::Outcome;
|
||||||
|
/// # fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
|
/// # Outcome::from(request, "Hello, world!")
|
||||||
|
/// # }
|
||||||
///
|
///
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// // this is rank -6 (static path, ~static query)
|
||||||
/// Outcome::from(request, "Hello, world!")
|
/// let route = Route::new(Method::Get, "/foo?bar=baz&<zoo>", handler);
|
||||||
/// }
|
/// assert_eq!(route.rank, -6);
|
||||||
///
|
///
|
||||||
/// // this is a rank -3 route matching requests to `GET /`
|
/// // this is rank -5 (static path, fully dynamic query)
|
||||||
/// let index = Route::new(Method::Get, "/", handler);
|
/// let route = Route::new(Method::Get, "/foo?<zoo..>", handler);
|
||||||
|
/// assert_eq!(route.rank, -5);
|
||||||
///
|
///
|
||||||
/// // this is a rank -4 route matching requests to `GET /?<name>`
|
/// // this is a rank -4 route (static path, no query)
|
||||||
/// let index_name = Route::new(Method::Get, "/?<name>", handler);
|
/// let route = Route::new(Method::Get, "/", handler);
|
||||||
|
/// assert_eq!(route.rank, -4);
|
||||||
///
|
///
|
||||||
/// // this is a rank -1 route matching requests to `GET /<name>`
|
/// // this is a rank -3 route (dynamic path, ~static query)
|
||||||
/// let name = Route::new(Method::Get, "/<name>", handler);
|
/// let route = Route::new(Method::Get, "/foo/<bar>?blue", handler);
|
||||||
|
/// assert_eq!(route.rank, -3);
|
||||||
|
///
|
||||||
|
/// // this is a rank -2 route (dynamic path, fully dynamic query)
|
||||||
|
/// let route = Route::new(Method::Get, "/<bar>?<blue>", handler);
|
||||||
|
/// assert_eq!(route.rank, -2);
|
||||||
|
///
|
||||||
|
/// // this is a rank -1 route (dynamic path, no query)
|
||||||
|
/// let route = Route::new(Method::Get, "/<bar>/foo/<baz..>", handler);
|
||||||
|
/// assert_eq!(route.rank, -1);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `path` is not a valid origin URI.
|
/// Panics if `path` is not a valid origin URI or Rocket route URI.
|
||||||
pub fn new<S, H>(method: Method, path: S, handler: H) -> Route
|
pub fn new<S, H>(method: Method, path: S, handler: H) -> Route
|
||||||
where S: AsRef<str>, H: Handler + 'static
|
where S: AsRef<str>, H: Handler + 'static
|
||||||
{
|
{
|
||||||
let path = path.as_ref();
|
let mut route = Route::ranked(0, method, path, handler);
|
||||||
let origin = Origin::parse_route(path)
|
route.rank = default_rank(&route);
|
||||||
.expect("invalid URI used as route path in `Route::new()`");
|
route
|
||||||
|
|
||||||
let rank = default_rank(&origin);
|
|
||||||
Route::ranked(rank, method, path, handler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new route with the given rank, method, path, and handler with
|
/// Creates a new route with the given rank, method, path, and handler with
|
||||||
|
@ -100,13 +154,13 @@ impl Route {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::Route;
|
||||||
/// use rocket::handler::Outcome;
|
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
///
|
/// # use rocket::{Request, Data};
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// # use rocket::handler::Outcome;
|
||||||
/// Outcome::from(request, "Hello, world!")
|
/// # fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
/// }
|
/// # Outcome::from(request, "Hello, world!")
|
||||||
|
/// # }
|
||||||
///
|
///
|
||||||
/// // this is a rank 1 route matching requests to `GET /`
|
/// // this is a rank 1 route matching requests to `GET /`
|
||||||
/// let index = Route::ranked(1, Method::Get, "/", handler);
|
/// let index = Route::ranked(1, Method::Get, "/", handler);
|
||||||
|
@ -114,21 +168,35 @@ impl Route {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `path` is not a valid origin URI.
|
/// Panics if `path` is not a valid origin URI or Rocket route URI.
|
||||||
pub fn ranked<S, H>(rank: isize, method: Method, path: S, handler: H) -> Route
|
pub fn ranked<S, H>(rank: isize, method: Method, path: S, handler: H) -> Route
|
||||||
where S: AsRef<str>, H: Handler + 'static
|
where S: AsRef<str>, H: Handler + 'static
|
||||||
{
|
{
|
||||||
let uri = Origin::parse_route(path.as_ref())
|
let path = path.as_ref();
|
||||||
.expect("invalid URI used as route path in `Route::ranked()`")
|
let uri = Origin::parse_route(path)
|
||||||
|
.unwrap_or_else(|e| panic(path, e))
|
||||||
|
.to_normalized()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
Route {
|
let mut route = Route {
|
||||||
name: None,
|
name: None,
|
||||||
format: None,
|
format: None,
|
||||||
base: Origin::dummy(),
|
base: Origin::dummy(),
|
||||||
handler: Box::new(handler),
|
handler: Box::new(handler),
|
||||||
|
metadata: Metadata::default(),
|
||||||
method, rank, uri
|
method, rank, uri
|
||||||
|
};
|
||||||
|
|
||||||
|
route.update_metadata().unwrap_or_else(|e| panic(path, e));
|
||||||
|
route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the cached routing metadata. MUST be called whenver the route's
|
||||||
|
/// URI is set or changes.
|
||||||
|
fn update_metadata(&mut self) -> Result<(), RouteUriError> {
|
||||||
|
let new_metadata = Metadata::from(&*self)?;
|
||||||
|
self.metadata = new_metadata;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the path of the base mount point of this route as an `&str`.
|
/// Retrieves the path of the base mount point of this route as an `&str`.
|
||||||
|
@ -136,15 +204,16 @@ impl Route {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::Route;
|
||||||
/// use rocket::handler::Outcome;
|
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
|
/// # use rocket::{Request, Data};
|
||||||
|
/// # use rocket::handler::Outcome;
|
||||||
|
/// #
|
||||||
|
/// # fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
|
/// # Outcome::from(request, "Hello, world!")
|
||||||
|
/// # }
|
||||||
///
|
///
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// let mut index = Route::new(Method::Get, "/", handler);
|
||||||
/// Outcome::from(request, "Hello, world!")
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let mut index = Route::ranked(1, Method::Get, "/", handler);
|
|
||||||
/// assert_eq!(index.base(), "/");
|
/// assert_eq!(index.base(), "/");
|
||||||
/// assert_eq!(index.base.path(), "/");
|
/// assert_eq!(index.base.path(), "/");
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -153,80 +222,95 @@ impl Route {
|
||||||
self.base.path()
|
self.base.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the base mount point of the route. Does not update the rank or any
|
/// Sets the base mount point of the route to `base` and sets the path to
|
||||||
/// other parameters. If `path` contains a query, it is ignored.
|
/// `path`. The `path` should _not_ contains the `base` mount point. If
|
||||||
|
/// `base` contains a query, it is ignored. Note that `self.uri` will
|
||||||
|
/// include the new `base` after this method is called.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if any of the following occur:
|
||||||
|
///
|
||||||
|
/// * The base mount point contains dynamic parameters.
|
||||||
|
/// * The base mount point or path contain encoded characters.
|
||||||
|
/// * The path is not a valid Rocket route URI.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::{Request, Route, Data};
|
/// use rocket::Route;
|
||||||
/// use rocket::http::{Method, uri::Origin};
|
/// use rocket::http::{Method, uri::Origin};
|
||||||
/// use rocket::handler::Outcome;
|
/// # use rocket::{Request, Data};
|
||||||
|
/// # use rocket::handler::Outcome;
|
||||||
|
/// #
|
||||||
|
/// # fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
||||||
|
/// # Outcome::from(request, "Hello, world!")
|
||||||
|
/// # }
|
||||||
///
|
///
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
/// let mut index = Route::new(Method::Get, "/", handler);
|
||||||
/// Outcome::from(request, "Hello, world!")
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let mut index = Route::ranked(1, Method::Get, "/", handler);
|
|
||||||
/// assert_eq!(index.base(), "/");
|
/// assert_eq!(index.base(), "/");
|
||||||
/// assert_eq!(index.base.path(), "/");
|
/// assert_eq!(index.base.path(), "/");
|
||||||
///
|
///
|
||||||
/// index.set_base(Origin::parse("/hi").unwrap());
|
/// let new_base = Origin::parse("/greeting").unwrap();
|
||||||
/// assert_eq!(index.base(), "/hi");
|
/// let new_uri = Origin::parse("/hi").unwrap();
|
||||||
/// assert_eq!(index.base.path(), "/hi");
|
/// index.set_uri(new_base, new_uri);
|
||||||
|
/// assert_eq!(index.base(), "/greeting");
|
||||||
|
/// assert_eq!(index.uri.path(), "/greeting/hi");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_base<'a>(&mut self, path: Origin<'a>) {
|
pub fn set_uri<'a>(
|
||||||
self.base = path.into_owned();
|
&mut self,
|
||||||
self.base.clear_query();
|
mut base: Origin<'a>,
|
||||||
}
|
path: Origin<'a>
|
||||||
|
) -> Result<(), RouteUriError> {
|
||||||
/// Sets the path of the route. Does not update the rank or any other
|
base.clear_query();
|
||||||
/// parameters.
|
for segment in RouteSegment::parse_path(&base) {
|
||||||
///
|
if segment?.kind != Kind::Static {
|
||||||
/// # Example
|
return Err(RouteUriError::DynamicBase);
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use rocket::{Request, Route, Data};
|
|
||||||
/// use rocket::http::{Method, uri::Origin};
|
|
||||||
/// use rocket::handler::Outcome;
|
|
||||||
///
|
|
||||||
/// fn handler<'r>(request: &'r Request, _data: Data) -> Outcome<'r> {
|
|
||||||
/// Outcome::from(request, "Hello, world!")
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// let mut index = Route::ranked(1, Method::Get, "/", handler);
|
|
||||||
/// assert_eq!(index.uri.path(), "/");
|
|
||||||
///
|
|
||||||
/// index.set_uri(Origin::parse("/hello").unwrap());
|
|
||||||
/// assert_eq!(index.uri.path(), "/hello");
|
|
||||||
/// ```
|
|
||||||
pub fn set_uri<'a>(&mut self, uri: Origin<'a>) {
|
|
||||||
self.uri = uri.into_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Decide whether a component has to be fully variable or not. That
|
|
||||||
// is, whether you can have: /a<a>b/ or even /<a>:<b>/
|
|
||||||
// TODO: Don't return a Vec...take in an &mut [&'a str] (no alloc!)
|
|
||||||
/// Given a URI, returns a vector of slices of that URI corresponding to the
|
|
||||||
/// dynamic segments in this route.
|
|
||||||
crate fn get_param_indexes(&self, uri: &Origin) -> Vec<(usize, usize)> {
|
|
||||||
let route_segs = self.uri.segments();
|
|
||||||
let uri_segs = uri.segments();
|
|
||||||
let start_addr = uri.path().as_ptr() as usize;
|
|
||||||
|
|
||||||
let mut result = Vec::with_capacity(self.uri.segment_count());
|
|
||||||
for (route_seg, uri_seg) in route_segs.zip(uri_segs) {
|
|
||||||
let i = (uri_seg.as_ptr() as usize) - start_addr;
|
|
||||||
if route_seg.ends_with("..>") {
|
|
||||||
result.push((i, uri.path().len()));
|
|
||||||
break;
|
|
||||||
} else if route_seg.ends_with('>') {
|
|
||||||
let j = i + uri_seg.len();
|
|
||||||
result.push((i, j));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
let complete_uri = format!("{}/{}", base, path);
|
||||||
|
let uri = Origin::parse_route(&complete_uri)?;
|
||||||
|
self.base = base.to_normalized().into_owned();
|
||||||
|
self.uri = uri.to_normalized().into_owned();
|
||||||
|
self.update_metadata()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RouteUriError {
|
||||||
|
Segment,
|
||||||
|
Uri(uri::Error<'static>),
|
||||||
|
DynamicBase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
|
||||||
|
fn from(_: (&'a str, SegmentError<'a>)) -> Self {
|
||||||
|
RouteUriError::Segment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<uri::Error<'a>> for RouteUriError {
|
||||||
|
fn from(error: uri::Error<'a>) -> Self {
|
||||||
|
RouteUriError::Uri(error.into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RouteUriError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RouteUriError::Segment => {
|
||||||
|
write!(f, "The URI contains malformed dynamic route path segments.")
|
||||||
|
}
|
||||||
|
RouteUriError::DynamicBase => {
|
||||||
|
write!(f, "The mount point contains dynamic parameters.")
|
||||||
|
}
|
||||||
|
RouteUriError::Uri(error) => {
|
||||||
|
write!(f, "Malformed URI: {}", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,6 +344,7 @@ impl fmt::Debug for Route {
|
||||||
.field("uri", &self.uri)
|
.field("uri", &self.uri)
|
||||||
.field("rank", &self.rank)
|
.field("rank", &self.rank)
|
||||||
.field("format", &self.format)
|
.field("format", &self.format)
|
||||||
|
.field("metadata", &self.metadata)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
|
||||||
|
|
||||||
#[derive(FromForm)]
|
|
||||||
struct Query {
|
|
||||||
field: String
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/?<query>")]
|
|
||||||
fn first(query: Query) -> String {
|
|
||||||
query.field
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn second() -> &'static str {
|
|
||||||
"no query"
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use rocket::Rocket;
|
|
||||||
use rocket::local::Client;
|
|
||||||
|
|
||||||
fn assert_no_collision(rocket: Rocket) {
|
|
||||||
let client = Client::new(rocket).unwrap();
|
|
||||||
let mut response = client.get("/?field=query").dispatch();
|
|
||||||
assert_eq!(response.body_string(), Some("query".into()));
|
|
||||||
|
|
||||||
let mut response = client.get("/").dispatch();
|
|
||||||
assert_eq!(response.body_string(), Some("no query".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_query_collisions() {
|
|
||||||
let rocket = rocket::ignite().mount("/", routes![first, second]);
|
|
||||||
assert_no_collision(rocket);
|
|
||||||
|
|
||||||
let rocket = rocket::ignite().mount("/", routes![second, first]);
|
|
||||||
assert_no_collision(rocket);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#![feature(plugin, decl_macro)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
#![allow(dead_code)] // This test is only here so that we can ensure it compiles.
|
#![allow(dead_code)] // This test is only here so that we can ensure it compiles.
|
||||||
|
|
||||||
extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use rocket::response::{self, Responder};
|
use rocket::response::{self, Responder};
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![feature(plugin, decl_macro, proc_macro_non_items)]
|
#![feature(proc_macro_non_items, proc_macro_gen, decl_macro)]
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,3 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib" }
|
rocket = { path = "../../core/lib" }
|
||||||
rocket_codegen = { path = "../../core/codegen" }
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
#![feature(plugin, decl_macro)]
|
|
||||||
#![plugin(rocket_codegen)]
|
|
||||||
|
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
// This example's illustration is the Rocket.toml file.
|
// This example's illustration is the Rocket.toml file.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue