Use the `RawStr` type for raw parameter strings.

This is a breaking change.

The `&str` type no longer implements `FromParam`. The `&RawStr` type
should be used in its place.
This commit is contained in:
Sergio Benitez 2017-03-31 00:18:58 -07:00
parent cff9901940
commit f5ec470a7d
15 changed files with 100 additions and 82 deletions

View File

@ -13,7 +13,7 @@ struct User<'a> {
} }
#[post("/<name>?<query>", format = "application/json", data = "<user>", rank = 2)] #[post("/<name>?<query>", format = "application/json", data = "<user>", rank = 2)]
fn get<'r>(name: &str, fn get<'r>(name: &RawStr,
query: User<'r>, query: User<'r>,
user: Form<'r, User<'r>>, user: Form<'r, User<'r>>,
cookies: Cookies) cookies: Cookies)

View File

@ -4,7 +4,7 @@
extern crate rocket; extern crate rocket;
#[get("/test/<one>/<two>/<three>")] #[get("/test/<one>/<two>/<three>")]
fn get(one: &str, two: usize, three: isize) -> &'static str { "hi" } fn get(one: String, two: usize, three: isize) -> &'static str { "hi" }
fn main() { fn main() {
let _ = routes![get]; let _ = routes![get];

View File

@ -4,7 +4,7 @@
extern crate rocket; extern crate rocket;
#[get("/<todo>")] #[get("/<todo>")]
fn todo(todo: &str) -> &str { fn todo(todo: String) -> String {
todo todo
} }

View File

@ -84,7 +84,7 @@ impl<'a> FromParam<'a> for UUID {
/// A value is successfully parsed if `param` is a properly formatted UUID. /// A value is successfully parsed if `param` is a properly formatted UUID.
/// Otherwise, a `UuidParseError` is returned. /// Otherwise, a `UuidParseError` is returned.
#[inline(always)] #[inline(always)]
fn from_param(param: &'a str) -> Result<UUID, Self::Error> { fn from_param(param: &'a RawStr) -> Result<UUID, Self::Error> {
param.parse() param.parse()
} }
} }
@ -141,14 +141,14 @@ mod test {
#[test] #[test]
fn test_from_param() { fn test_from_param() {
let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2"; let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2";
let uuid_wrapper = UUID::from_param(uuid_str).unwrap(); let uuid_wrapper = UUID::from_param(uuid_str.into()).unwrap();
assert_eq!(uuid_str, uuid_wrapper.to_string()) assert_eq!(uuid_str, uuid_wrapper.to_string())
} }
#[test] #[test]
fn test_into_inner() { fn test_into_inner() {
let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2"; let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2";
let uuid_wrapper = UUID::from_param(uuid_str).unwrap(); let uuid_wrapper = UUID::from_param(uuid_str.into()).unwrap();
let real_uuid: uuid_ext::Uuid = uuid_str.parse().unwrap(); let real_uuid: uuid_ext::Uuid = uuid_str.parse().unwrap();
let inner_uuid: uuid_ext::Uuid = uuid_wrapper.into_inner(); let inner_uuid: uuid_ext::Uuid = uuid_wrapper.into_inner();
assert_eq!(real_uuid, inner_uuid) assert_eq!(real_uuid, inner_uuid)
@ -157,7 +157,7 @@ mod test {
#[test] #[test]
fn test_partial_eq() { fn test_partial_eq() {
let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2"; let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2";
let uuid_wrapper = UUID::from_param(uuid_str).unwrap(); let uuid_wrapper = UUID::from_param(uuid_str.into()).unwrap();
let real_uuid: uuid_ext::Uuid = uuid_str.parse().unwrap(); let real_uuid: uuid_ext::Uuid = uuid_str.parse().unwrap();
assert_eq!(uuid_wrapper, real_uuid) assert_eq!(uuid_wrapper, real_uuid)
} }
@ -165,7 +165,7 @@ mod test {
#[test] #[test]
fn test_from_param_invalid() { fn test_from_param_invalid() {
let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2p"; let uuid_str = "c1aa1e3b-9614-4895-9ebd-705255fa5bc2p";
let uuid_result = UUID::from_param(uuid_str); let uuid_result = UUID::from_param(uuid_str.into());
assert_eq!(uuid_result, Err(UuidParseError::InvalidLength(37))); assert_eq!(uuid_result, Err(UuidParseError::InvalidLength(37)));
} }
} }

View File

@ -8,7 +8,7 @@ extern crate rocket;
use rocket::response::content; use rocket::response::content;
#[get("/hello/<name>/<age>")] #[get("/hello/<name>/<age>")]
fn hello(name: &str, age: i8) -> String { fn hello(name: String, age: i8) -> String {
format!("Hello, {} year old named {}!", age, name) format!("Hello, {} year old named {}!", age, name)
} }

View File

@ -75,7 +75,7 @@ fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result<Redirect, String> {
} }
#[get("/user/<username>")] #[get("/user/<username>")]
fn user_page(username: &str) -> String { fn user_page(username: &RawStr) -> String {
format!("This is {}'s page.", username) format!("This is {}'s page.", username)
} }

View File

@ -6,12 +6,12 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[get("/hello/<name>/<age>")] #[get("/hello/<name>/<age>")]
fn hello(name: &str, age: u8) -> String { fn hello(name: String, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name) format!("Hello, {} year old named {}!", age, name)
} }
#[get("/hello/<name>")] #[get("/hello/<name>")]
fn hi<'r>(name: &'r str) -> &'r str { fn hi(name: String) -> String {
name name
} }

View File

@ -3,15 +3,17 @@
extern crate rocket; extern crate rocket;
use rocket::http::RawStr;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[get("/hello/<name>/<age>")] #[get("/hello/<name>/<age>")]
fn hello(name: &str, age: i8) -> String { fn hello(name: String, age: i8) -> String {
format!("Hello, {} year old named {}!", age, name) format!("Hello, {} year old named {}!", age, name)
} }
#[get("/hello/<name>/<age>", rank = 2)] #[get("/hello/<name>/<age>", rank = 2)]
fn hi(name: &str, age: &str) -> String { fn hi(name: String, age: &RawStr) -> String {
format!("Hi {}! Your age ({}) is kind of funky.", name, age) format!("Hi {}! Your age ({}) is kind of funky.", name, age)
} }

View File

@ -7,8 +7,7 @@ use std::io;
use std::fs::File; use std::fs::File;
use rocket::{Request, Route, Data, Catcher, Error}; use rocket::{Request, Route, Data, Catcher, Error};
use rocket::http::Status; use rocket::http::{Status, RawStr};
use rocket::request::FromParam;
use rocket::response::{self, Responder}; use rocket::response::{self, Responder};
use rocket::response::status::Custom; use rocket::response::status::Custom;
use rocket::handler::Outcome; use rocket::handler::Outcome;
@ -23,7 +22,8 @@ fn hi(_req: &Request, _: Data) -> Outcome<'static> {
} }
fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> { fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
Outcome::of(req.get_param(0).unwrap_or("unnamed")) let param = req.get_param::<&'a RawStr>(0);
Outcome::of(param.map(|r| r.as_str()).unwrap_or("unnamed"))
} }
fn echo_url(req: &Request, _: Data) -> Outcome<'static> { fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
@ -31,7 +31,8 @@ fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
.as_str() .as_str()
.split_at(6) .split_at(6)
.1; .1;
Outcome::of(String::from_param(param).unwrap())
Outcome::of(RawStr::from_str(param).url_decode())
} }
fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> { fn upload<'r>(req: &'r Request, data: Data) -> Outcome<'r> {

View File

@ -6,6 +6,7 @@ extern crate rocket;
mod tests; mod tests;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::http::RawStr;
#[get("/")] #[get("/")]
fn root() -> Redirect { fn root() -> Redirect {
@ -13,8 +14,8 @@ fn root() -> Redirect {
} }
#[get("/users/<name>")] #[get("/users/<name>")]
fn user(name: &str) -> Result<&'static str, Redirect> { fn user(name: &RawStr) -> Result<&'static str, Redirect> {
match name { match name.as_str() {
"Sergio" => Ok("Hello, Sergio!"), "Sergio" => Ok("Hello, Sergio!"),
_ => Err(Redirect::to("/users/login")), _ => Err(Redirect::to("/users/login")),
} }

View File

@ -3,11 +3,12 @@
extern crate rocket; extern crate rocket;
#[cfg(test)] #[cfg(test)] mod tests;
mod tests;
use rocket::http::RawStr;
#[get("/users/<name>")] #[get("/users/<name>")]
fn user(name: &str) -> Option<&'static str> { fn user(name: &RawStr) -> Option<&'static str> {
if name == "Sergio" { if name == "Sergio" {
Some("Hello, Sergio!") Some("Hello, Sergio!")
} else { } else {

View File

@ -2,6 +2,7 @@ use std::fmt;
use std::borrow::Cow; use std::borrow::Cow;
use rocket::request::FromParam; use rocket::request::FromParam;
use rocket::http::RawStr;
use rand::{self, Rng}; use rand::{self, Rng};
/// Table to retrieve base62 values from. /// Table to retrieve base62 values from.
@ -44,9 +45,9 @@ fn valid_id(id: &str) -> bool {
/// Returns an instance of `PasteID` if the path segment is a valid ID. /// Returns an instance of `PasteID` if the path segment is a valid ID.
/// Otherwise returns the invalid ID as the `Err` value. /// Otherwise returns the invalid ID as the `Err` value.
impl<'a> FromParam<'a> for PasteID<'a> { impl<'a> FromParam<'a> for PasteID<'a> {
type Error = &'a str; type Error = &'a RawStr;
fn from_param(param: &'a str) -> Result<PasteID<'a>, &'a str> { fn from_param(param: &'a RawStr) -> Result<PasteID<'a>, &'a RawStr> {
match valid_id(param) { match valid_id(param) {
true => Ok(PasteID(Cow::Borrowed(param))), true => Ok(PasteID(Cow::Borrowed(param))),
false => Err(param) false => Err(param)

View File

@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::fmt::Debug; use std::fmt::Debug;
use http::uri::{URI, Segments, SegmentError}; use http::uri::{URI, Segments, SegmentError};
use http::RawStr;
/// Trait to convert a dynamic path segment string to a concrete value. /// Trait to convert a dynamic path segment string to a concrete value.
/// ///
@ -51,15 +52,16 @@ use http::uri::{URI, Segments, SegmentError};
/// ///
/// For instance, imagine you've asked for an `<id>` as a `usize`. To determine /// For instance, imagine you've asked for an `<id>` as a `usize`. To determine
/// when the `<id>` was not a valid `usize` and retrieve the string that failed /// when the `<id>` was not a valid `usize` and retrieve the string that failed
/// to parse, you can use a `Result<usize, &str>` type for the `<id>` parameter /// to parse, you can use a `Result<usize, &RawStr>` type for the `<id>`
/// as follows: /// parameter as follows:
/// ///
/// ```rust /// ```rust
/// # #![feature(plugin)] /// # #![feature(plugin)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
/// # use rocket::http::RawStr;
/// #[get("/<id>")] /// #[get("/<id>")]
/// fn hello(id: Result<usize, &str>) -> String { /// fn hello(id: Result<usize, &RawStr>) -> String {
/// match id { /// match id {
/// Ok(id_num) => format!("usize: {}", id_num), /// Ok(id_num) => format!("usize: {}", id_num),
/// Err(string) => format!("Not a usize: {}", string) /// Err(string) => format!("Not a usize: {}", string)
@ -80,7 +82,7 @@ use http::uri::{URI, Segments, SegmentError};
/// type returns successfully. Otherwise, the raw path segment is returned /// type returns successfully. Otherwise, the raw path segment is returned
/// in the `Err` value. /// in the `Err` value.
/// ///
/// * **str** /// * **&RawStr**
/// ///
/// _This implementation always returns successfully._ /// _This implementation always returns successfully._
/// ///
@ -107,14 +109,6 @@ use http::uri::{URI, Segments, SegmentError};
/// The path segment is parsed by `T`'s `FromParam` implementation. The /// The path segment is parsed by `T`'s `FromParam` implementation. The
/// returned `Result` value is returned. /// returned `Result` value is returned.
/// ///
/// # `str` vs. `String`
///
/// Paths are URL encoded. As a result, the `str` `FromParam` implementation
/// returns the raw, URL encoded version of the path segment string. On the
/// other hand, `String` decodes the path parameter, but requires an allocation
/// to do so. This tradeoff is similiar to that of form values, and you should
/// use whichever makes sense for your application.
///
/// # Example /// # Example
/// ///
/// Say you want to parse a segment of the form: /// Say you want to parse a segment of the form:
@ -138,13 +132,14 @@ use http::uri::{URI, Segments, SegmentError};
/// ///
/// ```rust /// ```rust
/// use rocket::request::FromParam; /// use rocket::request::FromParam;
/// use rocket::http::RawStr;
/// # #[allow(dead_code)] /// # #[allow(dead_code)]
/// # struct MyParam<'r> { key: &'r str, value: usize } /// # struct MyParam<'r> { key: &'r str, value: usize }
/// ///
/// impl<'r> FromParam<'r> for MyParam<'r> { /// impl<'r> FromParam<'r> for MyParam<'r> {
/// type Error = &'r str; /// type Error = &'r RawStr;
/// ///
/// fn from_param(param: &'r str) -> Result<MyParam<'r>, &'r str> { /// fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
/// let (key, val_str) = match param.find(':') { /// let (key, val_str) = match param.find(':') {
/// Some(i) if i > 0 => (&param[..i], &param[(i + 1)..]), /// Some(i) if i > 0 => (&param[..i], &param[(i + 1)..]),
/// _ => return Err(param) /// _ => return Err(param)
@ -172,11 +167,12 @@ use http::uri::{URI, Segments, SegmentError};
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
/// # use rocket::request::FromParam; /// # use rocket::request::FromParam;
/// # use rocket::http::RawStr;
/// # #[allow(dead_code)] /// # #[allow(dead_code)]
/// # struct MyParam<'r> { key: &'r str, value: usize } /// # struct MyParam<'r> { key: &'r str, value: usize }
/// # impl<'r> FromParam<'r> for MyParam<'r> { /// # impl<'r> FromParam<'r> for MyParam<'r> {
/// # type Error = &'r str; /// # type Error = &'r RawStr;
/// # fn from_param(param: &'r str) -> Result<MyParam<'r>, &'r str> { /// # fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
/// # Err(param) /// # Err(param)
/// # } /// # }
/// # } /// # }
@ -197,29 +193,35 @@ pub trait FromParam<'a>: Sized {
/// Parses an instance of `Self` from a dynamic path parameter string or /// Parses an instance of `Self` from a dynamic path parameter string or
/// returns an `Error` if one cannot be parsed. /// returns an `Error` if one cannot be parsed.
fn from_param(param: &'a str) -> Result<Self, Self::Error>; fn from_param(param: &'a RawStr) -> Result<Self, Self::Error>;
} }
impl<'a> FromParam<'a> for &'a str { impl<'a> FromParam<'a> for &'a RawStr {
type Error = (); type Error = ();
fn from_param(param: &'a str) -> Result<&'a str, Self::Error> {
#[inline(always)]
fn from_param(param: &'a RawStr) -> Result<&'a RawStr, Self::Error> {
Ok(param) Ok(param)
} }
} }
impl<'a> FromParam<'a> for String { impl<'a> FromParam<'a> for String {
type Error = &'a str; type Error = &'a RawStr;
fn from_param(p: &'a str) -> Result<String, Self::Error> {
URI::percent_decode(p.as_bytes()).map_err(|_| p).map(|s| s.into_owned()) #[inline(always)]
fn from_param(param: &'a RawStr) -> Result<String, Self::Error> {
param.percent_decode().map(|cow| cow.into_owned()).map_err(|_| param)
} }
} }
macro_rules! impl_with_fromstr { macro_rules! impl_with_fromstr {
($($T:ident),+) => ($( ($($T:ident),+) => ($(
impl<'a> FromParam<'a> for $T { impl<'a> FromParam<'a> for $T {
type Error = &'a str; type Error = &'a RawStr;
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
$T::from_str(param).map_err(|_| param) #[inline(always)]
fn from_param(param: &'a RawStr) -> Result<Self, Self::Error> {
$T::from_str(param.as_str()).map_err(|_| param)
} }
} }
)+) )+)
@ -230,22 +232,26 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64,
SocketAddr); SocketAddr);
impl<'a, T: FromParam<'a>> FromParam<'a> for Result<T, T::Error> { impl<'a, T: FromParam<'a>> FromParam<'a> for Result<T, T::Error> {
type Error = (); type Error = !;
fn from_param(p: &'a str) -> Result<Self, Self::Error> {
Ok(match T::from_param(p) { #[inline]
Ok(val) => Ok(val), fn from_param(param: &'a RawStr) -> Result<Self, Self::Error> {
Err(e) => Err(e), match T::from_param(param) {
}) Ok(val) => Ok(Ok(val)),
Err(e) => Ok(Err(e)),
}
} }
} }
impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> { impl<'a, T: FromParam<'a>> FromParam<'a> for Option<T> {
type Error = (); type Error = !;
fn from_param(p: &'a str) -> Result<Self, Self::Error> {
Ok(match T::from_param(p) { #[inline]
Ok(val) => Some(val), fn from_param(param: &'a RawStr) -> Result<Self, Self::Error> {
Err(_) => None match T::from_param(param) {
}) Ok(val) => Ok(Some(val)),
Err(_) => Ok(None)
}
} }
} }
@ -277,9 +283,10 @@ pub trait FromSegments<'a>: Sized {
} }
impl<'a> FromSegments<'a> for Segments<'a> { impl<'a> FromSegments<'a> for Segments<'a> {
type Error = (); type Error = !;
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, ()> { #[inline(always)]
fn from_segments(segments: Segments<'a>) -> Result<Segments<'a>, Self::Error> {
Ok(segments) Ok(segments)
} }
} }
@ -335,21 +342,25 @@ impl<'a> FromSegments<'a> for PathBuf {
} }
impl<'a, T: FromSegments<'a>> FromSegments<'a> for Result<T, T::Error> { impl<'a, T: FromSegments<'a>> FromSegments<'a> for Result<T, T::Error> {
type Error = (); type Error = !;
fn from_segments(segments: Segments<'a>) -> Result<Result<T, T::Error>, ()> {
Ok(match T::from_segments(segments) { #[inline]
Ok(val) => Ok(val), fn from_segments(segments: Segments<'a>) -> Result<Result<T, T::Error>, !> {
Err(e) => Err(e), match T::from_segments(segments) {
}) Ok(val) => Ok(Ok(val)),
Err(e) => Ok(Err(e)),
}
} }
} }
impl<'a, T: FromSegments<'a>> FromSegments<'a> for Option<T> { impl<'a, T: FromSegments<'a>> FromSegments<'a> for Option<T> {
type Error = (); type Error = !;
fn from_segments(segments: Segments<'a>) -> Result<Option<T>, ()> {
Ok(match T::from_segments(segments) { #[inline]
Ok(val) => Some(val), fn from_segments(segments: Segments<'a>) -> Result<Option<T>, !> {
Err(_) => None match T::from_segments(segments) {
}) Ok(val) => Ok(Some(val)),
Err(_) => Ok(None)
}
} }
} }

View File

@ -14,7 +14,7 @@ use super::{FromParam, FromSegments};
use router::Route; use router::Route;
use http::uri::{URI, Segments}; use http::uri::{URI, Segments};
use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar, Key}; use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar, Key};
use http::{ContentType, Accept, MediaType}; use http::{RawStr, ContentType, Accept, MediaType};
use http::parse::media_type; use http::parse::media_type;
use http::hyper; use http::hyper;
@ -362,7 +362,7 @@ impl<'r> Request<'r> {
/// ///
/// # Example /// # Example
/// ///
/// Retrieve parameter `0`, which is expected to be an `&str`, in a manual /// Retrieve parameter `0`, which is expected to be a `String`, in a manual
/// route: /// route:
/// ///
/// ```rust /// ```rust
@ -371,7 +371,7 @@ impl<'r> Request<'r> {
/// ///
/// # #[allow(dead_code)] /// # #[allow(dead_code)]
/// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> { /// fn name<'a>(req: &'a Request, _: Data) -> Outcome<'a> {
/// Outcome::of(req.get_param(0).unwrap_or("unnamed")) /// Outcome::of(req.get_param::<String>(0).unwrap_or("unnamed".into()))
/// } /// }
/// ``` /// ```
pub fn get_param<'a, T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> { pub fn get_param<'a, T: FromParam<'a>>(&'a self, n: usize) -> Result<T, Error> {
@ -391,7 +391,7 @@ impl<'r> Request<'r> {
/// Get the `n`th path parameter as a string, if it exists. This is used by /// Get the `n`th path parameter as a string, if it exists. This is used by
/// codegen. /// codegen.
#[doc(hidden)] #[doc(hidden)]
pub fn get_param_str(&self, n: usize) -> Option<&str> { pub fn get_param_str(&self, n: usize) -> Option<&RawStr> {
let params = self.extra.params.borrow(); let params = self.extra.params.borrow();
if n >= params.len() { if n >= params.len() {
debug!("{} is >= param count {}", n, params.len()); debug!("{} is >= param count {}", n, params.len());
@ -405,7 +405,7 @@ impl<'r> Request<'r> {
return None; return None;
} }
Some(&path[i..j]) 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

View File

@ -53,9 +53,10 @@ const FLASH_COOKIE_NAME: &'static str = "_flash";
/// # /// #
/// use rocket::response::{Flash, Redirect}; /// use rocket::response::{Flash, Redirect};
/// use rocket::request::FlashMessage; /// use rocket::request::FlashMessage;
/// use rocket::http::RawStr;
/// ///
/// #[post("/login/<name>")] /// #[post("/login/<name>")]
/// fn login(name: &str) -> Result<&'static str, Flash<Redirect>> { /// fn login(name: &RawStr) -> Result<&'static str, Flash<Redirect>> {
/// if name == "special_user" { /// if name == "special_user" {
/// Ok("Hello, special user!") /// Ok("Hello, special user!")
/// } else { /// } else {