diff --git a/lib/src/http/uri.rs b/lib/src/http/uri.rs index 5e68a871..83eb913a 100644 --- a/lib/src/http/uri.rs +++ b/lib/src/http/uri.rs @@ -348,7 +348,230 @@ impl<'a> fmt::Display for URI<'a> { } } +/// Trait implemented by types that can be displayed as part of a URI. +/// +/// Types implementing this trait can be displayed in a URI-safe manner. Unlike +/// `Display`, the string written by a `UriDisplay` implementation must be +/// URI-safe. In practice, this means that the string must either be +/// percent-encoded or consist only of characters that are alphanumeric, "-", +/// ".", "_", or "~" - the "unreserved" characters. +/// +/// # Code Generation +/// +/// When the `uri!` macro is used to generate a URI for a route, the types for +/// the route's URI parameters must implement `UriDisplay`. The `UriDisplay` +/// implementation for these types is used when generating the URI. +/// +/// To illustrate `UriDisplay`'s role in code generation for `uri!`, consider +/// the following fictional route and struct definition: +/// +/// ```rust,ignore +/// struct Value { .. }; +/// +/// #[get("/item//")] +/// fn get_item(id: i32, value: Value) -> T { .. } +/// ``` +/// +/// A URI for this route can be generated as follows: +/// +/// ```rust,ignore +/// // With unnamed parameters. +/// uri!(get_item: 100, Value { .. }); +/// +/// // With named parameters. +/// uri!(get_item: id = 100, value = Value { .. }); +/// ``` +/// +/// After verifying parameters and their types, Rocket will generate code +/// similar to the following: +/// +/// ```rust,ignore +/// format!("/item/{id}/{value}", +/// id = &100 as &UriDisplay, +/// value = &Value { .. } as &UriDisplay); +/// ``` +/// +/// For this expression to typecheck, both `i32` and `Value` must implement +/// `UriDisplay`. As can be seen, the implementation will be used to display the +/// value in a URI-safe manner. +/// +/// [`uri!`]: /rocket_codegen/#procedural-macros +/// +/// # Provided Implementations +/// +/// Rocket implements `UriDisplay` for several built-in types. Their behavior is +/// documented here. +/// +/// * **i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, bool, +/// IpAddr, Ipv4Addr, Ipv6Addr** +/// +/// The implementation of `UriDisplay` for these types is identical to the +/// `Display` implementation. +/// +/// * **[`&RawStr`](/rocket/http/struct.RawStr.html), String, &str, Cow** +/// +/// The string is percent encoded. +/// +/// * **&T, &mut T** _where_ **T: UriDisplay** +/// +/// Uses the implementation of `UriDisplay` for `T`. +/// +/// # Implementing +/// +/// Implementing `UriDisplay` is similar to implementing `Display` with the +/// caveat that extra care must be taken to ensure that the written string is +/// URI-safe. As mentioned before, in practice, this means that the string must +/// either be percent-encoded or consist only of characters that are +/// alphanumeric, "-", ".", "_", or "~". +/// +/// When manually implementing `UriDisplay` for your types, you should defer to +/// existing implementations of `UriDisplay` as much as possible. In the example +/// below, for instance, `Name`'s implementation defers to `String`'s +/// implementation. To percent-encode a string, use [`URI::percent_encode()`]. +/// +/// [`URI::percent_encode()`]: https://api.rocket.rs/rocket/http/uri/struct.URI.html#method.percent_encode +/// +/// ## Example +/// +/// The following snippet consists of a `Name` type that implements both +/// `FromParam` and `UriDisplay`. The `FromParam` implementation allows `Name` +/// to be used as the target type of a dynamic parameter, while the `UriDisplay` +/// implementation allows URIs to be generated for routes with `Name` as a +/// dynamic parameter type. +/// +/// ```rust +/// # #![feature(plugin, decl_macro)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # fn main() { } +/// use rocket::http::RawStr; +/// use rocket::request::FromParam; +/// +/// struct Name(String); +/// +/// impl<'r> FromParam<'r> for Name { +/// type Error = &'r RawStr; +/// +/// /// Validates parameters that contain no spaces. +/// fn from_param(param: &'r RawStr) -> Result { +/// let decoded = param.percent_decode().map_err(|_| param)?; +/// match decoded.contains(' ') { +/// false => Ok(Name(decoded.into_owned())), +/// true => Err(param), +/// } +/// } +/// } +/// +/// use std::fmt; +/// use rocket::http::uri::UriDisplay; +/// use rocket::response::Redirect; +/// +/// impl UriDisplay for Name { +/// /// Delegates to the `UriDisplay` implementation for `String` to ensure +/// /// that the written string is URI-safe. In this case, the string will +/// /// be percent encoded. +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +/// UriDisplay::fmt(&self.0, f) +/// } +/// } +/// +/// #[get("/name/")] +/// fn redirector(name: Name) -> Redirect { +/// Redirect::to(uri!(real: name).as_str()) +/// } +/// +/// #[get("/")] +/// fn real(name: Name) -> String { +/// format!("Hello, {}!", name.0) +/// } +/// ``` + +// FIXME: Put this more narrative-like text in the guide. Fix it up beforehand. +// +// Now we have the following routes. The first route accepts a URI parameter of +// type `Name` and redirects to the second route: +// +// ```rust +// # #![feature(plugin, decl_macro)] +// # #![plugin(rocket_codegen)] +// # extern crate rocket; +// # use rocket::request::FromParam; +// # use rocket::http::RawStr; +// # struct Name(String); +// # impl Name { +// # fn new(name: String) -> Option { +// # if !name.contains(' ') { Some(name) } else { None } +// # } +// # } +// # impl<'r> FromParam<'r> for Name { +// # type Error = &'r RawStr; +// # fn from_param(param: &'r RawStr) -> Result { +// # Name::new(param.percent_decode().into_owned()).ok_or(param) +// # } +// # } +// use rocket::response::Redirect; +// +// #[get("/name/")] +// fn redirector(name: Name) -> Redirect { +// Redirect::to(&format!("/{}", name.0)) +// } +// +// #[get("/")] +// fn real(name: Name) -> String { +// format!("Hello, {}!", name.0) +// } +// ``` +// +// The redirection in the `redirector` route creates a URI that should lead to +// the `real` route. But it does this in an ad-hoc manner. What happens if the +// `real` route changes? At best, the redirection will fail and the user will +// receive a 404. +// +// To prevent this kind of issue the `uri!` macro can be used, passing in the +// `name` received from the route. When the `Name` type is used along with the +// `uri!` macro, the `UriDisplay` trait must be implemented. Both of these +// steps are done in the example below: +// +// ```rust +// # #![feature(plugin, decl_macro)] +// # #![plugin(rocket_codegen)] +// # extern crate rocket; +// # use rocket::request::FromParam; +// # use rocket::http::RawStr; +// # struct Name(String); +// # impl Name { +// # fn new(name: String) -> Option { +// # if !name.contains(' ') { Some(name) } else { None } +// # } +// # } +// # impl<'r> FromParam<'r> for Name { +// # type Error = &'r RawStr; +// # fn from_param(param: &'r RawStr) -> Result { +// # Name::new(param.percent_decode().into_owned()).ok_or(param) +// # } +// # } +// use std::fmt; +// use rocket::http::uri::UriDisplay; +// use rocket::response::Redirect; +// +// impl UriDisplay for Name { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// UriDisplay::fmt(&self.0, f) +// } +// } +// +// #[get("/name/")] +// fn redirector(name: Name) -> Redirect { +// Redirect::to(uri!(real: name).as_str()) +// } +// +// #[get("/")] +// fn real(name: Name) -> String { +// format!("Hello, {}!", name.0) +// } +// ``` pub trait UriDisplay { + /// Formats `self` in a URI-safe manner using the given formatter. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result; } @@ -359,6 +582,7 @@ impl<'a> fmt::Display for &'a UriDisplay { } } +/// Percent-encodes the raw string. impl<'a> UriDisplay for &'a RawStr { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -366,6 +590,7 @@ impl<'a> UriDisplay for &'a RawStr { } } +/// Percent-encodes the raw string. impl<'a> UriDisplay for &'a str { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -373,6 +598,7 @@ impl<'a> UriDisplay for &'a str { } } +/// Percent-encodes the raw string. impl<'a> UriDisplay for Cow<'a, str> { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -380,6 +606,7 @@ impl<'a> UriDisplay for Cow<'a, str> { } } +/// Percent-encodes the raw string. impl UriDisplay for String { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -389,6 +616,7 @@ impl UriDisplay for String { macro_rules! impl_with_display { ($($T:ty),+) => {$( + /// This implementation is identical to the `Display` implementation. impl UriDisplay for $T { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -407,6 +635,7 @@ impl_with_display! { macro_rules! impl_for_ref { ($($T:ty),+) => {$( + /// Uses the implementation of `UriDisplay` for `T`. impl<'a, T: UriDisplay + ?Sized> UriDisplay for $T { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {