mirror of https://github.com/rwf2/Rocket.git
Allow nested values in 'UriDisplay'.
This commit is contained in:
parent
26db5ecb4e
commit
34421f13f3
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue