Rocket

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

commit b7db74144f3c287642958a8bf0be623360e7773f
parent 543b07a4bad03a46704ce709c84481061e09613f
Author: Sergio Benitez <sb@sergio.bz>
Date:   Tue, 27 Nov 2018 10:01:47 -0600

Parameterize 'UriDisplay' with 'Path' or 'Query'.

This commit introduces the sealed `UriPart` marker trait as well as the
implementing `Path` and `Query` marker types, allowing for parts of a
URI to be distinguished at the type level. Consequently, `UriDisplay`
has been parameterized with `P: UriPart`, creating `UriDisplay<Path>`
and `UriDisplay<Query>`. The effect of this change is improved type
safely for URI rendering as well as the ability to omit rendering values
in query parts via `Option` and `Result`.

The `UriDisplay` derive was replaced by `UriDisplayQuery` and
`UriDisplayPath` which derive implementations for `UriDisplay<Path>`
and `UriDisplay<Query>`, respectively.

This commit also works around a rustdoc visibility issue by creating a
hidden `http::private` module.

Finally, this commit also removes the now vestigial use of the
`rustc_private` feature in codegen.

Fixes #827.

Diffstat:
Mcontrib/codegen/Cargo.toml | 2+-
Mcore/codegen/Cargo.toml | 2+-
Mcore/codegen/src/attribute/route.rs | 3++-
Mcore/codegen/src/attribute/segments.rs | 3+--
Mcore/codegen/src/bang/uri.rs | 167+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mcore/codegen/src/derive/from_form.rs | 2+-
Mcore/codegen/src/derive/from_form_value.rs | 2+-
Mcore/codegen/src/derive/responder.rs | 2+-
Mcore/codegen/src/derive/uri_display.rs | 38+++++++++++++++++++++++++++++++++-----
Mcore/codegen/src/http_codegen.rs | 9+++++----
Mcore/codegen/src/lib.rs | 59+++++++++++++++++++++++++++++++++++++++++------------------
Mcore/codegen/src/proc_macro_ext.rs | 3+--
Mcore/codegen/tests/route.rs | 2+-
Mcore/codegen/tests/typed-uris.rs | 6+++---
Mcore/codegen/tests/ui-fail/typed-uri-bad-type.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcore/codegen/tests/ui-fail/typed-uri-bad-type.stderr | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mcore/codegen/tests/ui-fail/typed-uris-bad-params.rs | 2+-
Mcore/codegen/tests/ui-fail/uri_display.rs | 30++++++++++++++++++++++++------
Mcore/codegen/tests/ui-fail/uri_display.stderr | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mcore/codegen/tests/ui-fail/uri_display_type_errors.rs | 28++++++++++++++++------------
Mcore/codegen/tests/ui-fail/uri_display_type_errors.stderr | 52++++++++++++++++++++++++++++++----------------------
Mcore/codegen/tests/uri_display.rs | 90++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mcore/http/src/content_type.rs | 3++-
Mcore/http/src/ext.rs | 2++
Mcore/http/src/lib.rs | 26++++++++++++++------------
Mcore/http/src/parse/indexed.rs | 1-
Mcore/http/src/parse/media_type.rs | 2+-
Mcore/http/src/route.rs | 10+++++++---
Mcore/http/src/uri/formatter.rs | 353++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mcore/http/src/uri/from_uri_param.rs | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mcore/http/src/uri/mod.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/http/src/uri/uri_display.rs | 218++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcore/lib/src/config/config.rs | 2+-
Mcore/lib/src/config/custom_values.rs | 2+-
Mcore/lib/src/local/client.rs | 2+-
Mcore/lib/src/request/form/form.rs | 4++--
Mcore/lib/src/request/form/lenient.rs | 4++--
Mcore/lib/src/request/request.rs | 8++++----
38 files changed, 1136 insertions(+), 410 deletions(-)

diff --git a/contrib/codegen/Cargo.toml b/contrib/codegen/Cargo.toml @@ -18,7 +18,7 @@ database_attribute = [] proc-macro = true [dependencies] -devise = "0.1" +devise = { git = "http://github.com/SergioBenitez/Devise", rev = "b5295e3e" } quote = "0.6" [build-dependencies] diff --git a/core/codegen/Cargo.toml b/core/codegen/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true indexmap = "1.0" quote = "0.6.1" rocket_http = { version = "0.4.0-rc.1", path = "../http/" } -devise = "0.1" +devise = { git = "http://github.com/SergioBenitez/Devise", rev = "b5295e3e" } [build-dependencies] yansi = { git = "https://github.com/SergioBenitez/yansi", rev = "59c3a91" } diff --git a/core/codegen/src/attribute/route.rs b/core/codegen/src/attribute/route.rs @@ -218,7 +218,8 @@ fn query_exprs(route: &Route) -> Option<TokenStream2> { let mut #ident: Option<#ty> = None; }, Kind::Multi => quote_spanned! { span => - let mut __trail = ::rocket::http::SmallVec::<[___r::FormItem; 8]>::new(); + let mut __trail = + ::rocket::http::private::SmallVec::<[___r::FormItem; 8]>::new(); }, Kind::Static => quote!() }; diff --git a/core/codegen/src/attribute/segments.rs b/core/codegen/src/attribute/segments.rs @@ -115,14 +115,13 @@ crate fn parse_segment(segment: &str, span: Span) -> PResult<Segment> { crate fn parse_segments( string: &str, - sep: char, source: Source, span: Span ) -> DResult<Vec<Segment>> { let mut segments = vec![]; let mut diags = Diagnostics::new(); - for result in RouteSegment::parse_many(string, sep, source) { + for result in RouteSegment::parse_many(string, source) { if let Err((segment_string, error)) = result { diags.push(into_diagnostic(segment_string, string, span, &error)); if let Error::Trailing(..) = error { diff --git a/core/codegen/src/bang/uri.rs b/core/codegen/src/bang/uri.rs @@ -81,6 +81,91 @@ fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> { } } +fn add_binding(to: &mut Vec<TokenStream2>, ident: &Ident, ty: &Type, expr: &Expr, source: Source) { + let uri_mod = quote!(rocket::http::uri); + let (span, ident_tmp) = (expr.span(), ident.prepend("tmp_")); + let from_uri_param = if source == Source::Query { + quote_spanned!(span => #uri_mod::FromUriParam<#uri_mod::Query, _>) + } else { + quote_spanned!(span => #uri_mod::FromUriParam<#uri_mod::Path, _>) + }; + + to.push(quote_spanned!(span => + let #ident_tmp = #expr; + let #ident = <#ty as #from_uri_param>::from_uri_param(#ident_tmp); + )); +} + +fn explode_path<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>( + uri: &Origin, + bindings: &mut Vec<TokenStream2>, + mut items: I +) -> TokenStream2 { + let (uri_mod, path) = (quote!(rocket::http::uri), uri.path()); + if !path.contains('<') { + return quote!(#uri_mod::UriArgumentsKind::Static(#path)); + } + + let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Path>); + let dyn_exprs = RouteSegment::parse_path(uri).map(|segment| { + let segment = segment.expect("segment okay; prechecked on parse"); + match segment.kind { + Kind::Static => { + let string = &segment.string; + quote!(&#string as &dyn #uri_display) + } + Kind::Single | Kind::Multi => { + let (ident, ty, expr) = items.next().expect("one item for each dyn"); + add_binding(bindings, &ident, &ty, &expr, Source::Path); + quote_spanned!(expr.span() => &#ident as &dyn #uri_display) + } + } + }); + + quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])) +} + +fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>( + uri: &Origin, + bindings: &mut Vec<TokenStream2>, + mut items: I +) -> Option<TokenStream2> { + let (uri_mod, query) = (quote!(rocket::http::uri), uri.query()?); + if !query.contains('<') { + return Some(quote!(#uri_mod::UriArgumentsKind::Static(#query))); + } + + let query_arg = quote!(#uri_mod::UriQueryArgument); + let uri_display = quote!(#uri_mod::UriDisplay<#uri_mod::Query>); + let dyn_exprs = RouteSegment::parse_query(uri)?.map(|segment| { + let segment = segment.expect("segment okay; prechecked on parse"); + match segment.kind { + Kind::Static => { + let string = &segment.string; + quote!(#query_arg::Raw(#string)) + } + Kind::Single => { + let (ident, ty, expr) = items.next().expect("one item for each dyn"); + add_binding(bindings, &ident, &ty, &expr, Source::Query); + let name = &segment.name; + + quote_spanned!(expr.span() => + #query_arg::NameValue(#name, &#ident as &dyn #uri_display) + ) + } + Kind::Multi => { + let (ident, ty, expr) = items.next().expect("one item for each dyn"); + add_binding(bindings, &ident, &ty, &expr, Source::Query); + quote_spanned!(expr.span() => + #query_arg::Value(&#ident as &dyn #uri_display) + ) + } + } + }); + + Some(quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))) +} + // Returns an Origin URI with the mount point and route path concatinated. The // query string is mangled by replacing single dynamic parameters in query parts // (`<param>`) with `param=<param>`. @@ -90,79 +175,33 @@ fn build_origin(internal: &InternalUriParams) -> Origin<'static> { .unwrap_or(""); let path = format!("{}/{}", mount_point, internal.route_uri.path()); - let query = RouteSegment::parse_query(&internal.route_uri).map(|segments| { - segments.map(|r| r.expect("invalid query segment")).map(|seg| { - match (seg.source, seg.kind) { - (Source::Query, Kind::Single) => format!("{k}=<{k}>", k = seg.name), - _ => seg.string.into_owned() - } - }).collect::<Vec<_>>().join("&") - }); - + let query = internal.route_uri.query(); Origin::new(path, query).to_normalized().into_owned() } -fn explode<'a, I>(route_str: &str, items: I) -> TokenStream2 - where I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)> -{ - // Generate the statements to typecheck each parameter. - // Building <$T as ::rocket::http::uri::FromUriParam<_>>::from_uri_param($e). - let (mut let_bindings, mut fmt_exprs) = (vec![], vec![]); - for (mut ident, ty, expr) in items { - let (span, expr) = (expr.span(), expr); - let ident_tmp = ident.prepend("tmp_"); - - let_bindings.push(quote_spanned!(span => - let #ident_tmp = #expr; - let #ident = <#ty as rocket::http::uri::FromUriParam<_>>::from_uri_param(#ident_tmp); - )); - - // generating: arg tokens for format string - fmt_exprs.push(quote_spanned! { span => - &#ident as &dyn rocket::http::uri::UriDisplay - }); - } - - // Convert all of the '<...>' into '{}'. - let mut inside = false; - let fmt_string: String = route_str.chars().filter_map(|c| { - Some(match c { - '<' => { inside = true; '{' } - '>' => { inside = false; '}' } - _ if !inside => c, - _ => return None - }) - }).collect(); - - // Don't allocate if there are no formatting expressions. - if fmt_exprs.is_empty() { - quote!({ #fmt_string.into() }) - } else { - quote!({ #(#let_bindings)* format!(#fmt_string, #(#fmt_exprs),*).into() }) - } -} - crate fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> { // Parse the internal invocation and the user's URI param expressions. let internal = syn::parse::<InternalUriParams>(input).map_err(syn_to_diag)?; let exprs = extract_exprs(&internal)?; - // Create an iterator over the `ident`, `ty`, and `expr` triple. - let mut arguments = internal.fn_args.iter() + // Create an iterator over all of the `ident`, `ty`, and `expr` triple. + let arguments = internal.fn_args.iter() .zip(exprs.iter()) .map(|(FnArg { ident, ty }, &expr)| (ident, ty, expr)); - // Generate an expression for the path and query. - let origin = build_origin(&internal); - let path_param_count = origin.path().matches('<').count(); - let path = explode(origin.path(), arguments.by_ref().take(path_param_count)); - let query = Optional(origin.query().map(|q| explode(q, arguments))); - - let span = internal.uri_params.route_path.span(); - Ok(quote_spanned!(span => { - rocket::http::uri::Origin::new::< - std::borrow::Cow<'static, str>, - std::borrow::Cow<'static, str>, - >(#path, #query) - }).into()) + // Create iterators for just the path and query parts. + let path_param_count = internal.route_uri.path().matches('<').count(); + let path_params = arguments.clone().take(path_param_count); + let query_params = arguments.skip(path_param_count); + + let mut bindings = vec![]; + let uri = build_origin(&internal); + let uri_mod = quote!(rocket::http::uri); + let path = explode_path(&uri, &mut bindings, path_params); + let query = Optional(explode_query(&uri, &mut bindings, query_params)); + + Ok(quote!({ + #(#bindings)* + #uri_mod::UriArguments { path: #path, query: #query, }.into_origin() + }).into()) } diff --git a/core/codegen/src/derive/from_form.rs b/core/codegen/src/derive/from_form.rs @@ -59,7 +59,7 @@ fn validate_struct(gen: &DeriveGenerator, data: Struct) -> Result<()> { pub fn derive_from_form(input: TokenStream) -> TokenStream { let form_error = quote!(::rocket::request::FormParseError); - DeriveGenerator::build_for(input, "::rocket::request::FromForm<'__f>") + DeriveGenerator::build_for(input, quote!(impl<'__f> ::rocket::request::FromForm<'__f>)) .generic_support(GenericSupport::Lifetime | GenericSupport::Type) .replace_generic(0, 0) .data_support(DataSupport::NamedStruct) diff --git a/core/codegen/src/derive/from_form_value.rs b/core/codegen/src/derive/from_form_value.rs @@ -7,7 +7,7 @@ struct Form { } pub fn derive_from_form_value(input: TokenStream) -> TokenStream { - DeriveGenerator::build_for(input, "::rocket::request::FromFormValue<'__v>") + DeriveGenerator::build_for(input, quote!(impl<'__v> ::rocket::request::FromFormValue<'__v>)) .generic_support(GenericSupport::None) .data_support(DataSupport::Enum) .validate_enum(|generator, data| { diff --git a/core/codegen/src/derive/responder.rs b/core/codegen/src/derive/responder.rs @@ -17,7 +17,7 @@ struct FieldAttr { } pub fn derive_responder(input: TokenStream) -> TokenStream { - DeriveGenerator::build_for(input, "::rocket::response::Responder<'__r>") + DeriveGenerator::build_for(input, quote!(impl<'__r> ::rocket::response::Responder<'__r>)) .generic_support(GenericSupport::Lifetime) .data_support(DataSupport::Struct | DataSupport::Enum) .replace_generic(0, 0) diff --git a/core/codegen/src/derive/uri_display.rs b/core/codegen/src/derive/uri_display.rs @@ -7,6 +7,7 @@ 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"; +const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field"; fn validate_fields(fields: Fields, parent_span: Span) -> Result<()> { if fields.count() == 0 { @@ -36,15 +37,17 @@ fn validate_enum(gen: &DeriveGenerator, data: Enum) -> Result<()> { Ok(()) } -pub fn derive_uri_display(input: TokenStream) -> TokenStream { - DeriveGenerator::build_for(input, "::rocket::http::uri::UriDisplay") +pub fn derive_uri_display_query(input: TokenStream) -> TokenStream { + let display_trait = quote!(::rocket::http::uri::UriDisplay<::rocket::http::uri::Query>); + let formatter = quote!(::rocket::http::uri::Formatter<::rocket::http::uri::Query>); + DeriveGenerator::build_for(input, quote!(impl #display_trait)) .generic_support(GenericSupport::Type | GenericSupport::Lifetime) .data_support(DataSupport::Struct | DataSupport::Enum) .validate_enum(validate_enum) .validate_struct(validate_struct) - .map_type_generic(|_, ident, _| quote!(#ident : ::rocket::http::uri::UriDisplay)) - .function(|_, inner| quote! { - fn fmt(&self, f: &mut ::rocket::http::uri::Formatter) -> ::std::fmt::Result { + .map_type_generic(move |_, ident, _| quote!(#ident : #display_trait)) + .function(move |_, inner| quote! { + fn fmt(&self, f: &mut #formatter) -> ::std::fmt::Result { #inner Ok(()) } @@ -66,3 +69,28 @@ pub fn derive_uri_display(input: TokenStream) -> TokenStream { }) .to_tokens() } + +pub fn derive_uri_display_path(input: TokenStream) -> TokenStream { + let display_trait = quote!(::rocket::http::uri::UriDisplay<::rocket::http::uri::Path>); + let formatter = quote!(::rocket::http::uri::Formatter<::rocket::http::uri::Path>); + DeriveGenerator::build_for(input, quote!(impl #display_trait)) + .data_support(DataSupport::TupleStruct) + .generic_support(GenericSupport::Type | GenericSupport::Lifetime) + .map_type_generic(move |_, ident, _| quote!(#ident : #display_trait)) + .validate_fields(|_, fields| match fields.count() { + 1 => Ok(()), + _ => Err(fields.span().error(EXACTLY_ONE_FIELD)) + }) + .function(move |_, inner| quote! { + fn fmt(&self, f: &mut #formatter) -> ::std::fmt::Result { + #inner + Ok(()) + } + }) + .map_field(|_, field| { + let span = field.span().into(); + let accessor = field.accessor(); + quote_spanned!(span => f.write_value(&#accessor)?;) + }) + .to_tokens() +} diff --git a/core/codegen/src/http_codegen.rs b/core/codegen/src/http_codegen.rs @@ -95,12 +95,13 @@ impl ToTokens for MediaType { let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str()); let (keys, values) = self.0.params().split2(); - let (http, cow) = (quote!(::rocket::http), quote!(::std::borrow::Cow)); + let cow = quote!(::std::borrow::Cow); + let (pub_http, http) = (quote!(::rocket::http), quote!(::rocket::http::private)); let (http_, http__) = (repeat(&http), repeat(&http)); let (cow_, cow__) = (repeat(&cow), repeat(&cow)); // TODO: Produce less code when possible (for known media types). - tokens.extend(quote!(#http::MediaType { + tokens.extend(quote!(#pub_http::MediaType { source: #http::Source::None, top: #http::Indexed::Concrete(#cow::Borrowed(#top)), sub: #http::Indexed::Concrete(#cow::Borrowed(#sub)), @@ -216,7 +217,7 @@ impl FromMeta for RoutePath { fn from_meta(meta: MetaItem) -> Result<Self> { let (origin, string) = (Origin::from_meta(meta)?, StringLit::from_meta(meta)?); let path_span = string.subspan(1..origin.0.path().len() + 1).expect("path"); - let path = parse_segments(origin.0.path(), '/', Source::Path, path_span); + let path = parse_segments(origin.0.path(), Source::Path, path_span); let query = origin.0.query() .map(|q| { @@ -227,7 +228,7 @@ impl FromMeta for RoutePath { // TODO: Show a help message with what's expected. Err(query_span.error("query cannot contain empty segments").into()) } else { - parse_segments(q, '&', Source::Query, query_span) + parse_segments(q, Source::Query, query_span) } }).transpose(); diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs @@ -1,7 +1,6 @@ #![feature(proc_macro_diagnostic, proc_macro_span)] #![feature(crate_visibility_modifier)] #![feature(transpose_result)] -#![feature(rustc_private)] #![recursion_limit="128"] #![doc(html_root_url = "https://api.rocket.rs/v0.4")] @@ -65,8 +64,6 @@ extern crate proc_macro; extern crate rocket_http as http; extern crate indexmap; -extern crate syntax_pos; - #[macro_use] mod proc_macro_ext; mod derive; mod attribute; @@ -616,21 +613,21 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { emit!(derive::responder::derive_responder(input)) } -/// Derive for the [`UriDisplay`] trait. +/// Derive for the [`UriDisplay<Query>`] 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. +/// The [`UriDisplay<Query>`] 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)] +/// #[derive(UriDisplayQuery)] /// enum Kind { /// A(String), /// B(usize), /// } /// -/// #[derive(UriDisplay)] +/// #[derive(UriDisplayQuery)] /// struct MyStruct { /// name: String, /// id: usize, @@ -638,10 +635,10 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// Each field's type is required to implement [`UriDisplay`]. +/// Each field's type is required to implement [`UriDisplay<Query>`]. /// -/// The derive generates an implementation of the [`UriDisplay`] trait. The -/// implementation calls [`Formatter::write_named_value()`] for every named +/// The derive generates an implementation of the [`UriDisplay<Query>`] 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. @@ -658,9 +655,9 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// /// ```rust /// # #[macro_use] extern crate rocket; -/// # #[derive(UriDisplay)] +/// # #[derive(UriDisplayQuery)] /// # struct Kind(String); -/// #[derive(UriDisplay)] +/// #[derive(UriDisplayQuery)] /// struct MyStruct { /// name: String, /// id: usize, @@ -675,12 +672,38 @@ pub fn derive_responder(input: TokenStream) -> TokenStream { /// the example above, the field `MyStruct::kind` is rendered with a name of /// `type`. /// -/// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html +/// [`UriDisplay<Query>`]: ../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)) +#[proc_macro_derive(UriDisplayQuery, attributes(form))] +pub fn derive_uri_display_query(input: TokenStream) -> TokenStream { + emit!(derive::uri_display::derive_uri_display_query(input)) +} + +/// Derive for the [`UriDisplay<Path>`] trait. +/// +/// The [`UriDisplay<Path>`] derive can only be applied to tuple structs with +/// one field. +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// #[derive(UriDisplayPath)] +/// struct Name(String); +/// +/// #[derive(UriDisplayPath)] +/// struct Age(usize); +/// ``` +/// +/// The field's type is required to implement [`UriDisplay<Path>`]. +/// +/// The derive generates an implementation of the [`UriDisplay<Path>`] trait. +/// The implementation calls [`Formatter::write_value()`] for the field. +/// +/// [`UriDisplay<Path>`]: ../rocket/http/uri/trait.UriDisplay.html +/// [`Formatter::write_value()`]: ../rocket/http/uri/struct.Formatter.html#method.write_value +#[proc_macro_derive(UriDisplayPath)] +pub fn derive_uri_display_path(input: TokenStream) -> TokenStream { + emit!(derive::uri_display::derive_uri_display_path(input)) } /// Generates a [`Vec`] of [`Route`]s from a set of route paths. diff --git a/core/codegen/src/proc_macro_ext.rs b/core/codegen/src/proc_macro_ext.rs @@ -1,7 +1,6 @@ -use std::ops::{Bound, RangeBounds}; +use std::ops::RangeBounds; use proc_macro::{Span, Diagnostic, Literal}; -use syntax_pos::{Span as InnerSpan, Pos, BytePos}; pub type PResult<T> = ::std::result::Result<T, Diagnostic>; diff --git a/core/codegen/tests/route.rs b/core/codegen/tests/route.rs @@ -13,7 +13,7 @@ use rocket::http::{Status, RawStr, ContentType}; // Use all of the code generation avaiable at once. -#[derive(FromForm, UriDisplay)] +#[derive(FromForm, UriDisplayQuery)] struct Inner<'r> { field: &'r RawStr } diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs @@ -6,16 +6,16 @@ use std::path::PathBuf; use rocket::http::{RawStr, Cookies}; -use rocket::http::uri::{Origin, FromUriParam}; +use rocket::http::uri::{Origin, FromUriParam, Query}; use rocket::request::Form; -#[derive(FromForm, UriDisplay)] +#[derive(FromForm, UriDisplayQuery)] struct User<'a> { name: &'a RawStr, nickname: String, } -impl<'a, 'b> FromUriParam<(&'a str, &'b str)> for User<'a> { +impl<'a, 'b> FromUriParam<Query, (&'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() } diff --git a/core/codegen/tests/ui-fail/typed-uri-bad-type.rs b/core/codegen/tests/ui-fail/typed-uri-bad-type.rs @@ -21,9 +21,62 @@ fn not_uri_display(id: i32, name: S) { } #[post("/<id>/<name>")] fn not_uri_display_but_unused(id: i32, name: S) { } +#[post("/<id>/<name>")] +fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { } + +use rocket::request::{Query, FromQuery}; + +impl<'q> FromQuery<'q> for S { + type Error = (); + fn from_query(query: Query<'q>) -> Result<Self, Self::Error> { Ok(S) } +} + +#[post("/?<id>")] +fn simple_q(id: i32) { } + +#[post("/?<id>&<rest..>")] +fn other_q(id: usize, rest: S) { } + +#[post("/?<id>&<name>")] +fn optionals_q(id: Option<i32>, name: Result<String, &RawStr>) { } + fn main() { - uri!(simple: id = "hi"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str> - uri!(simple: "hello"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str> - uri!(simple: id = 239239i64); //~ ERROR i32: rocket::http::uri::FromUriParam<i64> - uri!(not_uri_display: 10, S); //~ ERROR S: rocket::http::uri::FromUriParam<_> + uri!(simple: id = "hi"); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> + + uri!(simple: "hello"); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> + + uri!(simple: id = 239239i64); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64> + + uri!(not_uri_display: 10, S); + //~^ ERROR S: rocket::http::uri::FromUriParam<rocket::http::uri::Path, _> + + // This one is okay. In paths, a value _must_ be supplied. + uri!(optionals: id = 10, name = "bob".to_string()); + + uri!(optionals: id = Some(10), name = Ok("bob".into())); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>> + //~^^ ERROR String: rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>> + + uri!(simple_q: "hi"); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str> + + uri!(simple_q: id = "hi"); + //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str> + + uri!(other_q: 100, S); + //~^ ERROR S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _> + + uri!(other_q: rest = S, id = 100); + //~^ ERROR S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _> + + // This one is okay. + uri!(optionals_q: None, Err("foo".into())); + + // For queries, we need to know the exact variant. + uri!(optionals_q: id = 10, name = "Bob".to_string()); + //~^ ERROR Option<i32>: rocket::http::uri::FromUriParam<rocket::http::uri::Query, {integer}> + //~^^ ERROR: Result<std::string::String, &rocket::http::RawStr>: rocket::http::uri::FromUriParam<rocket::http::uri::Query, std::string::String> } diff --git a/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr @@ -1,33 +1,99 @@ -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<&str>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:25:23 +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:44:23 | -25 | uri!(simple: id = "hi"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str> - | ^^^^ the trait `rocket::http::uri::FromUriParam<&str>` is not implemented for `i32` +44 | uri!(simple: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `i32` | = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<&str>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:26:18 +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:47:18 | -26 | uri!(simple: "hello"); //~ ERROR i32: rocket::http::uri::FromUriParam<&str> - | ^^^^^^^ the trait `rocket::http::uri::FromUriParam<&str>` is not implemented for `i32` +47 | uri!(simple: "hello"); + | ^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `i32` | = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<i64>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:27:23 +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:50:23 | -27 | uri!(simple: id = 239239i64); //~ ERROR i32: rocket::http::uri::FromUriParam<i64> - | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam<i64>` is not implemented for `i32` +50 | uri!(simple: id = 239239i64); + | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `i32` | = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<_>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:28:31 +error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<rocket::http::uri::Path, _>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:53:31 | -28 | uri!(not_uri_display: 10, S); //~ ERROR S: rocket::http::uri::FromUriParam<_> - | ^ the trait `rocket::http::uri::FromUriParam<_>` is not implemented for `S` +53 | uri!(not_uri_display: 10, S); + | ^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S` -error: aborting due to 4 previous errors +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:59:26 + | +59 | uri!(optionals: id = Some(10), name = Ok("bob".into())); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32` + | + = note: required because of the requirements on the impl of `rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>` + +error[E0277]: the trait bound `std::string::String: rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:59:43 + | +59 | uri!(optionals: id = Some(10), name = Ok("bob".into())); + | ^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` is not implemented for `std::string::String` + | + = help: the following implementations were found: + <std::string::String as rocket::http::uri::FromUriParam<P, &'a str>> + = note: required because of the requirements on the impl of `rocket::http::uri::FromUriParam<rocket::http::uri::Path, std::result::Result<_, _>>` for `std::result::Result<std::string::String, &rocket::http::RawStr>` + +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:63:20 + | +63 | uri!(simple_q: "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `i32` + | + = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + +error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:66:25 + | +66 | uri!(simple_q: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `i32` + | + = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + +error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:69:24 + | +69 | uri!(other_q: 100, S); + | ^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S` + +error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:72:26 + | +72 | uri!(other_q: rest = S, id = 100); + | ^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S` + +error[E0277]: the trait bound `std::option::Option<i32>: rocket::http::uri::FromUriParam<rocket::http::uri::Query, {integer}>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:79:28 + | +79 | uri!(optionals_q: id = 10, name = "Bob".to_string()); + | ^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, {integer}>` is not implemented for `std::option::Option<i32>` + | + = help: the following implementations were found: + <std::option::Option<T> as rocket::http::uri::FromUriParam<rocket::http::uri::Path, A>> + = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + +error[E0277]: the trait bound `std::result::Result<std::string::String, &rocket::http::RawStr>: rocket::http::uri::FromUriParam<rocket::http::uri::Query, std::string::String>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:79:39 + | +79 | uri!(optionals_q: id = 10, name = "Bob".to_string()); + | ^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, std::string::String>` is not implemented for `std::result::Result<std::string::String, &rocket::http::RawStr>` + | + = help: the following implementations were found: + <std::result::Result<T, E> as rocket::http::uri::FromUriParam<rocket::http::uri::Path, A>> + = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + +error: aborting due to 12 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/core/codegen/tests/ui-fail/typed-uris-bad-params.rs b/core/codegen/tests/ui-fail/typed-uris-bad-params.rs @@ -12,7 +12,7 @@ fn has_one(id: i32) { } #[post("/<id>")] fn has_one_guarded(cookies: Cookies, id: i32) { } -#[post("/<id>/<name>")] +#[post("/<id>?<name>")] fn has_two(cookies: Cookies, id: i32, name: String) { } fn main() { diff --git a/core/codegen/tests/ui-fail/uri_display.rs b/core/codegen/tests/ui-fail/uri_display.rs @@ -1,30 +1,48 @@ #[macro_use] extern crate rocket; -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Foo1; //~^ ERROR not supported -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Foo2(); //~^ ERROR not supported -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] enum Foo3 { } //~^ ERROR not supported -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] enum Foo4 { Variant, //~^ ERROR not supported } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Foo5(String, String); //~^ ERROR exactly one -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Foo6 { #[form(field = 123)] //~^ ERROR invalid value: expected string field: String, } + +#[derive(UriDisplayPath)] +struct Foo7(String, usize); +//~^ ERROR exactly one + +#[derive(UriDisplayPath)] +struct Foo8; +//~^ ERROR exactly one + +#[derive(UriDisplayPath)] +enum Foo9 { } +//~^ ERROR not supported + +#[derive(UriDisplayPath)] +struct Foo10 { +//~^ ERROR not supported + named: usize +} diff --git a/core/codegen/tests/ui-fail/uri_display.stderr b/core/codegen/tests/ui-fail/uri_display.stderr @@ -7,8 +7,8 @@ error: fieldless structs or variants are not supported note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:3:10 | -3 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +3 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ error: fieldless structs or variants are not supported --> $DIR/uri_display.rs:8:1 @@ -19,8 +19,8 @@ error: fieldless structs or variants are not supported note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:7:10 | -7 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +7 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ error: empty enums are not supported --> $DIR/uri_display.rs:12:1 @@ -31,8 +31,8 @@ error: empty enums are not supported note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:11:10 | -11 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +11 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ error: fieldless structs or variants are not supported --> $DIR/uri_display.rs:17:5 @@ -43,8 +43,8 @@ error: fieldless structs or variants are not supported note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:15:10 | -15 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +15 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ error: tuple structs or variants must have exactly one field --> $DIR/uri_display.rs:22:12 @@ -55,8 +55,8 @@ error: tuple structs or variants must have exactly one field note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:21:10 | -21 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +21 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ error: invalid value: expected string literal --> $DIR/uri_display.rs:27:20 @@ -67,8 +67,59 @@ error: invalid value: expected string literal note: error occurred while deriving `UriDisplay` --> $DIR/uri_display.rs:25:10 | -25 | #[derive(UriDisplay)] - | ^^^^^^^^^^ +25 | #[derive(UriDisplayQuery)] + | ^^^^^^^^^^^^^^^ -error: aborting due to 6 previous errors +error: struct must have exactly one field + --> $DIR/uri_display.rs:33:12 + | +33 | struct Foo7(String, usize); + | ^^^^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:32:10 + | +32 | #[derive(UriDisplayPath)] + | ^^^^^^^^^^^^^^ + +error: struct must have exactly one field + --> $DIR/uri_display.rs:37:1 + | +37 | struct Foo8; + | ^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:36:10 + | +36 | #[derive(UriDisplayPath)] + | ^^^^^^^^^^^^^^ + +error: enums are not supported + --> $DIR/uri_display.rs:41:1 + | +41 | enum Foo9 { } + | ^^^^^^^^^^^^^^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:40:10 + | +40 | #[derive(UriDisplayPath)] + | ^^^^^^^^^^^^^^ + +error: named structs are not supported + --> $DIR/uri_display.rs:45:1 + | +45 | / struct Foo10 { +46 | | //~^ ERROR not supported +47 | | named: usize +48 | | } + | |_^ + | +note: error occurred while deriving `UriDisplay` + --> $DIR/uri_display.rs:44:10 + | +44 | #[derive(UriDisplayPath)] + | ^^^^^^^^^^^^^^ + +error: aborting due to 10 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 @@ -2,44 +2,48 @@ struct BadType; -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Bar1(BadType); -//~^ ERROR UriDisplay +//~^ ERROR UriDisplay<rocket::http::uri::Query> -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Bar2 { field: BadType, - //~^ ERROR UriDisplay + //~^ ERROR UriDisplay<rocket::http::uri::Query> } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Bar3 { field: String, bad: BadType, - //~^ ERROR UriDisplay + //~^ ERROR UriDisplay<rocket::http::uri::Query> } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] enum Bar4 { Inner(BadType), - //~^ ERROR UriDisplay + //~^ ERROR UriDisplay<rocket::http::uri::Query> } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] enum Bar5 { Inner { field: BadType, - //~^ ERROR UriDisplay + //~^ ERROR UriDisplay<rocket::http::uri::Query> }, } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] enum Bar6 { Inner { field: String, other: BadType, - //~^ ERROR UriDisplay + //~^ ERROR UriDisplay<rocket::http::uri::Query> }, } +#[derive(UriDisplayPath)] +struct Baz(BadType); +//~^ ERROR UriDisplay<rocket::http::uri::Path> + 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 @@ -1,54 +1,62 @@ -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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<rocket::http::uri::Query>` for `&BadType` -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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<rocket::http::uri::Query>` for `&BadType` -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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<rocket::http::uri::Query>` for `&BadType` -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&&BadType` -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&&BadType` -error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay` is not satisfied +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + | ^^^^^^^^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` 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` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&BadType` + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Query>` for `&&BadType` -error: aborting due to 6 previous errors +error[E0277]: the trait bound `BadType: rocket::http::uri::UriDisplay<rocket::http::uri::Path>` is not satisfied + --> $DIR/uri_display_type_errors.rs:46:12 + | +46 | struct Baz(BadType); + | ^^^^^^^ the trait `rocket::http::uri::UriDisplay<rocket::http::uri::Path>` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `rocket::http::uri::UriDisplay<rocket::http::uri::Path>` for `&BadType` + +error: aborting due to 7 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 @@ -3,16 +3,16 @@ #[macro_use] extern crate rocket; use rocket::http::RawStr; -use rocket::http::uri::UriDisplay; +use rocket::http::uri::{UriDisplay, Query, Path}; -macro_rules! assert_uri_display { +macro_rules! assert_uri_display_query { ($v:expr, $s:expr) => ( - let uri_string = format!("{}", &$v as &UriDisplay); + let uri_string = format!("{}", &$v as &UriDisplay<Query>); assert_eq!(uri_string, $s); ) } -#[derive(UriDisplay, Clone)] +#[derive(UriDisplayQuery, Clone)] enum Foo<'r> { First(&'r RawStr), Second { @@ -28,25 +28,25 @@ enum Foo<'r> { #[test] fn uri_display_foo() { let foo = Foo::First("hello".into()); - assert_uri_display!(foo, "hello"); + assert_uri_display_query!(foo, "hello"); let foo = Foo::First("hello there".into()); - assert_uri_display!(foo, "hello%20there"); + assert_uri_display_query!(foo, "hello%20there"); let foo = Foo::Second { inner: "hi".into(), other: 123 }; - assert_uri_display!(foo, "inner=hi&other=123"); + assert_uri_display_query!(foo, "inner=hi&other=123"); let foo = Foo::Second { inner: "hi bo".into(), other: 321 }; - assert_uri_display!(foo, "inner=hi%20bo&other=321"); + assert_uri_display_query!(foo, "inner=hi%20bo&other=321"); let foo = Foo::Third { kind: "hello".into() }; - assert_uri_display!(foo, "type=hello"); + assert_uri_display_query!(foo, "type=hello"); let foo = Foo::Third { kind: "hello there".into() }; - assert_uri_display!(foo, "type=hello%20there"); + assert_uri_display_query!(foo, "type=hello%20there"); } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Bar<'a> { foo: Foo<'a>, baz: String, @@ -56,18 +56,18 @@ struct Bar<'a> { 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!"); + assert_uri_display_query!(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"); + assert_uri_display_query!(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"); + assert_uri_display_query!(bar, "foo.type=hello&baz=turkey%20day"); } -#[derive(UriDisplay)] +#[derive(UriDisplayQuery)] struct Baz<'a> { foo: Foo<'a>, bar: Bar<'a>, @@ -80,7 +80,7 @@ fn uri_display_baz() { 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&\ + assert_uri_display_query!(baz, "foo.inner=hi&foo.other=123&\ bar.foo.inner=bye&bar.foo.other=321&bar.baz=done&\ last=ok"); @@ -88,7 +88,63 @@ fn uri_display_baz() { 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&\ + assert_uri_display_query!(baz, "foo=bye&\ bar.foo.type=hello&bar.baz=end&\ last=done"); } + +#[derive(UriDisplayQuery)] +struct Bam<'a> { + foo: &'a str, + bar: Option<usize>, + baz: Result<&'a RawStr, usize>, +} + +#[test] +fn uri_display_bam() { + let bam = Bam { foo: "hi hi", bar: Some(1), baz: Err(2) }; + assert_uri_display_query!(bam, "foo=hi%20hi&bar=1"); + + let bam = Bam { foo: "hi hi", bar: None, baz: Err(2) }; + assert_uri_display_query!(bam, "foo=hi%20hi"); + + let bam = Bam { foo: "hi hi", bar: Some(1), baz: Ok("tony".into()) }; + assert_uri_display_query!(bam, "foo=hi%20hi&bar=1&baz=tony"); + + let bam = Bam { foo: "hi hi", bar: None, baz: Ok("tony".into()) }; + assert_uri_display_query!(bam, "foo=hi%20hi&baz=tony"); +} + +macro_rules! assert_uri_display_path { + ($v:expr, $s:expr) => ( + let uri_string = format!("{}", &$v as &UriDisplay<Path>); + assert_eq!(uri_string, $s); + ) +} + +#[derive(UriDisplayPath)] +struct FooP(&'static str); + +#[derive(UriDisplayPath)] +struct BarP<'a>(&'a str); + +#[derive(UriDisplayPath)] +struct BazP<'a, T>(&'a T); + +#[derive(UriDisplayPath)] +struct BamP<T>(T); + +#[derive(UriDisplayPath)] +struct BopP(FooP); + +#[test] +fn uri_display_path() { + assert_uri_display_path!(FooP("hi"), "hi"); + assert_uri_display_path!(FooP("hi there"), "hi%20there"); + assert_uri_display_path!(BarP("hi there"), "hi%20there"); + assert_uri_display_path!(BazP(&FooP("hi")), "hi"); + assert_uri_display_path!(BazP(&BarP("hi there")), "hi%20there"); + assert_uri_display_path!(BamP(12), "12"); + assert_uri_display_path!(BamP(BazP(&100)), "100"); + assert_uri_display_path!(BopP(FooP("bop foo")), "bop%20foo"); +} diff --git a/core/http/src/content_type.rs b/core/http/src/content_type.rs @@ -3,7 +3,8 @@ use std::ops::Deref; use std::str::FromStr; use std::fmt; -use {Header, MediaType, Source}; +use header::Header; +use media_type::{MediaType, Source}; use ext::IntoCollection; use hyper::mime::Mime; diff --git a/core/http/src/ext.rs b/core/http/src/ext.rs @@ -101,6 +101,8 @@ impl<'a, B: 'static + ToOwned + ?Sized> IntoOwned for Cow<'a, B> { use std::path::Path; +// Outside of http, this is used by a test. +#[doc(hidden)] pub trait Normalize { fn normalized_str(&self) -> Cow<str>; } diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs @@ -2,6 +2,7 @@ #![feature(proc_macro_hygiene)] #![feature(try_from)] #![feature(crate_visibility_modifier)] +#![feature(doc_cfg)] #![recursion_limit="512"] //! Types that map to concepts in HTTP. @@ -12,11 +13,9 @@ //! //! [#17]: https://github.com/SergioBenitez/Rocket/issues/17 -#[macro_use] -extern crate pear; +#[macro_use] extern crate pear; +#[macro_use] extern crate percent_encoding; extern crate smallvec; -#[doc(hidden)] #[macro_use] -pub extern crate percent_encoding; extern crate cookie; extern crate time; extern crate indexmap; @@ -51,15 +50,18 @@ crate mod parse; pub mod uncased; -// We need to export these for codegen, but otherwise it's unnecessary. -// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817) -// FIXME(rustc): These show up in the rexported module. -#[doc(hidden)] pub use parse::Indexed; -#[doc(hidden)] pub use media_type::{MediaParams, Source}; -#[doc(hidden)] pub use smallvec::{SmallVec, Array}; +#[doc(hidden)] +pub mod private { + // We need to export these for codegen, but otherwise it's unnecessary. + // TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817) + // FIXME(rustc): These show up in the rexported module. + pub use parse::Indexed; + pub use media_type::{MediaParams, Source}; + pub use smallvec::{SmallVec, Array}; -// This one we need to expose for core. -#[doc(hidden)] pub use cookies::{Key, CookieJar}; + // This one we need to expose for core. + pub use cookies::{Key, CookieJar}; +} pub use method::Method; pub use content_type::ContentType; diff --git a/core/http/src/parse/indexed.rs b/core/http/src/parse/indexed.rs @@ -30,7 +30,6 @@ impl AsPtr for [u8] { } } - #[derive(PartialEq)] pub enum Indexed<'a, T: ?Sized + ToOwned + 'a> { Indexed(usize, usize), diff --git a/core/http/src/parse/media_type.rs b/core/http/src/parse/media_type.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use pear::{parser, switch}; use pear::parsers::*; -use {MediaType, Source}; +use media_type::{MediaType, Source}; use parse::checkers::{is_whitespace, is_valid_token}; use parse::IndexedStr; diff --git a/core/http/src/route.rs b/core/http/src/route.rs @@ -128,9 +128,13 @@ impl<'a> RouteSegment<'a> { pub fn parse_many( string: &str, - sep: char, source: Source, ) -> impl Iterator<Item = SResult> { + let sep = match source { + Source::Query => '&', + _ => '/', + }; + let mut last_multi_seg: Option<&str> = None; string.split(sep).filter(|s| !s.is_empty()).enumerate().map(move |(i, seg)| { if let Some(multi_seg) = last_multi_seg { @@ -149,10 +153,10 @@ impl<'a> RouteSegment<'a> { } pub fn parse_path(uri: &'a Origin) -> impl Iterator<Item = SResult<'a>> { - Self::parse_many(uri.path(), '/', Source::Path) + Self::parse_many(uri.path(), Source::Path) } pub fn parse_query(uri: &'a Origin) -> Option<impl Iterator<Item = SResult<'a>>> { - uri.query().map(|q| Self::parse_many(q, '&', Source::Query)) + uri.query().map(|q| Self::parse_many(q, Source::Query)) } } diff --git a/core/http/src/uri/formatter.rs b/core/http/src/uri/formatter.rs @@ -1,16 +1,35 @@ -use std::fmt; +use std::fmt::{self, Write}; +use std::marker::PhantomData; use smallvec::SmallVec; -use uri::UriDisplay; +use uri::{UriPart, Path, Query, UriDisplay, Origin}; /// A struct used to format strings for [`UriDisplay`]. /// +/// # Marker Generic: `Formatter<Path>` vs. `Formatter<Query>` +/// +/// Like [`UriDisplay`], the [`UriPart`] parameter `P` in `Formatter<P>` must be +/// either [`Path`] or [`Query`] resulting in either `Formatter<Path>` or +/// `Formatter<Query>`. The `Path` version is used when formatting parameters +/// in the path part of the URI while the `Query` version is used when +/// formatting parameters in the query part of the URI. The +/// [`write_named_value()`] method is only available to `UriDisplay<Query>`. +/// +/// [`UriPart`]: uri::UriPart +/// [`Path`]: uri::Path +/// [`Query`]: uri::Query +/// +/// # Overview +/// /// 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: +/// struct properly formats series of values for use in URIs. In particular, +/// this struct applies the following transformations: /// -/// * When **mutliple values** are written, they are separated by `&`. +/// * When **mutliple values** are written, they are separated by `/` for +/// `Path` types and `&` for `Query` types. +/// +/// Additionally, for `Formatter<Query>`: /// /// * When a **named value** is written with [`write_named_value()`], the name /// is written out, followed by a `=`, followed by the value. @@ -36,8 +55,8 @@ use uri::UriDisplay; /// 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 +/// every time it is called. Instead, it only prefixes 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() @@ -45,15 +64,15 @@ use uri::UriDisplay; /// # 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 +/// display the semantics of `Formatter<Query>`. 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}; +/// use rocket::http::uri::{Formatter, UriDisplay, Query}; /// /// struct Outer { /// value: Inner, @@ -66,8 +85,8 @@ use uri::UriDisplay; /// extra: usize /// } /// -/// impl UriDisplay for Outer { -/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// impl UriDisplay<Query> for Outer { +/// fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { /// f.write_named_value("outer_field", &self.value)?; /// f.write_named_value("another", &self.another)?; /// f.write_raw("out")?; @@ -76,8 +95,8 @@ use uri::UriDisplay; /// } /// } /// -/// impl UriDisplay for Inner { -/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// impl UriDisplay<Query> for Inner { +/// fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { /// f.write_named_value("inner_field", &self.value)?; /// f.write_value(&self.extra)?; /// f.write_raw("inside") @@ -86,7 +105,7 @@ use uri::UriDisplay; /// /// let inner = Inner { value: 0, extra: 1 }; /// let outer = Outer { value: inner, another: 2, extra: 3 }; -/// let uri_string = format!("{}", &outer as &UriDisplay); +/// let uri_string = format!("{}", &outer as &UriDisplay<Query>); /// assert_eq!(uri_string, "outer_field.inner_field=0&\ /// outer_field=1&\ /// outer_field=inside&\ @@ -104,58 +123,66 @@ use uri::UriDisplay; /// # #[macro_use] extern crate rocket; /// use std::fmt::{self, Write}; /// -/// use rocket::http::uri::{Formatter, UriDisplay}; +/// use rocket::http::uri::{UriDisplay, Formatter, UriPart, Path, Query}; /// /// pub struct Complex(u8, u8); /// -/// impl UriDisplay for Complex { -/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// impl<P: UriPart> UriDisplay<P> for Complex { +/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { /// write!(f, "{}+{}", self.0, self.1) /// } /// } /// -/// #[derive(UriDisplay)] +/// let uri_string = format!("{}", &Complex(42, 231) as &UriDisplay<Path>); +/// assert_eq!(uri_string, "42+231"); +/// +/// #[derive(UriDisplayQuery)] /// 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"); +/// let message = Message { number: Complex(42, 47) }; +/// let uri_string = format!("{}", &message as &UriDisplay<Query>); +/// assert_eq!(uri_string, "number=42+47"); /// ``` /// /// [`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>, - crate previous: bool, - crate fresh: bool +pub struct Formatter<'i, P: UriPart> { + prefixes: SmallVec<[&'static str; 3]>, + inner: &'i mut (dyn Write + 'i), + previous: bool, + fresh: bool, + delimiter: char, + _marker: PhantomData<P>, +} + +impl<'i> Formatter<'i, Path> { + #[inline(always)] + crate fn new(inner: &'i mut (dyn Write + 'i)) -> Self { + Formatter::make(inner, '/') + } +} + +impl<'i> Formatter<'i, Query> { + #[inline(always)] + crate fn new(inner: &'i mut (dyn Write + 'i)) -> Self { + Formatter::make(inner, '&') + } } -impl<'i, 'f: 'i> Formatter<'i, 'f> { - crate fn new(formatter: &'i mut fmt::Formatter<'f>) -> Self { +impl<'i, P: UriPart> Formatter<'i, P> { + #[inline(always)] + fn make(inner: &'i mut (dyn Write + 'i), delimiter: char) -> Self { Formatter { + inner, delimiter, prefixes: SmallVec::new(), - inner: formatter, previous: false, fresh: true, + _marker: PhantomData, } } - fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result - where F: FnOnce(&mut Self) -> fmt::Result - { - // 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(always)] fn refreshed<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, f: F) -> fmt::Result { self.refresh(); @@ -167,7 +194,7 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// Writes `string` to `self`. /// /// If `self` is _fresh_ (after a call to other `write_` methods or - /// [`refresh()`]), prefixes any names as necessary. + /// [`refresh()`]), prefixes any names and adds separators as necessary. /// /// This method is called by the `write!` macro. /// @@ -179,12 +206,12 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// # extern crate rocket; /// use std::fmt; /// - /// use rocket::http::uri::{Formatter, UriDisplay}; + /// use rocket::http::uri::{Formatter, UriDisplay, UriPart, Path}; /// /// struct Foo; /// - /// impl UriDisplay for Foo { - /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// impl<P: UriPart> UriDisplay<P> for Foo { + /// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { /// f.write_raw("f")?; /// f.write_raw("o")?; /// f.write_raw("o") @@ -192,14 +219,23 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// } /// /// let foo = Foo; - /// let uri_string = format!("{}", &foo as &UriDisplay); + /// let uri_string = format!("{}", &foo as &UriDisplay<Path>); /// 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 { + // This implementation is a bit of a lie to the type system. Instead of + // implementing this twice, one for <Path> and again for <Query>, we do + // this once here. This is okay since we know that this handles the + // cases for both Path and Query, and doing it this way allows us to + // keep the uri part generic _generic_ in other implementations that use + // `write_raw`. + if self.fresh && self.delimiter == '/' { + if self.previous { + self.inner.write_char(self.delimiter)?; + } + } else if self.fresh && self.delimiter == '&' { if self.previous { - self.inner.write_str("&")?; + self.inner.write_char(self.delimiter)?; } if !self.prefixes.is_empty() { @@ -216,40 +252,7 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { 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.refreshed(|f| f.with_prefix(name, |f| f.write_value(value))) + self.inner.write_str(string.as_ref()) } /// Writes the unnamed value `value`. Any nested names are prefixed as @@ -263,29 +266,33 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// # extern crate rocket; /// use std::fmt; /// - /// use rocket::http::uri::{Formatter, UriDisplay}; + /// use rocket::http::uri::{Formatter, UriDisplay, UriPart, Path, Query}; /// /// struct Foo(usize); /// - /// impl UriDisplay for Foo { - /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// impl<P: UriPart> UriDisplay<P> for Foo { + /// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { /// f.write_value(&self.0) /// } /// } /// /// let foo = Foo(123); - /// let uri_string = format!("{}", &foo as &UriDisplay); + /// + /// let uri_string = format!("{}", &foo as &UriDisplay<Path>); + /// assert_eq!(uri_string, "123"); + /// + /// let uri_string = format!("{}", &foo as &UriDisplay<Query>); /// assert_eq!(uri_string, "123"); /// ``` #[inline] - pub fn write_value<T: UriDisplay>(&mut self, value: T) -> fmt::Result { + pub fn write_value<T: UriDisplay<P>>(&mut self, value: T) -> fmt::Result { 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. + /// as insert a separator. /// /// [`write_raw()`]: Formatter::write_raw() /// @@ -295,12 +302,12 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// # #[macro_use] extern crate rocket; /// use std::fmt; /// - /// use rocket::http::uri::{Formatter, UriDisplay}; + /// use rocket::http::uri::{Formatter, UriDisplay, Query, Path}; /// /// struct Foo; /// - /// impl UriDisplay for Foo { - /// fn fmt(&self, f: &mut Formatter) -> fmt::Result { + /// impl UriDisplay<Query> for Foo { + /// fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { /// f.write_raw("a")?; /// f.write_raw("raw")?; /// f.refresh(); @@ -308,14 +315,29 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { /// } /// } /// - /// #[derive(UriDisplay)] - /// struct Message { - /// inner: Foo, + /// let uri_string = format!("{}", &Foo as &UriDisplay<Query>); + /// assert_eq!(uri_string, "araw&format"); + /// + ///// #[derive(UriDisplayQuery)] + ///// struct Message { + ///// inner: Foo, + ///// } + ///// + ///// let msg = Message { inner: Foo }; + ///// let uri_string = format!("{}", &msg as &UriDisplay); + ///// assert_eq!(uri_string, "inner=araw&inner=format"); + /// + /// impl UriDisplay<Path> for Foo { + /// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result { + /// f.write_raw("a")?; + /// f.write_raw("raw")?; + /// f.refresh(); + /// f.write_raw("format") + /// } /// } /// - /// let msg = Message { inner: Foo }; - /// let uri_string = format!("{}", &msg as &UriDisplay); - /// assert_eq!(uri_string, "inner=araw&inner=format"); + /// let uri_string = format!("{}", &Foo as &UriDisplay<Path>); + /// assert_eq!(uri_string, "araw/format"); /// ``` #[inline(always)] pub fn refresh(&mut self) { @@ -323,8 +345,137 @@ impl<'i, 'f: 'i> Formatter<'i, 'f> { } } -impl<'f, 'i: 'f> fmt::Write for Formatter<'f, 'i> { +impl<'i> Formatter<'i, Query> { + fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result + where F: FnOnce(&mut Self) -> fmt::Result + { + // The `prefix` string is pushed in a `StackVec` for use by recursive + // (nested) calls to `write_raw`. The string is pushed here and then + // popped here. `self.prefixes` is modified nowhere else, and no strings + // leak from the the vector. As a result, it is impossible for a + // `prefix` to be accessed incorrectly as: + // + // * Rust _guarantees_ it exists for the lifetime of this method + // * it is only reachable while this method's stack is active because + // it is popped before this method returns + // * thus, at any point that it's reachable, it's valid + // + // Said succinctly: this `prefixes` stack shadows a subset of the + // `with_prefix` stack precisely, making it reachable to other code. + let prefix: &'static str = unsafe { ::std::mem::transmute(prefix) }; + + self.prefixes.push(prefix); + let result = f(self); + self.prefixes.pop(); + + result + } + + /// 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, Query}; + /// + /// struct Foo { + /// name: usize + /// } + /// + /// // Note: This is identical to what #[derive(UriDisplayQuery)] would + /// // generate! In practice, _always_ use the derive. + /// impl UriDisplay<Query> for Foo { + /// fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { + /// f.write_named_value("name", &self.name) + /// } + /// } + /// + /// let foo = Foo { name: 123 }; + /// let uri_string = format!("{}", &foo as &UriDisplay<Query>); + /// assert_eq!(uri_string, "name=123"); + /// ``` + #[inline] + pub fn write_named_value<T: UriDisplay<Query>>(&mut self, name: &str, value: T) -> fmt::Result { + self.refreshed(|f| f.with_prefix(name, |f| f.write_value(value))) + } +} + +impl<'i, P: UriPart> fmt::Write for Formatter<'i, P> { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_raw(s) } } + +// Used by code generation. +#[doc(hidden)] +pub enum UriArgumentsKind<A> { + Static(&'static str), + Dynamic(A) +} + +// Used by code generation. +#[doc(hidden)] +pub enum UriQueryArgument<'a> { + Raw(&'a str), + NameValue(&'a str, &'a dyn UriDisplay<Query>), + Value(&'a dyn UriDisplay<Query>) +} + +// Used by code generation. +#[doc(hidden)] +pub struct UriArguments<'a> { + pub path: UriArgumentsKind<&'a [&'a dyn UriDisplay<Path>]>, + pub query: Option<UriArgumentsKind<&'a [UriQueryArgument<'a>]>>, +} + +// Used by code generation. +impl<'a> UriArguments<'a> { + #[doc(hidden)] + pub fn into_origin(self) -> Origin<'static> { + use std::borrow::Cow; + use self::{UriArgumentsKind::*, UriQueryArgument::*}; + + let path: Cow<'static, str> = match self.path { + Static(path) => path.into(), + Dynamic(args) => { + let mut string = String::from("/"); + { + let mut formatter = Formatter::<Path>::new(&mut string); + for value in args { + let _ = formatter.write_value(value); + } + } + + string.into() + } + }; + + let query: Option<Cow<'static, str>> = self.query.map(|query| match query { + Static(query) => query.into(), + Dynamic(args) => { + let mut string = String::new(); + { + let mut f = Formatter::<Query>::new(&mut string); + for arg in args { + let _ = match arg { + Raw(v) => f.write_raw(v), + NameValue(n, v) => f.write_named_value(n, v), + Value(v) => f.write_value(v), + }; + } + } + + string.into() + } + }); + + Origin::new(path, query) + } +} diff --git a/core/http/src/uri/from_uri_param.rs b/core/http/src/uri/from_uri_param.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use RawStr; -use uri::UriDisplay; +use uri::{self, UriPart, UriDisplay}; /// Conversion trait for parameters used in [`uri!`] invocations. /// @@ -9,6 +9,8 @@ use uri::UriDisplay; /// [`UriDisplay`]. As such, this trait typically does not need to be implemented. /// Instead, implement [`UriDisplay`]. /// +/// # Overview +/// /// This trait is invoked once per expression passed into a [`uri!`] invocation. /// In particular, for a route URI parameter of type `T` and a user-supplied /// expression of type `S`, `<T as FromUriParam<S>>::from_uri_param` is @@ -20,8 +22,15 @@ use uri::UriDisplay; /// implementation, provided by Rocket, allows an `&str` to be used in a `uri!` /// invocation for route URI parameters declared as `String`: /// -/// ```rust,ignore -/// impl<'a> FromUriParam<&'a str> for String { type Target = &'a str; } +/// ```rust +/// # extern crate rocket; +/// # use rocket::http::uri::{FromUriParam, UriPart}; +/// # struct S; +/// # type String = S; +/// impl<'a, P: UriPart> FromUriParam<P, &'a str> for String { +/// type Target = &'a str; +/// # fn from_uri_param(s: &'a str) -> Self::Target { "hi" } +/// } /// ``` /// /// Because the [`FromUriParam::Target`] type is the same as the input type, the @@ -29,21 +38,34 @@ use uri::UriDisplay; /// place of a `String` without penalty. A similar no-op conversion exists for /// [`&RawStr`](RawStr): /// -/// ```rust,ignore -/// impl<'a, 'b> FromUriParam<&'a str> for &'b RawStr { type Target = &'a str; } +/// ```rust +/// # extern crate rocket; +/// # use rocket::http::uri::{FromUriParam, UriPart}; +/// # struct S; +/// # type RawStr = S; +/// impl<'a, 'b, P: UriPart> FromUriParam<P, &'a str> for &'b RawStr { +/// type Target = &'a str; +/// # fn from_uri_param(s: &'a str) -> Self::Target { "hi" } +/// } /// ``` /// +/// # Provided Implementations +/// +/// See [Foreign Impls](#foreign-impls) for implementations provided by Rocket. +/// /// # Implementing /// /// This trait should only be implemented when you'd like to allow a type /// different from the route's declared type to be used in its place in a `uri!` /// invocation. For instance, if the route has a type of `T` and you'd like to -/// use a type of `S` in a `uri!` invocation, you'd implement `FromUriParam<T> -/// for S`. +/// use a type of `S` in a `uri!` invocation, you'd implement `FromUriParam<P, +/// T> for S` where `P` is `Path` for conversions valid in the path part of a +/// URI, `Uri` for conversions valid in the query part of a URI, or `P: UriPart` +/// when a conversion is valid in either case. /// -/// This is typically only warranted for owned-value types with -/// corresponding reference types: `String` and `&str`, for instance. In this -/// case, it's desirable to allow an `&str` to be used in place of a `String`. +/// This is typically only warranted for owned-value types with corresponding +/// reference types: `String` and `&str`, for instance. In this case, it's +/// desirable to allow an `&str` to be used in place of a `String`. /// /// When implementing `FromUriParam`, be aware that Rocket will use the /// [`UriDisplay`] implementation of [`FromUriParam::Target`], _not_ of the @@ -51,16 +73,17 @@ use uri::UriDisplay; /// /// # Example /// -/// The following example implements `FromUriParam<(&str, &str)>` for a `User` -/// type. The implementation allows an `(&str, &str)` type to be used in a -/// `uri!` invocation where a `User` type is expected. +/// The following example implements `FromUriParam<Query, (&str, &str)>` for a +/// `User` type. The implementation allows an `(&str, &str)` type to be used in +/// a `uri!` invocation where a `User` type is expected in the query part of the +/// URI. /// /// ```rust /// # #[macro_use] extern crate rocket; /// use std::fmt; /// /// use rocket::http::RawStr; -/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam}; +/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query}; /// /// #[derive(FromForm)] /// struct User<'a> { @@ -68,14 +91,14 @@ use uri::UriDisplay; /// nickname: String, /// } /// -/// impl<'a> UriDisplay for User<'a> { -/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// impl<'a> UriDisplay<Query> for User<'a> { +/// fn fmt(&self, f: &mut Formatter<Query>) -> 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> { +/// impl<'a, 'b> FromUriParam<Query, (&'a str, &'b str)> for User<'a> { /// type Target = User<'a>; /// /// fn from_uri_param((name, nickname): (&'a str, &'b str)) -> User<'a> { @@ -92,19 +115,19 @@ use uri::UriDisplay; /// # use std::fmt; /// use rocket::http::RawStr; /// use rocket::request::Form; -/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam}; +/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query}; /// # /// # #[derive(FromForm)] /// # struct User<'a> { name: &'a RawStr, nickname: String, } /// # -/// # impl<'a> UriDisplay for User<'a> { -/// # fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// # impl<'a> UriDisplay<Query> for User<'a> { +/// # fn fmt(&self, f: &mut Formatter<Query>) -> 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> { +/// # impl<'a, 'b> FromUriParam<Query, (&'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() } @@ -122,9 +145,9 @@ use uri::UriDisplay; /// [`uri!`]: ::rocket_codegen::uri /// [`UriDisplay`]: uri::UriDisplay /// [`FromUriParam::Target`]: uri::FromUriParam::Target -pub trait FromUriParam<T> { +pub trait FromUriParam<P: UriPart, T> { /// The resulting type of this conversion. - type Target: UriDisplay; + type Target: UriDisplay<P>; /// Converts a value of type `T` into a value of type `Self::Target`. The /// resulting value of type `Self::Target` will be rendered into a URI using @@ -132,61 +155,61 @@ pub trait FromUriParam<T> { fn from_uri_param(param: T) -> Self::Target; } -impl<T: UriDisplay> FromUriParam<T> for T { +impl<P: UriPart, T: UriDisplay<P>> FromUriParam<P, T> for T { type Target = T; #[inline(always)] fn from_uri_param(param: T) -> T { param } } -impl<'a, T: UriDisplay> FromUriParam<&'a T> for T { +impl<'a, P: UriPart, T: UriDisplay<P>> FromUriParam<P, &'a T> for T { type Target = &'a T; #[inline(always)] fn from_uri_param(param: &'a T) -> &'a T { param } } -impl<'a, T: UriDisplay> FromUriParam<&'a mut T> for T { +impl<'a, P: UriPart, T: UriDisplay<P>> FromUriParam<P, &'a mut T> for T { type Target = &'a mut T; #[inline(always)] fn from_uri_param(param: &'a mut T) -> &'a mut T { param } } /// A no cost conversion allowing an `&str` to be used in place of a `String`. -impl<'a> FromUriParam<&'a str> for String { +impl<'a, P: UriPart> FromUriParam<P, &'a str> for String { type Target = &'a str; #[inline(always)] fn from_uri_param(param: &'a str) -> &'a str { param } } /// A no cost conversion allowing an `&str` to be used in place of an `&RawStr`. -impl<'a, 'b> FromUriParam<&'a str> for &'b RawStr { +impl<'a, 'b, P: UriPart> FromUriParam<P, &'a str> for &'b RawStr { type Target = &'a str; #[inline(always)] fn from_uri_param(param: &'a str) -> &'a str { param } } /// A no cost conversion allowing a `String` to be used in place of an `&RawStr`. -impl<'a> FromUriParam<String> for &'a RawStr { +impl<'a, P: UriPart> FromUriParam<P, String> for &'a RawStr { type Target = String; #[inline(always)] fn from_uri_param(param: String) -> String { param } } /// A no cost conversion allowing a `String` to be used in place of an `&str`. -impl<'a> FromUriParam<String> for &'a str { +impl<'a, P: UriPart> FromUriParam<P, String> for &'a str { type Target = String; #[inline(always)] fn from_uri_param(param: String) -> String { param } } /// A no cost conversion allowing an `&Path` to be used in place of a `PathBuf`. -impl<'a> FromUriParam<&'a Path> for PathBuf { +impl<'a> FromUriParam<uri::Path, &'a Path> for PathBuf { type Target = &'a Path; #[inline(always)] fn from_uri_param(param: &'a Path) -> &'a Path { param } } /// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`. -impl<'a> FromUriParam<&'a str> for PathBuf { +impl<'a> FromUriParam<uri::Path, &'a str> for PathBuf { type Target = &'a Path; #[inline(always)] @@ -194,3 +217,25 @@ impl<'a> FromUriParam<&'a str> for PathBuf { Path::new(param) } } + +/// A no cost conversion allowing any `T` to be used in place of an `Option<T>` +/// in path parts. +impl<A, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Option<T> { + type Target = T::Target; + + #[inline(always)] + fn from_uri_param(param: A) -> Self::Target { + T::from_uri_param(param) + } +} + +/// A no cost conversion allowing any `T` to be used in place of an `Result<T, +/// E>` in path parts. +impl<A, E, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Result<T, E> { + type Target = T::Target; + + #[inline(always)] + fn from_uri_param(param: A) -> Self::Target { + T::from_uri_param(param) + } +} diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs @@ -19,3 +19,63 @@ pub use self::uri_display::*; pub use self::formatter::*; pub use self::from_uri_param::*; pub use self::segments::*; + +mod private { + pub trait Sealed {} + impl Sealed for super::Path {} + impl Sealed for super::Query {} +} + +/// Marker trait for types that mark a part of a URI. +/// +/// This trait exists solely to categorize types that mark a part of the URI, +/// currently [`Path`] and [`Query`]. Said another way, types that implement +/// this trait are marker types that represent a part of a URI at the +/// type-level. +/// +/// This trait is _sealed_: it cannot be implemented outside of Rocket. +/// +/// # Usage +/// +/// You will find this trait in traits like [`UriDisplay`] or structs like +/// [`Formatter`] as the bound on a generic parameter: `P: UriPart`. Because the +/// trait is sealed, the generic type is guaranteed to be instantiated as one of +/// [`Query`] or [`Path`], effectively creating two instances of the generic +/// items: `UriDisplay<Query>` and `UriDisplay<Path>`, and `Formatter<Query>` +/// and `Formatter<Path>`. Unlike having two distinct, non-generic traits, this +/// approach enables succinct, type-checked generic implementations of these +/// items. +/// +/// [`Query`]: uri::Query +/// [`Path`]: uri::Path +/// [`UriDisplay`]: uri::UriDisplay +/// [`Formatter`]: uri::Formatter +pub trait UriPart: private::Sealed { } + +/// Marker type indicating use of a type for the path [`UriPart`] of a URI. +/// +/// In route URIs, this corresponds to all of the text before a `?`, if any, or +/// all of the text in the URI otherwise: +/// +/// ```text +/// #[get("/home/<name>/<page>?<item>")] +/// ^------------------ Path +/// ``` +/// +/// [`UriPart`]: uri::UriPart +pub enum Path { } + +/// Marker type indicating use of a type for the query [`UriPart`] of a URI. +/// +/// In route URIs, this corresponds to all of the text after a `?`, if any. +/// +/// ```text +/// #[get("/home/<name>/<page>?<item>&<form..>")] +/// ^-------------- Query +/// ``` +/// +/// [`UriPart`]: uri::UriPart +pub enum Query { } + +impl UriPart for Path { } +impl UriPart for Query { } diff --git a/core/http/src/uri/uri_display.rs b/core/http/src/uri/uri_display.rs @@ -1,10 +1,9 @@ -use std::fmt; -use std::path::{Path, PathBuf}; +use std::{fmt, path}; use std::borrow::Cow; use percent_encoding::utf8_percent_encode; -use uri::{Uri, Formatter, UNSAFE_PATH_ENCODE_SET}; +use uri::{Uri, UriPart, Path, Query, Formatter, UNSAFE_PATH_ENCODE_SET}; use {RawStr, ext::Normalize}; /// Trait implemented by types that can be displayed as part of a URI in `uri!`. @@ -15,11 +14,48 @@ use {RawStr, ext::Normalize}; /// percent-encoded or consist only of characters that are alphanumeric, "-", /// ".", "_", or "~" - the "unreserved" characters. /// +/// # Marker Generic: `UriDisplay<Path>` vs. `UriDisplay<Query>` +/// +/// The [`UriPart`] parameter `P` in `UriDisplay<P>` must be either [`Path`] or +/// [`Query`] (see the [`UriPart`] documentation for how this is enforced), +/// resulting in either `UriDisplay<Path>` or `UriDisplay<Query>`. +/// +/// As the names might imply, the `Path` version of the trait is used when +/// displaying parameters in the path part of the URI while the `Query` version +/// is used when display parameters in the query part of the URI. These distinct +/// versions of the trait exist exactly to differentiate, at the type-level, +/// where in the URI a value is to be written to, allowing for type safety in +/// the face of differences between the two locations. For example, while it is +/// valid to use a value of `None` in the query part, omitting the parameter +/// entirely, doing so is _not_ valid in the path part. By differentiating in +/// the type system, both of these conditions can be enforced appropriately +/// through distinct implementations of `UriDisplay<Path>` and +/// `UriDisplay<Query>`. +/// +/// Occasionally, the implementation of `UriDisplay` is independent of where the +/// parameter is to be displayed. When this is the case, the parameter may be +/// kept generic. That is, implementations can take the form: +/// +/// ```rust +/// # extern crate rocket; +/// # use std::fmt; +/// # use rocket::http::uri::{UriPart, UriDisplay, Formatter}; +/// # struct SomeType; +/// impl<P: UriPart> UriDisplay<P> for SomeType +/// # { fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { Ok(()) } } +/// ``` +/// +/// [`UriPart`]: uri::UriPart +/// [`Path`]: uri::Path +/// [`Query`]: uri::Query +/// /// # Code Generation /// /// When the `uri!` macro is used to generate a URI for a route, the types for -/// the route's URI parameters must implement `UriDisplay`. The `UriDisplay` -/// implementation for these types is used when generating the URI. +/// the route's _path_ URI parameters must implement `UriDisplay<Path>`, while +/// types in the route's query parameters must implement `UriDisplay<Query>`. +/// The `UriDisplay` implementation for these types is used when generating the +/// URI. /// /// To illustrate `UriDisplay`'s role in code generation for `uri!`, consider /// the following route: @@ -49,25 +85,26 @@ use {RawStr, ext::Normalize}; /// ``` /// /// After verifying parameters and their types, Rocket will generate code -/// similar to the following: +/// similar (in spirit) to the following: /// /// ```rust /// # extern crate rocket; -/// # use rocket::http::uri::UriDisplay; +/// # use rocket::http::uri::{UriDisplay, Path, Query, Origin}; /// # -/// format!("/item/{}?track={}", &100 as &UriDisplay, &"inbound" as &UriDisplay); +/// Origin::parse(&format!("/item/{}?track={}", +/// &100 as &UriDisplay<Path>, &"inbound" as &UriDisplay<Query>)); /// ``` /// -/// 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. +/// For this expression to typecheck, `i32` must implement `UriDisplay<Path>` +/// and `Value` must implement `UriDisplay<Query>`. As can be seen, the +/// implementations will be used to display the value in a URI-safe manner. /// /// [`uri!`]: /rocket_codegen/#typed-uris-uri /// /// # Provided Implementations /// -/// Rocket implements `UriDisplay` for several built-in types. Their behavior is -/// documented here. +/// Rocket implements `UriDisplay<P>` for all `P: UriPart` for several built-in +/// types. /// /// * **i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, /// f64, bool, IpAddr, Ipv4Addr, Ipv6Addr** @@ -83,6 +120,47 @@ use {RawStr, ext::Normalize}; /// /// Uses the implementation of `UriDisplay` for `T`. /// +/// Rocket implements `UriDisplay<Path>` (but not `UriDisplay<Query>`) for +/// several built-in types. +/// +/// * `T` for **`Option<T>`** _where_ **`T: UriDisplay<Path>`** +/// +/// Uses the implementation of `UriDisplay` for `T::Target`. +/// +/// When a type of `Option<T>` appears in a route path, use a type of `T` as +/// the parameter in `uri!`. Note that `Option<T>` itself _does not_ +/// implement `UriDisplay<Path>`. +/// +/// * `T` for **`Result<T, E>`** _where_ **`T: UriDisplay<Path>`** +/// +/// Uses the implementation of `UriDisplay` for `T::Target`. +/// +/// When a type of `Result<T, E>` appears in a route path, use a type of `T` +/// as the parameter in `uri!`. Note that `Result<T, E>` itself _does not_ +/// implement `UriDisplay<Path>`. +/// +/// Rocket implements `UriDisplay<Query>` (but not `UriDisplay<Path>`) for +/// several built-in types. +/// +/// * **`Form<T>`, `LenientForm<T>`** _where_ **`T: FromUriParam + FromForm`** +/// +/// Uses the implementation of `UriDisplay` for `T::Target`. +/// +/// In general, when a type of `Form<T>` is to be displayed as part of a +/// URI's query, it suffices to derive `UriDisplay` for `T`. Note that any +/// type that can be converted into a `T` using [`FromUriParam`] can be used +/// in place of a `Form<T>` in a `uri!` invocation. +/// +/// * **`Option<T>`** _where_ **`T: UriDisplay<Query>`** +/// +/// If the `Option` is `Some`, uses the implementation of `UriDisplay` for +/// `T`. Otherwise, nothing is rendered. +/// +/// * **`Result<T, E>`** _where_ **`T: UriDisplay<Query>`** +/// +/// If the `Result` is `Ok`, uses the implementation of `UriDisplay` for +/// `T`. Otherwise, nothing is rendered. +/// /// # Deriving /// /// Manually implementing `UriDisplay` should be done with care. For most use @@ -90,16 +168,25 @@ use {RawStr, ext::Normalize}; /// /// ```rust /// # #[macro_use] extern crate rocket; -/// # use rocket::http::uri::UriDisplay; -/// #[derive(FromForm, UriDisplay)] +/// # use rocket::http::uri::{UriDisplay, Query, Path}; +/// // Derives `UriDisplay<Query>` +/// #[derive(UriDisplayQuery)] /// struct User { /// name: String, /// age: usize, /// } /// /// let user = User { name: "Michael Smith".into(), age: 31 }; -/// let uri_string = format!("{}", &user as &UriDisplay); +/// let uri_string = format!("{}", &user as &UriDisplay<Query>); /// assert_eq!(uri_string, "name=Michael%20Smith&age=31"); +/// +/// // Derives `UriDisplay<Path>` +/// #[derive(UriDisplayPath)] +/// struct Name(String); +/// +/// let name = Name("Bob Smith".into()); +/// let uri_string = format!("{}", &name as &UriDisplay<Path>); +/// assert_eq!(uri_string, "Bob%20Smith"); /// ``` /// /// As long as every field in the structure (or enum) implements `UriDisplay`, @@ -109,6 +196,8 @@ use {RawStr, ext::Normalize}; /// derive] documentation for full details. /// /// [`UriDisplay` derive]: ../../../rocket_codegen/derive.UriDisplay.html +/// [`Formatter::write_named_value()`]: uri::Formatter::write_named_value() +/// [`Formatter::write_value()`]: uri::Formatter::write_value() /// /// # Implementing /// @@ -127,12 +216,12 @@ use {RawStr, ext::Normalize}; /// ## Example /// /// The following snippet consists of a `Name` type that implements both -/// `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. Note the custom parsing in the `FromParam` -/// implementation; as a result of this, a custom (reflexive) `UriDisplay` -/// implementation is required. +/// `FromParam` and `UriDisplay<Path>`. 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 path 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)] @@ -161,14 +250,15 @@ use {RawStr, ext::Normalize}; /// } /// /// use std::fmt; -/// use rocket::http::uri::{Formatter, UriDisplay}; +/// use rocket::http::uri::{Formatter, UriDisplay, Path}; /// 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. Prefixes the inner name with `name:`. -/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// impl UriDisplay<Path> for Name { +/// // Delegates to the `UriDisplay` implementation for `String` via the +/// // call to `write_value` to ensure /// that the written string is +/// // URI-safe. In this case, the string will /// be percent encoded. +/// // Prefixes the inner name with `name:`. +/// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result { /// f.write_value(&format!("name:{}", self.0)) /// } /// } @@ -186,63 +276,67 @@ use {RawStr, ext::Normalize}; /// let uri = uri!(real: Name("Mike Smith".into())); /// assert_eq!(uri.path(), "/name:Mike%20Smith"); /// ``` -pub trait UriDisplay { +pub trait UriDisplay<P: UriPart> { /// Formats `self` in a URI-safe manner using the given formatter. - fn fmt(&self, f: &mut Formatter) -> fmt::Result; + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result; +} + +impl<'a> fmt::Display for &'a UriDisplay<Path> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + UriDisplay::fmt(*self, &mut <Formatter<Path>>::new(f)) + } } -impl<'a> fmt::Display for &'a UriDisplay { +impl<'a> fmt::Display for &'a UriDisplay<Query> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - UriDisplay::fmt(*self, &mut Formatter::new(f)) + UriDisplay::fmt(*self, &mut <Formatter<Query>>::new(f)) } } /// Percent-encodes the raw string. -impl UriDisplay for RawStr { +impl<P: UriPart> UriDisplay<P> for RawStr { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { f.write_raw(&Uri::percent_encode(self.as_str())) } } /// Percent-encodes the raw string. -impl UriDisplay for str { +impl<P: UriPart> UriDisplay<P> for str { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { f.write_raw(&Uri::percent_encode(self)) } } /// Percent-encodes the raw string. -impl<'a> UriDisplay for Cow<'a, str> { +impl<'a, P: UriPart> UriDisplay<P> for Cow<'a, str> { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { f.write_raw(&Uri::percent_encode(self)) } } /// Percent-encodes the raw string. -impl UriDisplay for String { +impl<P: UriPart> UriDisplay<P> for String { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> 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 { +impl UriDisplay<Path> for path::PathBuf { #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let string = self.normalized_str(); - let enc: Cow<str> = utf8_percent_encode(&string, UNSAFE_PATH_ENCODE_SET).into(); - f.write_raw(&enc) + fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result { + self.as_path().fmt(f) } } /// Percent-encodes each segment in the path and normalizes separators. -impl UriDisplay for Path { +impl UriDisplay<Path> for path::Path { #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result { let string = self.normalized_str(); let enc: Cow<str> = utf8_percent_encode(&string, UNSAFE_PATH_ENCODE_SET).into(); f.write_raw(&enc) @@ -252,9 +346,9 @@ impl UriDisplay for Path { macro_rules! impl_with_display { ($($T:ty),+) => {$( /// This implementation is identical to the `Display` implementation. - impl UriDisplay for $T { + impl<P: UriPart> UriDisplay<P> for $T { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { use std::fmt::Write; write!(f, "{}", self) } @@ -274,9 +368,9 @@ impl_with_display! { macro_rules! impl_for_ref { ($($T:ty),+) => {$( /// Uses the implementation of `UriDisplay` for `T`. - impl<'a, T: UriDisplay + ?Sized> UriDisplay for $T { + impl<'a, P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for $T { #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { UriDisplay::fmt(*self, f) } } @@ -284,3 +378,25 @@ macro_rules! impl_for_ref { } impl_for_ref!(&'a mut T, &'a T); + +impl<T: UriDisplay<Query>> UriDisplay<Query> for Option<T> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { + if let Some(v) = self { + f.write_value(&v)?; + } + + Ok(()) + } +} + +impl<E, T: UriDisplay<Query>> UriDisplay<Query> for Result<T, E> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<Query>) -> fmt::Result { + if let Ok(v) = self { + f.write_value(&v)?; + } + + Ok(()) + } +} diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs @@ -10,7 +10,7 @@ use config::Environment::*; use config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel}; use config::{Table, Value, Array, Datetime}; -use http::Key; +use http::private::Key; /// Structure for Rocket application configuration. /// diff --git a/core/lib/src/config/custom_values.rs b/core/lib/src/config/custom_values.rs @@ -2,7 +2,7 @@ use std::fmt; #[cfg(feature = "tls")] use http::tls::{Certificate, PrivateKey}; -use http::Key; +use http::private::Key; use config::{Result, Config, Value, ConfigError, LoggingLevel}; diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use Rocket; use local::LocalRequest; -use http::{Method, CookieJar}; +use http::{Method, private::CookieJar}; use error::LaunchError; /// A structure to construct requests for local dispatching. diff --git a/core/lib/src/request/form/form.rs b/core/lib/src/request/form/form.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use outcome::Outcome::*; use request::{Request, form::{FromForm, FormItems, FormDataError}}; use data::{Outcome, Transform, Transformed, Data, FromData}; -use http::{Status, uri::FromUriParam}; +use http::{Status, uri::{Query, FromUriParam}}; /// A data guard for parsing [`FromForm`] types strictly. /// @@ -219,7 +219,7 @@ impl<'f, T: FromForm<'f>> FromData<'f> for Form<T> { } } -impl<'f, A, T: FromUriParam<A> + FromForm<'f>> FromUriParam<A> for Form<T> { +impl<'f, A, T: FromUriParam<Query, A> + FromForm<'f>> FromUriParam<Query, A> for Form<T> { type Target = T::Target; #[inline(always)] diff --git a/core/lib/src/request/form/lenient.rs b/core/lib/src/request/form/lenient.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use request::{Request, form::{Form, FormDataError, FromForm}}; use data::{Data, Transform, Transformed, FromData, Outcome}; -use http::uri::FromUriParam; +use http::uri::{Query, FromUriParam}; /// A data gaurd for parsing [`FromForm`] types leniently. /// @@ -109,7 +109,7 @@ impl<'f, T: FromForm<'f>> FromData<'f> for LenientForm<T> { } } -impl<'f, A, T: FromUriParam<A> + FromForm<'f>> FromUriParam<A> for LenientForm<T> { +impl<'f, A, T: FromUriParam<Query, A> + FromForm<'f>> FromUriParam<Query, A> for LenientForm<T> { type Target = T::Target; #[inline(always)] diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs @@ -13,10 +13,10 @@ use request::{FromFormValue, FormItems, FormItem}; use rocket::Rocket; use router::Route; use config::{Config, Limits}; -use http::uri::{Origin, Segments}; -use http::{Method, Header, HeaderMap, Cookies, CookieJar}; -use http::{RawStr, ContentType, Accept, MediaType, Indexed, SmallVec}; -use http::hyper; +use http::{hyper, uri::{Origin, Segments}}; +use http::{Method, Header, HeaderMap, Cookies}; +use http::{RawStr, ContentType, Accept, MediaType}; +use http::private::{Indexed, SmallVec, CookieJar}; type Indices = (usize, usize);