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:
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))
}
}