Allow nested values in 'UriDisplay'.

This commit is contained in:
Divyahans Gupta 2018-10-24 21:55:43 -07:00 committed by Sergio Benitez
parent 26db5ecb4e
commit 34421f13f3
6 changed files with 164 additions and 59 deletions

View File

@ -10,7 +10,7 @@ use rocket::http::ext::Normalize;
use rocket::local::Client;
use rocket::data::{self, Data, FromDataSimple};
use rocket::request::Form;
use rocket::http::{Status, RawStr, ContentType, uri::UriDisplay};
use rocket::http::{Status, RawStr, ContentType, uri::{Formatter, UriDisplay}};
// Use all of the code generation avaiable at once.
@ -21,8 +21,8 @@ struct Inner<'r> {
// TODO: Make this deriveable.
impl<'a> UriDisplay for Inner<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "field={}", self.field)
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_named_value("field", &self.field)
}
}

View File

@ -7,7 +7,7 @@ use std::fmt;
use std::path::PathBuf;
use rocket::http::{RawStr, Cookies};
use rocket::http::uri::{Origin, UriDisplay, FromUriParam};
use rocket::http::uri::{Origin, Formatter, UriDisplay, FromUriParam};
use rocket::request::Form;
#[derive(FromForm)]
@ -18,10 +18,9 @@ struct User<'a> {
// TODO: Make this deriveable.
impl<'a> UriDisplay for User<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "name={}&nickname={}",
&self.name.replace(' ', "+") as &UriDisplay,
&self.nickname.replace(' ', "+") as &UriDisplay)
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_named_value("name", &self.name)?;
f.write_named_value("nickname", &self.nickname)
}
}
@ -244,31 +243,31 @@ fn check_with_segments() {
fn check_complex() {
assert_uri_eq! {
uri!(complex: "no idea", 10, "high", ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: "Bob", 248, "?", User { name: "Robert".into(), nickname: "Bob".into() }) =>
"/name/Bob?foo=248&bar=10&bar=%3F&name=Robert&nickname=Bob",
uri!(complex: "Bob", 248, "a a", &User { name: "Robert".into(), nickname: "B".into() }) =>
"/name/Bob?foo=248&bar=10&bar=a%20a&name=Robert&nickname=B",
uri!(complex: "no idea", 248, "", &User { name: "A B".into(), nickname: "A".into() }) =>
"/name/no%20idea?foo=248&bar=10&bar=&name=A+B&nickname=A",
"/name/no%20idea?foo=248&bar=10&bar=&name=A%20B&nickname=A",
uri!(complex: "hi", 3, "b", &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
uri!(complex: name = "no idea", foo = 10, bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 10, name = "no idea", bar = "high", query = ("A B C", "a c")) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high", ) =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: query = *&("A B C", "a c"), foo = 10, name = "no idea", bar = "high") =>
"/name/no%20idea?foo=10&bar=10&bar=high&name=A+B+C&nickname=a+c",
"/name/no%20idea?foo=10&bar=10&bar=high&name=A%20B%20C&nickname=a%20c",
uri!(complex: foo = 3, name = "hi", bar = "b",
query = &User { name: "A B C".into(), nickname: "a b".into() }) =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
uri!(complex: query = &User { name: "A B C".into(), nickname: "a b".into() },
foo = 3, name = "hi", bar = "b") =>
"/name/hi?foo=3&bar=10&bar=b&name=A+B+C&nickname=a+b",
"/name/hi?foo=3&bar=10&bar=b&name=A%20B%20C&nickname=a%20b",
}
// Ensure variables are correctly processed.

View File

@ -0,0 +1,74 @@
use std::fmt;
use smallvec::SmallVec;
use uri::UriDisplay;
pub struct Formatter<'i, 'f: 'i> {
crate prefixes: SmallVec<[&'static str; 3]>,
crate inner: &'i mut fmt::Formatter<'f>,
crate previous: bool,
crate fresh: bool
}
impl<'i, 'f: 'i> Formatter<'i, 'f> {
pub fn write_raw<S: AsRef<str>>(&mut self, s: S) -> fmt::Result {
let s = s.as_ref();
if self.fresh && !self.prefixes.is_empty() {
if self.previous {
self.inner.write_str("&")?;
}
self.fresh = false;
self.previous = true;
for (i, prefix) in self.prefixes.iter().enumerate() {
self.inner.write_str(prefix)?;
if i < self.prefixes.len() - 1 {
self.inner.write_str(".")?;
}
}
self.inner.write_str("=")?;
}
self.inner.write_str(s)
}
fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result
where F: FnOnce(&mut Self) -> fmt::Result
{
self.fresh = true;
// TODO: PROOF OF CORRECTNESS.
let prefix: &'static str = unsafe { ::std::mem::transmute(prefix) };
self.prefixes.push(prefix);
let result = f(self);
self.prefixes.pop();
result
}
#[inline]
pub fn write_seq_value<T: UriDisplay>(&mut self, value: T) -> fmt::Result {
self.fresh = true;
self.write_value(value)
}
#[inline]
pub fn write_named_value<T: UriDisplay>(&mut self, name: &str, value: T) -> fmt::Result {
self.with_prefix(name, |f| f.write_value(value))
}
#[inline]
pub fn write_value<T: UriDisplay>(&mut self, value: T) -> fmt::Result {
UriDisplay::fmt(&value, self)
}
}
impl<'f, 'i: 'f> fmt::Write for Formatter<'f, 'i> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_raw(s)
}
}

View File

@ -56,30 +56,28 @@ use uri::UriDisplay;
/// `uri!` invocation where a `User` type is expected.
///
/// ```rust
/// # extern crate rocket;
/// # #[macro_use] extern crate rocket;
/// use std::fmt;
///
/// use rocket::http::RawStr;
/// use rocket::http::uri::{UriDisplay, FromUriParam};
/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam};
///
/// # /*
/// #[derive(FromForm)]
/// # */
/// struct User<'a> {
/// name: &'a RawStr,
/// nickname: String,
/// }
///
/// impl<'a> UriDisplay for User<'a> {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "name={}&nickname={}",
/// &self.name.replace(' ', "+") as &UriDisplay,
/// &self.nickname.replace(' ', "+") as &UriDisplay)
/// fn fmt(&self, f: &mut Formatter) -> fmt::Result {
/// f.write_named_value("name", &self.name)?;
/// f.write_named_value("nickname", &self.nickname)
/// }
/// }
///
/// impl<'a, 'b> FromUriParam<(&'a str, &'b str)> for User<'a> {
/// type Target = User<'a>;
///
/// fn from_uri_param((name, nickname): (&'a str, &'b str)) -> User<'a> {
/// User { name: name.into(), nickname: nickname.to_string() }
/// }
@ -88,12 +86,37 @@ use uri::UriDisplay;
///
/// With these implementations, the following typechecks:
///
/// ```rust,ignore
/// #[post("/<name>?<query>")]
/// fn some_route(name: &RawStr, query: User) -> T { .. }
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # use std::fmt;
/// use rocket::http::RawStr;
/// use rocket::request::Form;
/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam};
/// #
/// # #[derive(FromForm)]
/// # struct User<'a> { name: &'a RawStr, nickname: String, }
/// #
/// # impl<'a> UriDisplay for User<'a> {
/// # fn fmt(&self, f: &mut Formatter) -> fmt::Result {
/// # f.write_named_value("name", &self.name)?;
/// # f.write_named_value("nickname", &self.nickname)
/// # }
/// # }
/// #
/// # impl<'a, 'b> FromUriParam<(&'a str, &'b str)> for User<'a> {
/// # type Target = User<'a>;
/// # fn from_uri_param((name, nickname): (&'a str, &'b str)) -> User<'a> {
/// # User { name: name.into(), nickname: nickname.to_string() }
/// # }
/// # }
///
/// uri!(some_route: name = "hey", query = ("Robert Mike", "Bob"));
/// // => "/hey?name=Robert+Mike&nickname=Bob"
/// #[post("/<name>?<user..>")]
/// fn some_route(name: &RawStr, user: Form<User>) { /* .. */ }
///
/// let uri = uri!(some_route: name = "hey", user = ("Robert Mike", "Bob"));
/// assert_eq!(uri.path(), "/hey");
/// assert_eq!(uri.query(), Some("name=Robert%20Mike&nickname=Bob"));
/// ```
///
/// [`uri!`]: ::rocket_codegen::uri

View File

@ -2,6 +2,7 @@
mod uri;
mod uri_display;
mod formatter;
mod from_uri_param;
mod origin;
mod authority;
@ -15,5 +16,6 @@ pub use self::authority::*;
pub use self::origin::*;
pub use self::absolute::*;
pub use self::uri_display::*;
pub use self::formatter::*;
pub use self::from_uri_param::*;
pub use self::segments::*;

View File

@ -2,10 +2,11 @@ use std::fmt;
use std::path::{Path, PathBuf};
use std::borrow::Cow;
use {RawStr, uri::Uri, ext::Normalize};
use smallvec::SmallVec;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use {RawStr, uri::{Uri, Formatter}, ext::Normalize};
mod priv_encode_set {
/// This encode set is used for strings where '/' characters are known to be
/// safe; all other special path segment characters are encoded.
@ -83,8 +84,7 @@ use self::priv_encode_set::PATH_ENCODE_SET;
/// The implementation of `UriDisplay` for these types is identical to the
/// `Display` implementation.
///
/// * **[`&RawStr`](RawStr), `String`, `&str`,
/// `Cow<str>`**
/// * **[`&RawStr`](RawStr), `String`, `&str`, `Cow<str>`**
///
/// The string is percent encoded.
///
@ -137,15 +137,15 @@ use self::priv_encode_set::PATH_ENCODE_SET;
/// }
///
/// use std::fmt;
/// use rocket::http::uri::UriDisplay;
/// use rocket::http::uri::{Formatter, 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)
/// fn fmt(&self, f: &mut Formatter) -> fmt::Result {
/// f.write_value(&self.0)
/// }
/// }
///
@ -161,65 +161,71 @@ use self::priv_encode_set::PATH_ENCODE_SET;
/// ```
pub trait UriDisplay {
/// Formats `self` in a URI-safe manner using the given formatter.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
fn fmt(&self, f: &mut Formatter) -> fmt::Result;
}
impl<'a> fmt::Display for &'a UriDisplay {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
UriDisplay::fmt(*self, f)
let mut formatter = Formatter {
prefixes: SmallVec::new(),
inner: f,
previous: false,
fresh: true,
};
UriDisplay::fmt(*self, &mut formatter)
}
}
/// Percent-encodes the raw string.
impl UriDisplay for RawStr {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Uri::percent_encode((*self).as_str()))
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_raw(&Uri::percent_encode(self.as_str()))
}
}
/// Percent-encodes the raw string.
impl UriDisplay for str {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Uri::percent_encode(self))
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_raw(&Uri::percent_encode(self))
}
}
/// Percent-encodes the raw string.
impl<'a> UriDisplay for Cow<'a, str> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Uri::percent_encode(self))
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_raw(&Uri::percent_encode(self))
}
}
/// Percent-encodes the raw string.
impl UriDisplay for String {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Uri::percent_encode(self.as_str()))
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_raw(&Uri::percent_encode(self.as_str()))
}
}
/// Percent-encodes each segment in the path and normalizes separators.
impl UriDisplay for PathBuf {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let string = self.normalized_str();
let enc: Cow<str> = utf8_percent_encode(&string, PATH_ENCODE_SET).into();
write!(f, "{}", enc)
f.write_raw(&enc)
}
}
/// Percent-encodes each segment in the and normalizes separators.
/// Percent-encodes each segment in the path and normalizes separators.
impl UriDisplay for Path {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let string = self.normalized_str();
let enc: Cow<str> = utf8_percent_encode(&string, PATH_ENCODE_SET).into();
write!(f, "{}", enc)
f.write_raw(&enc)
}
}
@ -228,8 +234,9 @@ macro_rules! impl_with_display {
/// This implementation is identical to the `Display` implementation.
impl UriDisplay for $T {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use std::fmt::Write;
write!(f, "{}", self)
}
}
)+}
@ -249,7 +256,7 @@ macro_rules! impl_for_ref {
/// 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 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
UriDisplay::fmt(*self, f)
}
}