Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 34421f13f3265221fe0ebda72c104f9ce3c303d9
parent 26db5ecb4ef32728ed51a5e2d6eee2e4ad55ee3b
Author: Divyahans Gupta <divyahansg@gmail.com>
Date:   Wed, 24 Oct 2018 21:55:43 -0700

Allow nested values in 'UriDisplay'.

Diffstat:
Mcore/codegen/tests/route.rs | 6+++---
Mcore/codegen/tests/typed-uris.rs | 29++++++++++++++---------------
Acore/http/src/uri/formatter.rs | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/http/src/uri/from_uri_param.rs | 51+++++++++++++++++++++++++++++++++++++--------------
Mcore/http/src/uri/mod.rs | 2++
Mcore/http/src/uri/uri_display.rs | 63+++++++++++++++++++++++++++++++++++----------------------------
6 files changed, 165 insertions(+), 60 deletions(-)

diff --git a/core/codegen/tests/route.rs b/core/codegen/tests/route.rs @@ -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) } } diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs @@ -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. diff --git a/core/http/src/uri/formatter.rs b/core/http/src/uri/formatter.rs @@ -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) + } +} diff --git a/core/http/src/uri/from_uri_param.rs b/core/http/src/uri/from_uri_param.rs @@ -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 { .. } -/// -/// uri!(some_route: name = "hey", query = ("Robert Mike", "Bob")); -/// // => "/hey?name=Robert+Mike&nickname=Bob" +/// ```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() } +/// # } +/// # } +/// +/// #[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 diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs @@ -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::*; diff --git a/core/http/src/uri/uri_display.rs b/core/http/src/uri/uri_display.rs @@ -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) } }