Rocket

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

commit 3798cd3e6516295cdea78ed6a13a0dc1a2c689ce
parent cda4c520f19517867667b9f4a96cee19a14f1934
Author: Sergio Benitez <sb@sergio.bz>
Date:   Thu, 25 Oct 2018 02:53:04 -0700

Document and test 'UriDisplay', its derive, and 'Formatter'.

Diffstat:
Mcore/codegen/src/derive/from_form.rs | 10+++++-----
Mcore/codegen/src/derive/uri_display.rs | 21++++++++++++++-------
Mcore/codegen/src/lib.rs | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Acore/codegen/tests/ui-fail/uri_display.rs | 30++++++++++++++++++++++++++++++
Acore/codegen/tests/ui-fail/uri_display.stderr | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/codegen/tests/ui-fail/uri_display_type_errors.rs | 45+++++++++++++++++++++++++++++++++++++++++++++
Acore/codegen/tests/ui-fail/uri_display_type_errors.stderr | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/codegen/tests/uri_display.rs | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/http/src/uri/formatter.rs | 312++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcore/http/src/uri/uri_display.rs | 69++++++++++++++++++++++++++++++++++++++++++++++++---------------------
10 files changed, 711 insertions(+), 62 deletions(-)

diff --git a/core/codegen/src/derive/from_form.rs b/core/codegen/src/derive/from_form.rs @@ -2,13 +2,13 @@ use proc_macro::{Span, TokenStream}; use derive_utils::{*, ext::{TypeExt, Split3}}; #[derive(FromMeta)] -struct Form { - field: FormField, +crate struct Form { + crate field: FormField, } -struct FormField { - span: Span, - name: String +crate struct FormField { + crate span: Span, + crate name: String } fn is_valid_field_name(s: &str) -> bool { diff --git a/core/codegen/src/derive/uri_display.rs b/core/codegen/src/derive/uri_display.rs @@ -1,9 +1,11 @@ use proc_macro::{Span, TokenStream}; use derive_utils::*; -const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not allowed"; -const NO_NULLARY: &str = "nullary items are not allowed"; -const NO_EMPTY_ENUMS: &str = "empty enums are not allowed"; +use derive::from_form::Form; + +const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not supported"; +const NO_NULLARY: &str = "nullary items are not supported"; +const NO_EMPTY_ENUMS: &str = "empty enums are not supported"; const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one field"; fn validate_fields(fields: Fields, parent_span: Span) -> Result<()> { @@ -47,15 +49,20 @@ pub fn derive_uri_display(input: TokenStream) -> TokenStream { Ok(()) } }) - .map_field(|_, field| { + .try_map_field(|_, field| { let span = field.span().into(); let accessor = field.accessor(); - if let Some(ref ident) = field.ident { - let name = ident.to_string(); + let tokens = if let Some(ref ident) = field.ident { + let name = Form::from_attrs("form", &field.attrs) + .map(|result| result.map(|form| form.field.name)) + .unwrap_or_else(|| Ok(ident.to_string()))?; + quote_spanned!(span => f.write_named_value(#name, &#accessor)?;) } else { quote_spanned!(span => f.write_value(&#accessor)?;) - } + }; + + Ok(tokens) }) .to_tokens() } diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs @@ -583,7 +583,69 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { emit!(derive::responder::derive_responder(input)) } -#[proc_macro_derive(UriDisplay)] +/// Derive for the [`UriDisplay`] trait. +/// +/// The [`UriDisplay`] derive can be applied to enums and structs. When applied +/// to enums, variants must have at least one field. When applied to structs, +/// the struct must have at least one field. +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// #[derive(UriDisplay)] +/// enum Kind { +/// A(String), +/// B(usize), +/// } +/// +/// #[derive(UriDisplay)] +/// struct MyStruct { +/// name: String, +/// id: usize, +/// kind: Kind, +/// } +/// ``` +/// +/// Each field's type is required to implement [`UriDisplay`]. +/// +/// The derive generates an implementation of the [`UriDisplay`] trait. The +/// implementation calls [`Formatter::write_named_value()`] for every named +/// field, using the field's name (unless overriden, explained next) as the +/// `name` parameter, and [`Formatter::write_value()`] for every unnamed field +/// in the order the fields are declared. +/// +/// The derive accepts one field attribute: `form`, with the following syntax: +/// +/// ```text +/// form := 'field' '=' '"' IDENT '"' +/// +/// IDENT := valid identifier, as defined by Rust +/// ``` +/// +/// When applied, the attribute looks as follows: +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// # #[derive(UriDisplay)] +/// # struct Kind(String); +/// #[derive(UriDisplay)] +/// struct MyStruct { +/// name: String, +/// id: usize, +/// #[form(field = "type")] +/// kind: Kind, +/// } +/// ``` +/// +/// The field attribute directs that a different field name be used when calling +/// [`Formatter::write_named_value()`] for the given field. The value of the +/// `field` attribute is used instead of the structure's actual field name. In +/// the example above, the field `MyStruct::kind` is rendered with a name of +/// `type`. +/// +/// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html +/// [`Formatter::write_named_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_named_value +/// [`Formatter::write_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_value +#[proc_macro_derive(UriDisplay, attributes(form))] pub fn derive_uri_display(input: TokenStream) -> TokenStream { emit!(derive::uri_display::derive_uri_display(input)) } diff --git a/core/codegen/tests/ui-fail/uri_display.rs b/core/codegen/tests/ui-fail/uri_display.rs @@ -0,0 +1,30 @@ +#[macro_use] extern crate rocket; + +#[derive(UriDisplay)] +struct Foo1; +//~^ ERROR not supported + +#[derive(UriDisplay)] +struct Foo2(); +//~^ ERROR not supported + +#[derive(UriDisplay)] +enum Foo3 { } +//~^ ERROR not supported + +#[derive(UriDisplay)] +enum Foo4 { + Variant, + //~^ ERROR not supported +} + +#[derive(UriDisplay)] +struct Foo5(String, String); +//~^ ERROR exactly one + +#[derive(UriDisplay)] +struct Foo6 { + #[form(field = 123)] + //~^ ERROR invalid value: expected string + field: String, +} diff --git a/core/codegen/tests/ui-fail/uri_display.stderr b/core/codegen/tests/ui-fail/uri_display.stderr @@ -0,0 +1,74 @@ +error: fieldless structs or variants are not supported + --> $DIR/uri_display.rs:4:1 + | +4 | struct Foo1; + | ^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:3:10 + | +3 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: fieldless structs or variants are not supported + --> $DIR/uri_display.rs:8:1 + | +8 | struct Foo2(); + | ^^^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:7:10 + | +7 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: empty enums are not supported + --> $DIR/uri_display.rs:12:1 + | +12 | enum Foo3 { } + | ^^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:11:10 + | +11 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: fieldless structs or variants are not supported + --> $DIR/uri_display.rs:17:5 + | +17 | Variant, + | ^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:15:10 + | +15 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: tuple structs or variants must have exactly one field + --> $DIR/uri_display.rs:22:12 + | +22 | struct Foo5(String, String); + | ^^^^^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:21:10 + | +21 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: invalid value: expected string literal + --> $DIR/uri_display.rs:27:20 + | +27 | #[form(field = 123)] + | ^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:25:10 + | +25 | #[derive(UriDisplay)] + | ^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/core/codegen/tests/ui-fail/uri_display_type_errors.rs b/core/codegen/tests/ui-fail/uri_display_type_errors.rs @@ -0,0 +1,45 @@ +#[macro_use] extern crate rocket; + +struct BadType; + +#[derive(UriDisplay)] +struct Bar1(BadType); +//~^ ERROR UriDisplay + +#[derive(UriDisplay)] +struct Bar2 { + field: BadType, + //~^ ERROR UriDisplay +} + +#[derive(UriDisplay)] +struct Bar3 { + field: String, + bad: BadType, + //~^ ERROR UriDisplay +} + +#[derive(UriDisplay)] +enum Bar4 { + Inner(BadType), + //~^ ERROR UriDisplay +} + +#[derive(UriDisplay)] +enum Bar5 { + Inner { + field: BadType, + //~^ ERROR UriDisplay + }, +} + +#[derive(UriDisplay)] +enum Bar6 { + Inner { + field: String, + other: BadType, + //~^ ERROR UriDisplay + }, +} + +fn main() { } diff --git a/core/codegen/tests/ui-fail/uri_display_type_errors.stderr b/core/codegen/tests/ui-fail/uri_display_type_errors.stderr @@ -0,0 +1,54 @@ +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:6:13 + | +6 | struct Bar1(BadType); + | ^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:11:5 + | +11 | field: BadType, + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:18:5 + | +18 | bad: BadType, + | ^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:24:11 + | +24 | Inner(BadType), + | ^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&&BadType` + +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:31:9 + | +31 | field: BadType, + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&&BadType` + +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied + --> $DIR/uri_display_type_errors.rs:40:9 + | +40 | other: BadType, + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay` for `&&BadType` + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/core/codegen/tests/uri_display.rs b/core/codegen/tests/uri_display.rs @@ -0,0 +1,94 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] extern crate rocket; + +use rocket::http::RawStr; +use rocket::http::uri::{UriDisplay, Formatter}; + +macro_rules! assert_uri_display { + ($v:expr, $s:expr) => ( + let uri_string = format!("{}", &$v as &UriDisplay); + assert_eq!(uri_string, $s); + ) +} + +#[derive(UriDisplay, Clone)] +enum Foo<'r> { + First(&'r RawStr), + Second { + inner: &'r RawStr, + other: usize, + }, + Third { + #[form(field = "type")] + kind: String, + }, +} + +#[test] +fn uri_display_foo() { + let foo = Foo::First("hello".into()); + assert_uri_display!(foo, "hello"); + + let foo = Foo::First("hello there".into()); + assert_uri_display!(foo, "hello%20there"); + + let foo = Foo::Second { inner: "hi".into(), other: 123 }; + assert_uri_display!(foo, "inner=hi&other=123"); + + let foo = Foo::Second { inner: "hi bo".into(), other: 321 }; + assert_uri_display!(foo, "inner=hi%20bo&other=321"); + + let foo = Foo::Third { kind: "hello".into() }; + assert_uri_display!(foo, "type=hello"); + + let foo = Foo::Third { kind: "hello there".into() }; + assert_uri_display!(foo, "type=hello%20there"); +} + +#[derive(UriDisplay)] +struct Bar<'a> { + foo: Foo<'a>, + baz: String, +} + +#[test] +fn uri_display_bar() { + let foo = Foo::First("hello".into()); + let bar = Bar { foo, baz: "well, hi!".into() }; + assert_uri_display!(bar, "foo=hello&baz=well,%20hi!"); + + let foo = Foo::Second { inner: "hi".into(), other: 123 }; + let bar = Bar { foo, baz: "done".into() }; + assert_uri_display!(bar, "foo.inner=hi&foo.other=123&baz=done"); + + let foo = Foo::Third { kind: "hello".into() }; + let bar = Bar { foo, baz: "turkey day".into() }; + assert_uri_display!(bar, "foo.type=hello&baz=turkey%20day"); +} + +#[derive(UriDisplay)] +struct Baz<'a> { + foo: Foo<'a>, + bar: Bar<'a>, + last: String +} + +#[test] +fn uri_display_baz() { + let foo1 = Foo::Second { inner: "hi".into(), other: 123 }; + let foo2 = Foo::Second { inner: "bye".into(), other: 321 }; + let bar = Bar { foo: foo2, baz: "done".into() }; + let baz = Baz { foo: foo1, bar, last: "ok".into() }; + assert_uri_display!(baz, "foo.inner=hi&foo.other=123&\ + bar.foo.inner=bye&bar.foo.other=321&bar.baz=done&\ + last=ok"); + + let foo1 = Foo::Third { kind: "hello".into() }; + let foo2 = Foo::First("bye".into()); + let bar = Bar { foo: foo1, baz: "end".into() }; + let baz = Baz { foo: foo2, bar, last: "done".into() }; + assert_uri_display!(baz, "foo=bye&\ + bar.foo.type=hello&bar.baz=end&\ + last=done"); +} diff --git a/core/http/src/uri/formatter.rs b/core/http/src/uri/formatter.rs @@ -4,6 +4,128 @@ use smallvec::SmallVec; use uri::UriDisplay; +/// A struct used to format strings for [`UriDisplay`]. +/// +/// A mutable version of this struct is passed to [`UriDisplay::fmt()`]. This +/// struct properly formats series of named values for use in URIs. In +/// particular, this struct applies the following transformations: +/// +/// * When **mutliple values** are written, they are separated by `&`. +/// +/// * When a **named value** is written with [`write_named_value()`], the name +/// is written out, followed by a `=`, followed by the value. +/// +/// * When **nested named values** are written, typically by passing a value +/// to [`write_named_value()`] whose implementation of `UriDisplay` also +/// calls `write_named_vlaue()`, the nested names are joined by a `.`, +/// written out followed by a `=`, followed by the value. +/// +/// [`UriDisplay`]: uri::UriDisplay +/// [`UriDisplay::fmt()`]: uri::UriDisplay::fmt() +/// [`write_named_value()`]: uri::Formatter::write_named_value() +/// +/// # Usage +/// +/// Usage is fairly straightforward: +/// +/// * For every _named value_ you wish to emit, call [`write_named_value()`]. +/// * For every _unnamed value_ you wish to emit, call [`write_value()`]. +/// * To write a string directly, call [`write_raw()`]. +/// +/// The `write_named_value` method automatically prefixes the `name` to the +/// written value and, along with `write_value` and `write_raw`, handles nested +/// calls to `write_named_value` automatically, prefixing names when necessary. +/// Unlike the other methods, `write_raw` does _not_ prefix any nested names +/// every time it is called. Instead, it only prefixes names the _first_ time it +/// is called, after a call to `write_named_value` or `write_value`, or after a +/// call to [`refresh()`]. +/// +/// [`refresh()`]: uri::Formatter::refresh() +/// +/// # Example +/// +/// The following example uses all of the `write` methods in a varied order to +/// display the semantics of `Formatter`. Note that `UriDisplay` should rarely +/// be implemented manually, preferring to use the derive, and that this +/// implementation is purely demonstrative. +/// +/// ```rust +/// # extern crate rocket; +/// use std::fmt; +/// +/// use rocket::http::uri::{Formatter, UriDisplay}; +/// +/// struct Outer { +/// value: Inner, +/// another: usize, +/// extra: usize +/// } +/// +/// struct Inner { +/// value: usize, +/// extra: usize +/// } +/// +/// impl UriDisplay for Outer { +/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// f.write_named_value("outer_field", &self.value)?; +/// f.write_named_value("another", &self.another)?; +/// f.write_raw("out")?; +/// f.write_raw("side")?; +/// f.write_value(&self.extra) +/// } +/// } +/// +/// impl UriDisplay for Inner { +/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// f.write_named_value("inner_field", &self.value)?; +/// f.write_value(&self.extra)?; +/// f.write_raw("inside") +/// } +/// } +/// +/// let inner = Inner { value: 0, extra: 1 }; +/// let outer = Outer { value: inner, another: 2, extra: 3 }; +/// let uri_string = format!("{}", &outer as &UriDisplay); +/// assert_eq!(uri_string, "outer_field.inner_field=0&\ +/// outer_field=1&\ +/// outer_field=inside&\ +/// another=2&\ +/// outside&\ +/// 3"); +/// ``` +/// +/// Note that you can also use the `write!` macro to write directly to the +/// formatter as long as the [`std::fmt::Write`] trait is in scope. Internally, +/// the `write!` macro calls [`write_raw()`], so care must be taken to ensure +/// that the written string is URI-safe. +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// use std::fmt::{self, Write}; +/// +/// use rocket::http::uri::{Formatter, UriDisplay}; +/// +/// pub struct Complex(u8, u8); +/// +/// impl UriDisplay for Complex { +/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// write!(f, "{}+{}", self.0, self.1) +/// } +/// } +/// +/// #[derive(UriDisplay)] +/// struct Message { +/// number: Complex, +/// } +/// +/// let message = Message { number: Complex(42, 231) }; +/// let uri_string = format!("{}", &message as &UriDisplay); +/// assert_eq!(uri_string, "number=42+231"); +/// ``` +/// +/// [`write_value()`]: uri::Formatter::write_value() +/// [`write_raw()`]: uri::Formatter::write_raw() pub struct Formatter<'i, 'f: 'i> { crate prefixes: SmallVec<[&'static str; 3]>, crate inner: &'i mut fmt::Formatter<'f>, @@ -12,34 +134,18 @@ pub struct Formatter<'i, 'f: 'i> { } 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("=")?; + crate fn new(formatter: &'i mut fmt::Formatter<'f>) -> Self { + Formatter { + prefixes: SmallVec::new(), + inner: formatter, + previous: false, + fresh: true, } - - 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) }; @@ -50,20 +156,170 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { result } - #[inline] - pub fn write_seq_value<T: UriDisplay>(&mut self, value: T) -> fmt::Result { - self.fresh = true; - self.write_value(value) + #[inline(always)] + fn refreshed<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, f: F) -> fmt::Result { + self.refresh(); + let result = f(self); + self.refresh(); + result } + /// Writes `string` to `self`. + /// + /// If `self` is _fresh_ (after a call to other `write_` methods or + /// [`refresh()`]), prefixes any names as necessary. + /// + /// This method is called by the `write!` macro. + /// + /// [`refresh()`]: Formatter::refresh() + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use std::fmt; + /// + /// use rocket::http::uri::{Formatter, UriDisplay}; + /// + /// struct Foo; + /// + /// impl UriDisplay for Foo { + /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// f.write_raw("f")?; + /// f.write_raw("o")?; + /// f.write_raw("o") + /// } + /// } + /// + /// let foo = Foo; + /// let uri_string = format!("{}", &foo as &UriDisplay); + /// assert_eq!(uri_string, "foo"); + /// ``` + pub fn write_raw<S: AsRef<str>>(&mut self, string: S) -> fmt::Result { + let s = string.as_ref(); + if self.fresh { + if self.previous { + self.inner.write_str("&")?; + } + + if !self.prefixes.is_empty() { + 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.fresh = false; + self.previous = true; + self.inner.write_str(s) + } + + /// Writes the named value `value` by prefixing `name` followed by `=` to + /// the value. Any nested names are also prefixed as necessary. + /// + /// Refreshes `self` before the name is written and after the value is + /// written. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use std::fmt; + /// + /// use rocket::http::uri::{Formatter, UriDisplay}; + /// + /// struct Foo { + /// name: usize + /// } + /// + /// impl UriDisplay for Foo { + /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// f.write_named_value("name", &self.name) + /// } + /// } + /// + /// let foo = Foo { name: 123 }; + /// let uri_string = format!("{}", &foo as &UriDisplay); + /// assert_eq!(uri_string, "name=123"); + /// ``` #[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)) + self.refreshed(|f| f.with_prefix(name, |f| f.write_value(value))) } + /// Writes the unnamed value `value`. Any nested names are prefixed as + /// necessary. + /// + /// Refreshes `self` before and after the value is written. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use std::fmt; + /// + /// use rocket::http::uri::{Formatter, UriDisplay}; + /// + /// struct Foo(usize); + /// + /// impl UriDisplay for Foo { + /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// f.write_value(&self.0) + /// } + /// } + /// + /// let foo = Foo(123); + /// let uri_string = format!("{}", &foo as &UriDisplay); + /// assert_eq!(uri_string, "123"); + /// ``` #[inline] pub fn write_value<T: UriDisplay>(&mut self, value: T) -> fmt::Result { - UriDisplay::fmt(&value, self) + self.refreshed(|f| UriDisplay::fmt(&value, f)) + } + + /// Refreshes the formatter. + /// + /// After refreshing, [`write_raw()`] will prefix any nested names as well + /// as insert an `&` separator. + /// + /// [`write_raw()`]: Formatter::write_raw() + /// + /// # Example + /// + /// ```rust + /// # #[macro_use] extern crate rocket; + /// use std::fmt; + /// + /// use rocket::http::uri::{Formatter, UriDisplay}; + /// + /// struct Foo; + /// + /// impl UriDisplay for Foo { + /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// f.write_raw("a")?; + /// f.write_raw("raw")?; + /// f.refresh(); + /// f.write_raw("format") + /// } + /// } + /// + /// #[derive(UriDisplay)] + /// struct Message { + /// inner: Foo, + /// } + /// + /// let msg = Message { inner: Foo }; + /// let uri_string = format!("{}", &msg as &UriDisplay); + /// assert_eq!(uri_string, "inner=araw&inner=format"); + /// ``` + #[inline(always)] + pub fn refresh(&mut self) { + self.fresh = true; } } diff --git a/core/http/src/uri/uri_display.rs b/core/http/src/uri/uri_display.rs @@ -2,7 +2,6 @@ use std::fmt; use std::path::{Path, PathBuf}; use std::borrow::Cow; -use smallvec::SmallVec; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use {RawStr, uri::{Uri, Formatter}, ext::Normalize}; @@ -35,9 +34,8 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// ```rust /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; -/// # type T = (); /// #[get("/item/<id>?<track>")] -/// fn get_item(id: i32, track: String) -> T { /* .. */ } +/// fn get_item(id: i32, track: String) { /* .. */ } /// ``` /// /// A URI for this route can be generated as follows: @@ -47,7 +45,7 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// # #[macro_use] extern crate rocket; /// # type T = (); /// # #[get("/item/<id>?<track>")] -/// # fn get_item(id: i32, track: String) -> T { /* .. */ } +/// # fn get_item(id: i32, track: String) { /* .. */ } /// # /// // With unnamed parameters. /// uri!(get_item: 100, "inbound"); @@ -67,7 +65,7 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// format!("/item/{}?track={}", &100 as &UriDisplay, &"inbound" as &UriDisplay); /// ``` /// -/// For this expression to typecheck, both `i32` and `Value` must implement +/// 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. /// @@ -92,6 +90,33 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// /// Uses the implementation of `UriDisplay` for `T`. /// +/// # Deriving +/// +/// Manually implementing `UriDisplay` should be done with care. For most use +/// cases, deriving `UriDisplay` will suffice: +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// # use rocket::http::uri::UriDisplay; +/// #[derive(FromForm, UriDisplay)] +/// struct User { +/// name: String, +/// age: usize, +/// } +/// +/// let user = User { name: "Michael Smith".into(), age: 31 }; +/// let uri_string = format!("{}", &user as &UriDisplay); +/// assert_eq!(uri_string, "name=Michael%20Smith&age=31"); +/// ``` +/// +/// As long as every field in the structure (or enum) implements `UriDisplay`, +/// the trait can be derived. The implementation calls +/// [`Formatter::write_named_value()`] for every named field and +/// [`Formatter::write_value()`] for every unnamed field. See the [`UriDisplay` +/// derive] documentation for full details. +/// +/// [`UriDisplay` derive]: ../../../rocket_codegen/derive.UriDisplay.html +/// /// # Implementing /// /// Implementing `UriDisplay` is similar to implementing @@ -112,27 +137,33 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// `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. +/// dynamic parameter type. Note the custom parsing in the `FromParam` +/// implementation; as a result of this, a custom (reflexive) `UriDisplay` +/// implementation is required. /// /// ```rust /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; -/// # fn main() { } /// use rocket::http::RawStr; /// use rocket::request::FromParam; /// /// struct Name(String); /// +/// const PREFIX: &str = "name:"; +/// /// impl<'r> FromParam<'r> for Name { /// type Error = &'r RawStr; /// -/// /// Validates parameters that contain no spaces. +/// /// Validates parameters that start with 'name:', extracting the text +/// /// after 'name:' as long as there is at least one character. /// fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { /// let decoded = param.percent_decode().map_err(|_| param)?; -/// match decoded.contains(' ') { -/// false => Ok(Name(decoded.into_owned())), -/// true => Err(param), +/// if !decoded.starts_with(PREFIX) || decoded.len() < (PREFIX.len() + 1) { +/// return Err(param); /// } +/// +/// let real_name = decoded[PREFIX.len()..].to_string(); +/// Ok(Name(real_name)) /// } /// } /// @@ -143,9 +174,9 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// 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. +/// /// be percent encoded. Prefixes the inner name with `name:`. /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { -/// f.write_value(&self.0) +/// f.write_value(&format!("name:{}", self.0)) /// } /// } /// @@ -158,6 +189,9 @@ use self::priv_encode_set::PATH_ENCODE_SET; /// fn real(name: Name) -> String { /// format!("Hello, {}!", name.0) /// } +/// +/// let uri = uri!(real: Name("Mike Smith".into())); +/// assert_eq!(uri.path(), "/name:Mike%20Smith"); /// ``` pub trait UriDisplay { /// Formats `self` in a URI-safe manner using the given formatter. @@ -166,14 +200,7 @@ pub trait UriDisplay { impl<'a> fmt::Display for &'a UriDisplay { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut formatter = Formatter { - prefixes: SmallVec::new(), - inner: f, - previous: false, - fresh: true, - }; - - UriDisplay::fmt(*self, &mut formatter) + UriDisplay::fmt(*self, &mut Formatter::new(f)) } }