From d7933dd6fd863503a21a5c343ee9f4bc863a586a Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 5 Dec 2018 04:20:22 -0800 Subject: [PATCH] Implement ignorable 'uri!' expressions. Closes #840. --- core/codegen/src/bang/uri.rs | 95 +++-- core/codegen/src/bang/uri_parsing.rs | 64 +++- core/codegen/src/derive/uri_display.rs | 59 ++- core/codegen/src/lib.rs | 56 ++- core/codegen/tests/typed-uris.rs | 71 +++- .../tests/ui-fail/typed-uri-bad-type.rs | 32 +- .../tests/ui-fail/typed-uri-bad-type.stderr | 122 +++--- .../tests/ui-fail/typed-uris-bad-params.rs | 11 +- .../ui-fail/typed-uris-bad-params.stderr | 176 ++++----- core/http/src/uri/formatter.rs | 34 +- core/http/src/uri/from_uri_param.rs | 216 ++++++++--- core/http/src/uri/mod.rs | 13 +- core/http/src/uri/uri_display.rs | 348 ++++++++++++++---- 13 files changed, 923 insertions(+), 374 deletions(-) diff --git a/core/codegen/src/bang/uri.rs b/core/codegen/src/bang/uri.rs index bcdfd91e..c4fb223c 100644 --- a/core/codegen/src/bang/uri.rs +++ b/core/codegen/src/bang/uri.rs @@ -32,10 +32,35 @@ crate fn _uri_macro(input: TokenStream) -> Result { Ok(quote!(#path!(#input2)).into()) } -fn extract_exprs(internal: &InternalUriParams) -> Result> { +fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<( + impl Iterator, + impl Iterator, + )> +{ let route_name = &internal.uri_params.route_path; match internal.validate() { - Validation::Ok(exprs) => Ok(exprs), + Validation::Ok(exprs) => { + let path_param_count = internal.route_uri.path().matches('<').count(); + for expr in exprs.iter().take(path_param_count) { + if !expr.as_expr().is_some() { + return Err(expr.span().unstable() + .error("path parameters cannot be ignored")); + } + } + + // Create an iterator over all `ident`, `ty`, and `expr` triples. + let arguments = internal.fn_args.iter() + .zip(exprs.into_iter()) + .map(|(FnArg { ident, ty }, expr)| (ident, ty, expr)); + + // Create iterators for just the path and query parts. + let path_params = arguments.clone() + .take(path_param_count) + .map(|(i, t, e)| (i, t, e.unwrap_expr())); + + let query_params = arguments.skip(path_param_count); + Ok((path_params, query_params)) + } Validation::Unnamed(expected, actual) => { let mut diag = internal.uri_params.args_span().error( format!("`{}` route uri expects {} but {} supplied", quote!(#route_name), @@ -125,7 +150,7 @@ fn explode_path<'a, I: Iterator>( quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])) } -fn explode_query<'a, I: Iterator>( +fn explode_query<'a, I: Iterator>( uri: &Origin, bindings: &mut Vec, mut items: I @@ -137,30 +162,38 @@ fn explode_query<'a, I: Iterator>( 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 dyn_exprs = RouteSegment::parse_query(uri)?.filter_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) - ) - } + if segment.kind == Kind::Static { + let string = &segment.string; + return Some(quote!(#query_arg::Raw(#string))); } + + let (ident, ty, arg_expr) = items.next().expect("one item for each dyn"); + let expr = match arg_expr.as_expr() { + Some(expr) => expr, + None => { + // Force a typecheck for the `Ignoreable` trait. Note that write + // out the path to `is_ignorable` to get the right span. + bindings.push(quote_spanned! { arg_expr.span() => + rocket::http::uri::assert_ignorable::<#uri_mod::Query, #ty>(); + }); + + return None; + } + }; + + let name = &segment.name; + add_binding(bindings, &ident, &ty, &expr, Source::Query); + Some(match segment.kind { + Kind::Single => quote_spanned! { expr.span() => + #query_arg::NameValue(#name, &#ident as &dyn #uri_display) + }, + Kind::Multi => quote_spanned! { expr.span() => + #query_arg::Value(&#ident as &dyn #uri_display) + }, + Kind::Static => unreachable!("Kind::Static returns early") + }) }); Some(quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))) @@ -182,17 +215,7 @@ fn build_origin(internal: &InternalUriParams) -> Origin<'static> { crate fn _uri_internal_macro(input: TokenStream) -> Result { // Parse the internal invocation and the user's URI param expressions. let internal = syn::parse::(input).map_err(syn_to_diag)?; - let exprs = extract_exprs(&internal)?; - - // 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)); - - // 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 (path_params, query_params) = extract_exprs(&internal)?; let mut bindings = vec![]; let uri = build_origin(&internal); diff --git a/core/codegen/src/bang/uri_parsing.rs b/core/codegen/src/bang/uri_parsing.rs index 64f93fa9..ddb765de 100644 --- a/core/codegen/src/bang/uri_parsing.rs +++ b/core/codegen/src/bang/uri_parsing.rs @@ -12,10 +12,16 @@ use self::syn::punctuated::Punctuated; use http::{uri::Origin, ext::IntoOwned}; use indexmap::IndexMap; +#[derive(Debug)] +pub enum ArgExpr { + Expr(Expr), + Ignored(Token![_]), +} + #[derive(Debug)] pub enum Arg { - Unnamed(Expr), - Named(Ident, Token![=], Expr), + Unnamed(ArgExpr), + Named(Ident, Token![=], ArgExpr), } #[derive(Debug)] @@ -48,7 +54,7 @@ pub enum Validation<'a> { // (Missing, Extra, Duplicate) Named(Vec<&'a Ident>, Vec<&'a Ident>, Vec<&'a Ident>), // Everything is okay; here are the expressions in the route decl order. - Ok(Vec<&'a Expr>) + Ok(Vec<&'a ArgExpr>) } // This is invoked by Rocket itself. The `uri!` macro expands to a call to a @@ -72,16 +78,26 @@ pub struct InternalUriParams { pub uri_params: UriParams, } +impl Parse for ArgExpr { + fn parse(input: ParseStream) -> parse::Result { + if input.peek(Token![_]) { + return Ok(ArgExpr::Ignored(input.parse::()?)); + } + + input.parse::().map(ArgExpr::Expr) + } +} + impl Parse for Arg { fn parse(input: ParseStream) -> parse::Result { let has_key = input.peek2(Token![=]); if has_key { let ident = input.parse::()?; let eq_token = input.parse::()?; - let expr = input.parse::()?; + let expr = input.parse::()?; Ok(Arg::Named(ident, eq_token, expr)) } else { - let expr = input.parse::()?; + let expr = input.parse::()?; Ok(Arg::Unnamed(expr)) } } @@ -208,7 +224,7 @@ impl InternalUriParams { else { Validation::Ok(args.unnamed().unwrap().collect()) } }, Args::Named(_) => { - let mut params: IndexMap<&Ident, Option<&Expr>> = self.fn_args.iter() + let mut params: IndexMap<&Ident, Option<&ArgExpr>> = self.fn_args.iter() .map(|FnArg { ident, .. }| (ident, None)) .collect(); @@ -253,18 +269,18 @@ impl Arg { fn is_named(&self) -> bool { match *self { Arg::Named(..) => true, - Arg::Unnamed(_) => false, + _ => false } } - fn unnamed(&self) -> &Expr { + fn unnamed(&self) -> &ArgExpr { match self { Arg::Unnamed(expr) => expr, _ => panic!("Called Arg::unnamed() on an Arg::named!"), } } - fn named(&self) -> (&Ident, &Expr) { + fn named(&self) -> (&Ident, &ArgExpr) { match self { Arg::Named(ident, _, expr) => (ident, expr), _ => panic!("Called Arg::named() on an Arg::Unnamed!"), @@ -279,14 +295,14 @@ impl Args { } } - fn named(&self) -> Option> { + fn named(&self) -> Option> { match self { Args::Named(args) => Some(args.iter().map(|arg| arg.named())), _ => None } } - fn unnamed(&self) -> Option> { + fn unnamed(&self) -> Option> { match self { Args::Unnamed(args) => Some(args.iter().map(|arg| arg.unnamed())), _ => None @@ -294,12 +310,36 @@ impl Args { } } +impl ArgExpr { + pub fn as_expr(&self) -> Option<&Expr> { + match self { + ArgExpr::Expr(expr) => Some(expr), + _ => None + } + } + + pub fn unwrap_expr(&self) -> &Expr { + match self { + ArgExpr::Expr(expr) => expr, + _ => panic!("Called ArgExpr::expr() on ArgExpr::Ignored!"), + } + } +} + +impl ToTokens for ArgExpr { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + ArgExpr::Expr(e) => e.to_tokens(tokens), + ArgExpr::Ignored(e) => e.to_tokens(tokens) + } + } +} impl ToTokens for Arg { fn to_tokens(&self, tokens: &mut TokenStream2) { match self { Arg::Unnamed(e) => e.to_tokens(tokens), - Arg::Named(ident, eq, expr) => tokens.extend(quote!(#ident #eq #expr)) + Arg::Named(ident, eq, expr) => tokens.extend(quote!(#ident #eq #expr)), } } } diff --git a/core/codegen/src/derive/uri_display.rs b/core/codegen/src/derive/uri_display.rs index 4ead7146..66196312 100644 --- a/core/codegen/src/derive/uri_display.rs +++ b/core/codegen/src/derive/uri_display.rs @@ -2,6 +2,7 @@ use proc_macro::{Span, TokenStream}; use devise::*; use derive::from_form::Form; +use proc_macro2::TokenStream as TokenStream2; const NO_EMPTY_FIELDS: &str = "fieldless structs or variants are not supported"; const NO_NULLARY: &str = "nullary items are not supported"; @@ -37,17 +38,21 @@ fn validate_enum(gen: &DeriveGenerator, data: Enum) -> Result<()> { Ok(()) } +#[allow(non_snake_case)] 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)) + let Query = quote!(::rocket::http::uri::Query); + let UriDisplay = quote!(::rocket::http::uri::UriDisplay<#Query>); + let Formatter = quote!(::rocket::http::uri::Formatter<#Query>); + let FromUriParam = quote!(::rocket::http::uri::FromUriParam); + + let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #UriDisplay)) .generic_support(GenericSupport::Type | GenericSupport::Lifetime) .data_support(DataSupport::Struct | DataSupport::Enum) .validate_enum(validate_enum) .validate_struct(validate_struct) - .map_type_generic(move |_, ident, _| quote!(#ident : #display_trait)) + .map_type_generic(move |_, ident, _| quote!(#ident : #UriDisplay)) .function(move |_, inner| quote! { - fn fmt(&self, f: &mut #formatter) -> ::std::fmt::Result { + fn fmt(&self, f: &mut #Formatter) -> ::std::fmt::Result { #inner Ok(()) } @@ -67,7 +72,49 @@ pub fn derive_uri_display_query(input: TokenStream) -> TokenStream { Ok(tokens) }) - .to_tokens() + .to_tokens(); + + let i = input.clone(); + let gen_trait = quote!(impl #FromUriParam<#Query, Self>); + let from_self = DeriveGenerator::build_for(i, gen_trait) + .generic_support(GenericSupport::Type | GenericSupport::Lifetime) + .data_support(DataSupport::Struct | DataSupport::Enum) + .function(|_, _| quote! { + type Target = Self; + #[inline(always)] + fn from_uri_param(param: Self) -> Self { param } + }) + .to_tokens(); + + let i = input.clone(); + let gen_trait = quote!(impl<'__r> #FromUriParam<#Query, &'__r Self>); + let from_ref = DeriveGenerator::build_for(i, gen_trait) + .generic_support(GenericSupport::Type | GenericSupport::Lifetime) + .data_support(DataSupport::Struct | DataSupport::Enum) + .function(|_, _| quote! { + type Target = &'__r Self; + #[inline(always)] + fn from_uri_param(param: &'__r Self) -> &'__r Self { param } + }) + .to_tokens(); + + let i = input.clone(); + let gen_trait = quote!(impl<'__r> #FromUriParam<#Query, &'__r mut Self>); + let from_mut = DeriveGenerator::build_for(i, gen_trait) + .generic_support(GenericSupport::Type | GenericSupport::Lifetime) + .data_support(DataSupport::Struct | DataSupport::Enum) + .function(|_, _| quote! { + type Target = &'__r mut Self; + #[inline(always)] + fn from_uri_param(param: &'__r mut Self) -> &'__r mut Self { param } + }) + .to_tokens(); + + let mut ts = TokenStream2::from(uri_display); + ts.extend(TokenStream2::from(from_self)); + ts.extend(TokenStream2::from(from_ref)); + ts.extend(TokenStream2::from(from_mut)); + ts.into() } pub fn derive_uri_display_path(input: TokenStream) -> TokenStream { diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 47448892..2df367b7 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -858,9 +858,11 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; /// # -/// #[get("/person//")] -/// fn person(name: String, age: u8) -> String { -/// format!("Hello {}! You're {} years old.", name, age) +/// #[get("/person/?")] +/// fn person(name: String, age: Option) -> String { +/// # "".into() /* +/// ... +/// # */ /// } /// ``` /// @@ -870,21 +872,29 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; /// # -/// # #[get("/person//")] -/// # fn person(name: String, age: u8) { } +/// # #[get("/person/?")] +/// # fn person(name: String, age: Option) { } /// # /// // with unnamed parameters, in route path declaration order /// let mike = uri!(person: "Mike Smith", 28); -/// assert_eq!(mike.path(), "/person/Mike%20Smith/28"); +/// assert_eq!(mike.to_string(), "/person/Mike%20Smith?age=28"); /// /// // with named parameters, order irrelevant /// let mike = uri!(person: name = "Mike", age = 28); /// let mike = uri!(person: age = 28, name = "Mike"); -/// assert_eq!(mike.path(), "/person/Mike/28"); +/// assert_eq!(mike.to_string(), "/person/Mike?age=28"); /// /// // with a specific mount-point /// let mike = uri!("/api", person: name = "Mike", age = 28); -/// assert_eq!(mike.path(), "/api/person/Mike/28"); +/// assert_eq!(mike.to_string(), "/api/person/Mike?age=28"); +/// +/// // with unnamed values ignored +/// let mike = uri!(person: "Mike", _); +/// assert_eq!(mike.to_string(), "/person/Mike"); +/// +/// // with named values ignored +/// let mike = uri!(person: name = "Mike", age = _); +/// assert_eq!(mike.to_string(), "/person/Mike"); /// ``` /// /// ## Grammar @@ -896,8 +906,9 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// /// mount = STRING /// params := unnamed | named -/// unnamed := EXPR (',' EXPR)* -/// named := IDENT = EXPR (',' named)? +/// unnamed := expr (',' expr)* +/// named := IDENT = expr (',' named)? +/// expr := EXPR | '_' /// /// EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`) /// IDENT := a valid Rust identifier (examples: `name`, `age`) @@ -913,7 +924,19 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// converted into a [`Uri`] using `.into()` as needed. /// /// A `uri!` invocation only typechecks if the type of every value in the -/// invocation matches the type declared for the parameter in the given route. +/// invocation matches the type declared for the parameter in the given route, +/// after conversion with [`FromUriParam`], or if a value is ignored using `_` +/// and the corresponding route type implements [`Ignorable`]. +/// +/// Each value passed into `uri!` is rendered in its appropriate place in the +/// URI using the [`UriDisplay`] implementation for the value's type. The +/// `UriDisplay` implementation ensures that the rendered value is URI-safe. +/// +/// If a mount-point is provided, the mount-point is prepended to the route's +/// URI. +/// +/// ### Conversion +/// /// The [`FromUriParam`] trait is used to typecheck and perform a conversion for /// each value. If a `FromUriParam` implementation exists for a type `T`, /// then a value of type `S` can be used in `uri!` macro for a route URI @@ -925,17 +948,18 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// impl<'a> FromUriParam<&'a str> for String { .. } /// ``` /// -/// Each value passed into `uri!` is rendered in its appropriate place in the -/// URI using the [`UriDisplay`] implementation for the value's type. The -/// `UriDisplay` implementation ensures that the rendered value is URI-safe. +/// ### Ignorables /// -/// If a mount-point is provided, the mount-point is prepended to the route's -/// URI. +/// Query parameters can be ignored using `_` in place of an expression. The +/// corresponding type in the route URI must implement [`Ignorable`]. Ignored +/// parameters are not interpolated into the resulting `Origin`. Path parameters +/// are not ignorable. /// /// [`Uri`]: ../rocket/http/uri/enum.Uri.html /// [`Origin`]: ../rocket/http/uri/struct.Origin.html /// [`FromUriParam`]: ../rocket/http/uri/trait.FromUriParam.html /// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html +/// [`Ignorable`]: ../rocket/http/uri/trait.Ignorable.html #[proc_macro] pub fn uri(input: TokenStream) -> TokenStream { emit!(bang::uri_macro(input)) diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index 02e6ffc0..cbdccd02 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -208,9 +208,9 @@ fn check_with_segments() { assert_uri_eq! { uri!(segments: PathBuf::from("one/two/three")) => "/a/one/two/three", uri!(segments: path = PathBuf::from("one/two/three")) => "/a/one/two/three", - uri!("/c", segments: PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o/", - uri!("/c", segments: path = PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o/", - uri!(segments: PathBuf::from("one/ tw?o/")) => "/a/one/%20tw%3Fo/", + uri!("/c", segments: PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o", + uri!("/c", segments: path = PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o", + uri!(segments: PathBuf::from("one/ tw?o/")) => "/a/one/%20tw%3Fo", uri!(param_and_segments: 10, PathBuf::from("a/b")) => "/a/10/then/a/b", uri!(param_and_segments: id = 10, path = PathBuf::from("a/b")) => "/a/10/then/a/b", @@ -223,7 +223,7 @@ fn check_with_segments() { assert_uri_eq! { uri!(segments: "one/two/three") => "/a/one/two/three", uri!("/oh", segments: path = "one/two/three") => "/oh/a/one/two/three", - uri!(segments: "one/ tw?o/") => "/a/one/%20tw%3Fo/", + uri!(segments: "one/ tw?o/") => "/a/one/%20tw%3Fo", uri!(param_and_segments: id = 10, path = "a/b") => "/a/10/then/a/b", uri!(guarded_segments: 10, "a/b") => "/a/10/then/a/b", uri!(guarded_segments: id = 10, path = "a/b") => "/a/10/then/a/b", @@ -283,8 +283,10 @@ fn check_location_promotion() { assert_uri_eq! { uri!(simple2: 1, &S1("A".into()).0) => "/1/A", + uri!(simple2: 1, &mut S1("A".into()).0) => "/1/A", uri!(simple2: 1, S1("A".into()).0) => "/1/A", uri!(simple2: 1, &S2 { name: "A".into() }.name) => "/1/A", + uri!(simple2: 1, &mut S2 { name: "A".into() }.name) => "/1/A", uri!(simple2: 1, S2 { name: "A".into() }.name) => "/1/A", uri!(simple2: 1, &s1.0) => "/1/Bob", uri!(simple2: 1, &s2.name) => "/1/Bob", @@ -350,3 +352,64 @@ mod typed_uris { } } } + +#[derive(FromForm, UriDisplayQuery)] +struct Third<'r> { + one: String, + two: &'r RawStr, +} + +#[post("//?&")] +fn optionals( + foo: Option, + bar: Result, + q1: Result, + rest: Option> +) { } + +#[test] +fn test_optional_uri_parameters() { + assert_uri_eq! { + uri!(optionals: + foo = 10, + bar = &"hi there", + q1 = 10, + rest = Third { one: "hi there".into(), two: "a b".into() } + ) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b", + + uri!(optionals: + foo = &10, + bar = &"hi there", + q1 = &10, + rest = &Third { one: "hi there".into(), two: "a b".into() } + ) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b", + + uri!(optionals: + foo = &mut 10, + bar = &mut "hi there", + q1 = &mut 10, + rest = &mut Third { one: "hi there".into(), two: "a b".into() } + ) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b", + + uri!(optionals: + foo = 10, + bar = &"hi there", + q1 = _, + rest = Third { one: "hi there".into(), two: "a b".into() } + ) => "/10/hi%20there?one=hi%20there&two=a%20b", + + uri!(optionals: + foo = 10, + bar = &"hi there", + q1 = 10, + rest = _ + ) => "/10/hi%20there?q1=10", + + uri!(optionals: + foo = 10, + bar = &"hi there", + q1 = _, + rest = _, + ) => "/10/hi%20there", + } +} diff --git a/core/codegen/tests/ui-fail/typed-uri-bad-type.rs b/core/codegen/tests/ui-fail/typed-uri-bad-type.rs index 2148ad76..3267e8d6 100644 --- a/core/codegen/tests/ui-fail/typed-uri-bad-type.rs +++ b/core/codegen/tests/ui-fail/typed-uri-bad-type.rs @@ -1,3 +1,6 @@ +// normalize-stderr-test: "<(.*) as (.*)>" -> "$1 as $$TRAIT" +// normalize-stderr-test: "and \d+ others" -> "and $$N others" + #![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; @@ -13,7 +16,7 @@ impl<'a> FromParam<'a> for S { } #[post("/")] -fn simple(id: i32) { } +fn simple(id: usize) { } #[post("//")] fn not_uri_display(id: i32, name: S) { } @@ -32,7 +35,7 @@ impl<'q> FromQuery<'q> for S { } #[post("/?")] -fn simple_q(id: i32) { } +fn simple_q(id: isize) { } #[post("/?&")] fn other_q(id: usize, rest: S) { } @@ -42,13 +45,13 @@ fn optionals_q(id: Option, name: Result) { } fn main() { uri!(simple: id = "hi"); - //~^ ERROR i32: rocket::http::uri::FromUriParam + //~^ ERROR usize: rocket::http::uri::FromUriParam uri!(simple: "hello"); - //~^ ERROR i32: rocket::http::uri::FromUriParam + //~^ ERROR usize: rocket::http::uri::FromUriParam uri!(simple: id = 239239i64); - //~^ ERROR i32: rocket::http::uri::FromUriParam + //~^ ERROR usize: rocket::http::uri::FromUriParam uri!(not_uri_display: 10, S); //~^ ERROR S: rocket::http::uri::FromUriParam @@ -61,10 +64,10 @@ fn main() { //~^^ ERROR String: rocket::http::uri::FromUriParam> uri!(simple_q: "hi"); - //~^ ERROR i32: rocket::http::uri::FromUriParam + //~^ ERROR isize: rocket::http::uri::FromUriParam uri!(simple_q: id = "hi"); - //~^ ERROR i32: rocket::http::uri::FromUriParam + //~^ ERROR isize: rocket::http::uri::FromUriParam uri!(other_q: 100, S); //~^ ERROR S: rocket::http::uri::FromUriParam @@ -72,11 +75,16 @@ fn main() { uri!(other_q: rest = S, id = 100); //~^ ERROR S: rocket::http::uri::FromUriParam - // This one is okay. - uri!(optionals_q: None, Err("foo".into())); + uri!(other_q: rest = _, id = 100); + //~^ ERROR S: rocket::http::uri::Ignorable - // For queries, we need to know the exact variant. + uri!(other_q: rest = S, id = _); + //~^ ERROR S: rocket::http::uri::FromUriParam + //~^^ ERROR usize: rocket::http::uri::Ignorable + + // These are all okay. + uri!(optionals_q: _, _); uri!(optionals_q: id = 10, name = "Bob".to_string()); - //~^ ERROR Option: rocket::http::uri::FromUriParam - //~^^ ERROR: Result: rocket::http::uri::FromUriParam + uri!(optionals_q: _, "Bob".into()); + uri!(optionals_q: id = _, name = _); } diff --git a/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr index 6d3ba824..bca7f4cc 100644 --- a/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr +++ b/core/codegen/tests/ui-fail/typed-uri-bad-type.stderr @@ -1,99 +1,129 @@ -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:44:23 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:47:23 | -44 | uri!(simple: id = "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `i32` +47 | uri!(simple: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `usize` | + = help: the following implementations were found: + usize as $TRAIT + usize as $TRAIT + usize as $TRAIT = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:47:18 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:50:18 | -47 | uri!(simple: "hello"); - | ^^^^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `i32` +50 | uri!(simple: "hello"); + | ^^^^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `usize` | + = help: the following implementations were found: + usize as $TRAIT + usize as $TRAIT + usize as $TRAIT = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:50:23 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:53:23 | -50 | uri!(simple: id = 239239i64); - | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `i32` +53 | uri!(simple: id = 239239i64); + | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `usize` | + = help: the following implementations were found: + usize as $TRAIT + usize as $TRAIT + usize as $TRAIT = 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:53:31 + --> $DIR/typed-uri-bad-type.rs:56:31 | -53 | uri!(not_uri_display: 10, S); +56 | uri!(not_uri_display: 10, S); | ^ the trait `rocket::http::uri::FromUriParam` is not implemented for `S` error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:59:26 + --> $DIR/typed-uri-bad-type.rs:62:26 | -59 | uri!(optionals: id = Some(10), name = Ok("bob".into())); +62 | uri!(optionals: id = Some(10), name = Ok("bob".into())); | ^^^^ the trait `rocket::http::uri::FromUriParam>` is not implemented for `i32` | + = help: the following implementations were found: + i32 as $TRAIT + i32 as $TRAIT + i32 as $TRAIT = note: required because of the requirements on the impl of `rocket::http::uri::FromUriParam>` for `std::option::Option` error[E0277]: the trait bound `std::string::String: rocket::http::uri::FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:59:43 + --> $DIR/typed-uri-bad-type.rs:62:43 | -59 | uri!(optionals: id = Some(10), name = Ok("bob".into())); +62 | uri!(optionals: id = Some(10), name = Ok("bob".into())); | ^^ the trait `rocket::http::uri::FromUriParam>` is not implemented for `std::string::String` | = help: the following implementations were found: - > + std::string::String as $TRAIT + std::string::String as $TRAIT + std::string::String as $TRAIT + std::string::String as $TRAIT + and $N others = note: required because of the requirements on the impl of `rocket::http::uri::FromUriParam>` for `std::result::Result` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:63:20 +error[E0277]: the trait bound `isize: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:66:20 | -63 | uri!(simple_q: "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `i32` +66 | uri!(simple_q: "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `isize` | + = help: the following implementations were found: + isize as $TRAIT + isize as $TRAIT + isize as $TRAIT = note: required by `rocket::http::uri::FromUriParam::from_uri_param` -error[E0277]: the trait bound `i32: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:66:25 +error[E0277]: the trait bound `isize: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:69:25 | -66 | uri!(simple_q: id = "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `i32` +69 | uri!(simple_q: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `isize` | + = help: the following implementations were found: + isize as $TRAIT + isize as $TRAIT + isize as $TRAIT = 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:69:24 + --> $DIR/typed-uri-bad-type.rs:72:24 | -69 | uri!(other_q: 100, S); +72 | uri!(other_q: 100, S); | ^ the trait `rocket::http::uri::FromUriParam` is not implemented for `S` error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:72:26 + --> $DIR/typed-uri-bad-type.rs:75:26 | -72 | uri!(other_q: rest = S, id = 100); +75 | uri!(other_q: rest = S, id = 100); | ^ the trait `rocket::http::uri::FromUriParam` is not implemented for `S` -error[E0277]: the trait bound `std::option::Option: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:79:28 +error[E0277]: the trait bound `S: rocket::http::uri::Ignorable` is not satisfied + --> $DIR/typed-uri-bad-type.rs:78:26 | -79 | uri!(optionals_q: id = 10, name = "Bob".to_string()); - | ^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `std::option::Option` +78 | uri!(other_q: rest = _, id = 100); + | ^ the trait `rocket::http::uri::Ignorable` is not implemented for `S` | - = help: the following implementations were found: - as rocket::http::uri::FromUriParam> - = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + = note: required by `rocket::http::uri::assert_ignorable` -error[E0277]: the trait bound `std::result::Result: rocket::http::uri::FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:79:39 +error[E0277]: the trait bound `usize: rocket::http::uri::Ignorable` is not satisfied + --> $DIR/typed-uri-bad-type.rs:81:34 | -79 | uri!(optionals_q: id = 10, name = "Bob".to_string()); - | ^^^^^ the trait `rocket::http::uri::FromUriParam` is not implemented for `std::result::Result` +81 | uri!(other_q: rest = S, id = _); + | ^ the trait `rocket::http::uri::Ignorable` is not implemented for `usize` | - = help: the following implementations were found: - as rocket::http::uri::FromUriParam> - = note: required by `rocket::http::uri::FromUriParam::from_uri_param` + = note: required by `rocket::http::uri::assert_ignorable` -error: aborting due to 12 previous errors +error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam` is not satisfied + --> $DIR/typed-uri-bad-type.rs:81:26 + | +81 | uri!(other_q: rest = S, id = _); + | ^ the trait `rocket::http::uri::FromUriParam` is not implemented for `S` + +error: aborting due to 13 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 index 63ee4fe6..fb0d86b3 100644 --- a/core/codegen/tests/ui-fail/typed-uris-bad-params.rs +++ b/core/codegen/tests/ui-fail/typed-uris-bad-params.rs @@ -4,7 +4,7 @@ use std::fmt; -use rocket::http::Cookies; +use rocket::http::{Cookies, RawStr}; #[post("/")] fn has_one(id: i32) { } @@ -15,6 +15,9 @@ fn has_one_guarded(cookies: Cookies, id: i32) { } #[post("/?")] fn has_two(cookies: Cookies, id: i32, name: String) { } +#[post("//")] +fn optionals(id: Option, name: Result) { } + fn main() { uri!(has_one); //~ ERROR expects 1 parameter but 0 @@ -69,4 +72,10 @@ fn main() { uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters //~^ HELP missing parameter: `name` //~^^ HELP unknown parameter: `cookies` + + uri!(optionals: id = _, name = "bob".into()); + //~^ ERROR cannot be ignored + + uri!(optionals: id = 10, name = _); + //~^ ERROR cannot be ignored } diff --git a/core/codegen/tests/ui-fail/typed-uris-bad-params.stderr b/core/codegen/tests/ui-fail/typed-uris-bad-params.stderr index 8aad4442..6ca80e67 100644 --- a/core/codegen/tests/ui-fail/typed-uris-bad-params.stderr +++ b/core/codegen/tests/ui-fail/typed-uris-bad-params.stderr @@ -1,229 +1,241 @@ error: `has_one` route uri expects 1 parameter but 0 were supplied - --> $DIR/typed-uris-bad-params.rs:19:10 + --> $DIR/typed-uris-bad-params.rs:22:10 | -19 | uri!(has_one); //~ ERROR expects 1 parameter but 0 +22 | uri!(has_one); //~ ERROR expects 1 parameter but 0 | ^^^^^^^ | = note: expected parameter: id: i32 error: `has_one` route uri expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:21:19 + --> $DIR/typed-uris-bad-params.rs:24:19 | -21 | uri!(has_one: 1, 23); //~ ERROR expects 1 parameter but 2 +24 | uri!(has_one: 1, 23); //~ ERROR expects 1 parameter but 2 | ^^^^^ | = note: expected parameter: id: i32 error: `has_one` route uri expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:22:19 + --> $DIR/typed-uris-bad-params.rs:25:19 | -22 | uri!(has_one: "Hello", 23, ); //~ ERROR expects 1 parameter but 2 +25 | uri!(has_one: "Hello", 23, ); //~ ERROR expects 1 parameter but 2 | ^^^^^^^^^^^^ | = note: expected parameter: id: i32 error: `has_one_guarded` route uri expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:23:27 + --> $DIR/typed-uris-bad-params.rs:26:27 | -23 | uri!(has_one_guarded: "hi", 100); //~ ERROR expects 1 parameter but 2 +26 | uri!(has_one_guarded: "hi", 100); //~ ERROR expects 1 parameter but 2 | ^^^^^^^^^ | = note: expected parameter: id: i32 error: `has_two` route uri expects 2 parameters but 3 were supplied - --> $DIR/typed-uris-bad-params.rs:25:19 + --> $DIR/typed-uris-bad-params.rs:28:19 | -25 | uri!(has_two: 10, "hi", "there"); //~ ERROR expects 2 parameters but 3 +28 | uri!(has_two: 10, "hi", "there"); //~ ERROR expects 2 parameters but 3 | ^^^^^^^^^^^^^^^^^ | = note: expected parameters: id: i32, name: String error: `has_two` route uri expects 2 parameters but 1 was supplied - --> $DIR/typed-uris-bad-params.rs:26:19 + --> $DIR/typed-uris-bad-params.rs:29:19 | -26 | uri!(has_two: 10); //~ ERROR expects 2 parameters but 1 +29 | uri!(has_two: 10); //~ ERROR expects 2 parameters but 1 | ^^ | = note: expected parameters: id: i32, name: String error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:28:19 + --> $DIR/typed-uris-bad-params.rs:31:19 | -28 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters +31 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:28:29 + --> $DIR/typed-uris-bad-params.rs:31:29 | -28 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters +31 | uri!(has_one: id = 100, name = "hi"); //~ ERROR invalid parameters | ^^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:31:19 + --> $DIR/typed-uris-bad-params.rs:34:19 | -31 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters +34 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:31:19 + --> $DIR/typed-uris-bad-params.rs:34:19 | -31 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters +34 | uri!(has_one: name = 100, id = 100); //~ ERROR invalid parameters | ^^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:34:19 + --> $DIR/typed-uris-bad-params.rs:37:19 | -34 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters +37 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:34:19 + --> $DIR/typed-uris-bad-params.rs:37:19 | -34 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters +37 | uri!(has_one: name = 100, age = 50, id = 100); //~ ERROR invalid parameters | ^^^^ ^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:37:19 + --> $DIR/typed-uris-bad-params.rs:40:19 | -37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters +40 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:37:19 + --> $DIR/typed-uris-bad-params.rs:40:19 | -37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters +40 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters | ^^^^ ^^^ help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:37:51 + --> $DIR/typed-uris-bad-params.rs:40:51 | -37 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters +40 | uri!(has_one: name = 100, age = 50, id = 100, id = 50); //~ ERROR invalid parameters | ^^ -error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:41:19 - | -41 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters - | ^^^^^^^^^^^^^^^^^^ - | - = note: uri parameters are: id: i32 -help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:41:29 - | -41 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters - | ^^ - error: invalid parameters for `has_one` route uri --> $DIR/typed-uris-bad-params.rs:44:19 | -44 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters - | ^^^^^^^^^^^^^^^^^^^ +44 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters + | ^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: duplicate parameter: `id` --> $DIR/typed-uris-bad-params.rs:44:29 | -44 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters +44 | uri!(has_one: id = 100, id = 100); //~ ERROR invalid parameters | ^^ error: invalid parameters for `has_one` route uri --> $DIR/typed-uris-bad-params.rs:47:19 | -47 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters +47 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters + | ^^^^^^^^^^^^^^^^^^^ + | + = note: uri parameters are: id: i32 +help: duplicate parameter: `id` + --> $DIR/typed-uris-bad-params.rs:47:29 + | +47 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters + | ^^ + +error: invalid parameters for `has_one` route uri + --> $DIR/typed-uris-bad-params.rs:50:19 + | +50 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters | ^^^^^^^^^^^ | = note: uri parameters are: id: i32 = help: missing parameter: `id` help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:47:19 + --> $DIR/typed-uris-bad-params.rs:50:19 | -47 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters +50 | uri!(has_one: name = "hi"); //~ ERROR invalid parameters | ^^^^ -error: invalid parameters for `has_one_guarded` route uri - --> $DIR/typed-uris-bad-params.rs:51:27 - | -51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: uri parameters are: id: i32 -help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:51:27 - | -51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters - | ^^^^^^^ - error: invalid parameters for `has_one_guarded` route uri --> $DIR/typed-uris-bad-params.rs:54:27 | -54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters +54 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:54:37 + --> $DIR/typed-uris-bad-params.rs:54:27 | -54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters +54 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ ERROR invalid parameters + | ^^^^^^^ + +error: invalid parameters for `has_one_guarded` route uri + --> $DIR/typed-uris-bad-params.rs:57:27 + | +57 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: uri parameters are: id: i32 +help: unknown parameter: `cookies` + --> $DIR/typed-uris-bad-params.rs:57:37 + | +57 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters | ^^^^^^^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:57:19 + --> $DIR/typed-uris-bad-params.rs:60:19 | -57 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters +60 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:57:29 + --> $DIR/typed-uris-bad-params.rs:60:29 | -57 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters +60 | uri!(has_two: id = 100, id = 100, ); //~ ERROR invalid parameters | ^^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:61:19 + --> $DIR/typed-uris-bad-params.rs:64:19 | -61 | uri!(has_two: name = "hi"); //~ ERROR invalid parameters +64 | uri!(has_two: name = "hi"); //~ ERROR invalid parameters | ^^^^^^^^^^^ | = note: uri parameters are: id: i32, name: String = help: missing parameter: `id` error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:64:19 + --> $DIR/typed-uris-bad-params.rs:67:19 | -64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters +67 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:64:19 + --> $DIR/typed-uris-bad-params.rs:67:19 | -64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters +67 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters | ^^^^^^^ help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:64:45 + --> $DIR/typed-uris-bad-params.rs:67:45 | -64 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters +67 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10); //~ ERROR invalid parameters | ^^ ^^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:69:19 + --> $DIR/typed-uris-bad-params.rs:72:19 | -69 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters +72 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:69:29 + --> $DIR/typed-uris-bad-params.rs:72:29 | -69 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters +72 | uri!(has_two: id = 100, cookies = "hi"); //~ ERROR invalid parameters | ^^^^^^^ -error: aborting due to 19 previous errors +error: path parameters cannot be ignored + --> $DIR/typed-uris-bad-params.rs:76:26 + | +76 | uri!(optionals: id = _, name = "bob".into()); + | ^ + +error: path parameters cannot be ignored + --> $DIR/typed-uris-bad-params.rs:79:37 + | +79 | uri!(optionals: id = 10, name = _); + | ^ + +error: aborting due to 21 previous errors diff --git a/core/http/src/uri/formatter.rs b/core/http/src/uri/formatter.rs index e7c72aa6..47ea1951 100644 --- a/core/http/src/uri/formatter.rs +++ b/core/http/src/uri/formatter.rs @@ -153,29 +153,14 @@ pub struct Formatter<'i, P: UriPart> { inner: &'i mut (dyn Write + 'i), previous: bool, fresh: bool, - delimiter: char, _marker: PhantomData

, } -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, P: UriPart> Formatter<'i, P> { #[inline(always)] - fn make(inner: &'i mut (dyn Write + 'i), delimiter: char) -> Self { + crate fn new(inner: &'i mut (dyn Write + 'i)) -> Self { Formatter { - inner, delimiter, + inner, prefixes: SmallVec::new(), previous: false, fresh: true, @@ -229,13 +214,13 @@ impl<'i, P: UriPart> Formatter<'i, P> { // 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.fresh && P::DELIMITER == '/' { if self.previous { - self.inner.write_char(self.delimiter)?; + self.inner.write_char(P::DELIMITER)?; } - } else if self.fresh && self.delimiter == '&' { + } else if self.fresh && P::DELIMITER == '&' { if self.previous { - self.inner.write_char(self.delimiter)?; + self.inner.write_char(P::DELIMITER)?; } if !self.prefixes.is_empty() { @@ -457,8 +442,9 @@ impl<'a> UriArguments<'a> { } }; - let query: Option> = self.query.map(|query| match query { - Static(query) => query.into(), + let query: Option> = self.query.and_then(|q| match q { + Static(query) => Some(query.into()), + Dynamic(args) if args.is_empty() => None, Dynamic(args) => { let mut string = String::new(); { @@ -472,7 +458,7 @@ impl<'a> UriArguments<'a> { } } - string.into() + Some(string.into()) } }); diff --git a/core/http/src/uri/from_uri_param.rs b/core/http/src/uri/from_uri_param.rs index 34f7b402..2a0c9c13 100644 --- a/core/http/src/uri/from_uri_param.rs +++ b/core/http/src/uri/from_uri_param.rs @@ -5,12 +5,30 @@ use uri::{self, UriPart, UriDisplay}; /// Conversion trait for parameters used in [`uri!`] invocations. /// -/// Rocket provides a blanket implementation for all types that implement -/// [`UriDisplay`]. As such, this trait typically does not need to be implemented. -/// Instead, implement [`UriDisplay`]. -/// /// # Overview /// +/// In addition to implementing [`UriDisplay`], to use a custom type in a `uri!` +/// expression, the `FromUriParam` trait must be implemented. The `UriDisplay` +/// derive automatically generates _identity_ implementations of `FromUriParam`, +/// so in the majority of cases, as with `UriDisplay`, this trait is never +/// implemented manually. +/// +/// In the rare case that `UriDisplay` is implemented manually, this trait, too, +/// must be implemented explicitly. In the majority of cases, implementation can +/// be automated. Rocket provides the [`impl_from_uri_param_identity`] macro to +/// generate the _identity_ implementations automatically. For a type `T`, these +/// are: +/// +/// * `impl FromUriParam for T` +/// * `impl<'x, P: UriPart> FromUriParam for T` +/// * `impl<'x, P: UriPart> FromUriParam for T` +/// +/// See [`impl_from_uri_param_identity`] for usage details. +/// +/// [`impl_from_uri_param_identity`]: ../macro.impl_from_uri_param_identity.html +/// +/// # Code Generation +/// /// 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`, `>::from_uri_param` is @@ -51,7 +69,30 @@ use uri::{self, UriPart, UriDisplay}; /// /// # Provided Implementations /// -/// See [Foreign Impls](#foreign-impls) for implementations provided by Rocket. +/// The following types have _identity_ implementations: +/// +/// * `String`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`, +/// `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `IpAddr`, +/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `&RawStr`, `Cow` +/// +/// The following conversions are implemented: +/// +/// * `&str` to `String` +/// * `&str` to `RawStr` +/// * `String` to `&str` +/// * `String` to `RawStr` +/// +/// The following types have _identity_ implementations _only in [`Path`]_: +/// +/// * `&Path`, `PathBuf` +/// +/// The following conversions are implemented _only in [`Path`]_: +/// +/// * `&str` to `&Path` +/// * `&str` to `PathBuf` +/// * `PathBuf` to `&Path` +/// +/// See [Foreign Impls](#foreign-impls) for all provided implementations. /// /// # Implementing /// @@ -145,6 +186,7 @@ use uri::{self, UriPart, UriDisplay}; /// [`uri!`]: ::rocket_codegen::uri /// [`UriDisplay`]: uri::UriDisplay /// [`FromUriParam::Target`]: uri::FromUriParam::Target +/// [`Path`]: uri::Path pub trait FromUriParam { /// The resulting type of this conversion. type Target: UriDisplay

; @@ -155,57 +197,121 @@ pub trait FromUriParam { fn from_uri_param(param: T) -> Self::Target; } -impl> FromUriParam for T { - type Target = T; - #[inline(always)] - fn from_uri_param(param: T) -> T { param } +use std::{borrow::Cow, net::{IpAddr, Ipv4Addr, Ipv6Addr}}; + +#[doc(hidden)] +#[macro_export(local_inner_macros)] +macro_rules! impl_conversion_ref { + ($(($($l:tt)+) $A:ty => $B:ty),*) => ( impl_conversion_ref!(@_ $(($($l)+,) $A => $B),*); ); + ($($A:ty => $B:ty),*) => ( impl_conversion_ref!(@_ $(() $A => $B),*); ); + + (@_ $(($($l:tt)*) $A:ty => $B:ty),*) => ($( + impl_conversion_ref!([P] ($($l)* P: $crate::uri::UriPart) $A => $B); + )*); + + ($([$P:ty] ($($l:tt)*) $A:ty => $B:ty),*) => ($( + impl_conversion_ref!(@_ [$P] ($($l)*) $A => $B); + impl_conversion_ref!(@_ [$P] ('x, $($l)*) &'x $A => $B); + impl_conversion_ref!(@_ [$P] ('x, $($l)*) &'x mut $A => $B); + )*); + + ($([$P:ty] $A:ty => $B:ty),*) => ( impl_conversion_ref!($([$P] () $A => $B),*);); + + (@_ [$P:ty] ($($l:tt)*) $A:ty => $B:ty) => ( + impl<$($l)*> $crate::uri::FromUriParam<$P, $A> for $B { + type Target = $A; + #[inline(always)] fn from_uri_param(param: $A) -> $A { param } + } + ); } -impl<'a, P: UriPart, T: UriDisplay

> FromUriParam for T { - type Target = &'a T; - #[inline(always)] - fn from_uri_param(param: &'a T) -> &'a T { param } +/// Macro to automatically generated _identity_ [`FromUriParam`] trait +/// implementations. +/// +/// For a type `T`, the _identity_ implementations of `FromUriParam` are: +/// +/// * `impl UriPart> FromUriParam for T` +/// * `impl<'x> FromUriParam for T` +/// * `impl<'x> FromUriParam for T` +/// +/// where `P` is one of: +/// +/// * `P: UriPart` (the generic `P`) +/// * [`Path`] +/// * [`Query`] +/// +/// This macro can be invoked in four ways: +/// +/// 1. `impl_from_uri_param_identity!(Type);` +/// +/// Generates the three _identity_ implementations for the generic `P`. +/// +/// * Example: `impl_from_uri_param_identity!(MyType);` +/// * Generates: `impl FromUriParam for MyType { ... }` +/// +/// 2. `impl_from_uri_param_identity!((generics*) Type);` +/// +/// Generates the three _identity_ implementations for the generic `P`, +/// adding the tokens `generics` to the `impl` generics of the generated +/// implementation. +/// +/// * Example: `impl_from_uri_param_identity!(('a) MyType<'a>);` +/// * Generates: `impl<'a, P: UriPart> FromUriParam for MyType<'a> { ... }` +/// +/// 3. `impl_from_uri_param_identity!([Part] Type);` +/// +/// Generates the three _identity_ implementations for the `UriPart` +/// `Part`, where `Part` is a path to [`Path`] or [`Query`]. +/// +/// * Example: `impl_from_uri_param_identity!([Path] MyType);` +/// * Generates: `impl FromUriParam for MyType { ... }` +/// +/// 4. `impl_from_uri_param_identity!([Part] (generics*) Type);` +/// +/// See 2 and 3. +/// +/// * Example: `impl_from_uri_param_identity!([Path] ('a) MyType<'a>);` +/// * Generates: `impl<'a> FromUriParam for MyType<'a> { ... }` +/// +/// [`FromUriParam`]: uri::FromUriParam +/// [`Path`]: uri::Path +/// [`Query`]: uri::Query +#[macro_export(local_inner_macros)] +macro_rules! impl_from_uri_param_identity { + ($(($($l:tt)*) $T:ty),*) => ($( impl_conversion_ref!(($($l)*) $T => $T); )*); + ($([$P:ty] ($($l:tt)*) $T:ty),*) => ($( impl_conversion_ref!([$P] ($($l)*) $T => $T); )*); + ($([$P:ty] $T:ty),*) => ($( impl_conversion_ref!([$P] $T => $T); )*); + ($($T:ty),*) => ($( impl_conversion_ref!($T => $T); )*); } -impl<'a, P: UriPart, T: UriDisplay

> FromUriParam for T { - type Target = &'a mut T; - #[inline(always)] - fn from_uri_param(param: &'a mut T) -> &'a mut T { param } +impl_from_uri_param_identity! { + String, + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize, + f32, f64, bool, + IpAddr, Ipv4Addr, Ipv6Addr } -/// A no cost conversion allowing an `&str` to be used in place of a `String`. -impl<'a, P: UriPart> FromUriParam for String { - type Target = &'a str; - #[inline(always)] - fn from_uri_param(param: &'a str) -> &'a str { param } +impl_from_uri_param_identity! { + ('a) &'a str, + ('a) &'a RawStr, + ('a) Cow<'a, str> } -/// A no cost conversion allowing an `&str` to be used in place of an `&RawStr`. -impl<'a, 'b, P: UriPart> FromUriParam for &'b RawStr { - type Target = &'a str; - #[inline(always)] - fn from_uri_param(param: &'a str) -> &'a str { param } +impl_conversion_ref! { + ('a) &'a str => String, + ('a, 'b) &'a str => &'b RawStr, + + ('a) String => &'a str, + ('a) String => &'a RawStr } -/// A no cost conversion allowing a `String` to be used in place of an `&RawStr`. -impl<'a, P: UriPart> FromUriParam for &'a RawStr { - type Target = String; - #[inline(always)] - fn from_uri_param(param: String) -> String { param } -} +impl_from_uri_param_identity!([uri::Path] ('a) &'a Path); +impl_from_uri_param_identity!([uri::Path] PathBuf); -/// A no cost conversion allowing a `String` to be used in place of an `&str`. -impl<'a, P: UriPart> FromUriParam 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 for PathBuf { - type Target = &'a Path; - #[inline(always)] - fn from_uri_param(param: &'a Path) -> &'a Path { param } +impl_conversion_ref! { + [uri::Path] ('a) &'a Path => PathBuf, + [uri::Path] ('a) PathBuf => &'a Path } /// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`. @@ -218,9 +324,18 @@ impl<'a> FromUriParam for PathBuf { } } -/// A no cost conversion allowing any `T` to be used in place of an `Option` -/// in path parts. -impl> FromUriParam for Option { +/// A no cost conversion allowing an `&&str` to be used in place of a `PathBuf`. +impl<'a, 'b> FromUriParam for PathBuf { + type Target = &'b Path; + + #[inline(always)] + fn from_uri_param(param: &'a &'b str) -> &'b Path { + Path::new(*param) + } +} + +/// A no cost conversion allowing any `T` to be used in place of an `Option`. +impl> FromUriParam for Option { type Target = T::Target; #[inline(always)] @@ -229,9 +344,8 @@ impl> FromUriParam for Option } } -/// A no cost conversion allowing any `T` to be used in place of an `Result` in path parts. -impl> FromUriParam for Result { +/// A no cost conversion allowing `T` to be used in place of an `Result`. +impl> FromUriParam for Result { type Target = T::Target; #[inline(always)] diff --git a/core/http/src/uri/mod.rs b/core/http/src/uri/mod.rs index 9532ad50..3e7c619f 100644 --- a/core/http/src/uri/mod.rs +++ b/core/http/src/uri/mod.rs @@ -50,7 +50,9 @@ mod private { /// [`Path`]: uri::Path /// [`UriDisplay`]: uri::UriDisplay /// [`Formatter`]: uri::Formatter -pub trait UriPart: private::Sealed { } +pub trait UriPart: private::Sealed { + const DELIMITER: char; +} /// Marker type indicating use of a type for the path [`UriPart`] of a URI. /// @@ -77,5 +79,10 @@ pub enum Path { } /// [`UriPart`]: uri::UriPart pub enum Query { } -impl UriPart for Path { } -impl UriPart for Query { } +impl UriPart for Path { + const DELIMITER: char = '/'; +} + +impl UriPart for Query { + const DELIMITER: char = '&'; +} diff --git a/core/http/src/uri/uri_display.rs b/core/http/src/uri/uri_display.rs index 509e80b0..77c8f94a 100644 --- a/core/http/src/uri/uri_display.rs +++ b/core/http/src/uri/uri_display.rs @@ -1,12 +1,11 @@ use std::{fmt, path}; use std::borrow::Cow; -use percent_encoding::utf8_percent_encode; +use RawStr; +use uri::{Uri, UriPart, Path, Query, Formatter}; -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!`. +/// Trait implemented by types that can be displayed as part of a URI in +/// [`uri!`]. /// /// Types implementing this trait can be displayed in a URI-safe manner. Unlike /// `Display`, the string written by a `UriDisplay` implementation must be @@ -14,7 +13,7 @@ use {RawStr, ext::Normalize}; /// percent-encoded or consist only of characters that are alphanumeric, "-", /// ".", "_", or "~" - the "unreserved" characters. /// -/// # Marker Generic: `UriDisplay` vs. `UriDisplay` +/// # Marker Generic: `Path`, `Query` /// /// The [`UriPart`] parameter `P` in `UriDisplay

` must be either [`Path`] or /// [`Query`] (see the [`UriPart`] documentation for how this is enforced), @@ -51,11 +50,12 @@ use {RawStr, ext::Normalize}; /// /// # Code Generation /// -/// When the `uri!` macro is used to generate a URI for a route, the types for +/// When the [`uri!`] macro is used to generate a URI for a route, the types for /// the route's _path_ URI parameters must implement `UriDisplay`, while /// types in the route's query parameters must implement `UriDisplay`. -/// The `UriDisplay` implementation for these types is used when generating the -/// URI. +/// Any parameters ignored with `_` must be of a type that implements +/// [`Ignorable`]. 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: @@ -64,7 +64,7 @@ use {RawStr, ext::Normalize}; /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; /// #[get("/item/?")] -/// fn get_item(id: i32, track: String) { /* .. */ } +/// fn get_item(id: i32, track: Option) { /* .. */ } /// ``` /// /// A URI for this route can be generated as follows: @@ -74,7 +74,7 @@ use {RawStr, ext::Normalize}; /// # #[macro_use] extern crate rocket; /// # type T = (); /// # #[get("/item/?")] -/// # fn get_item(id: i32, track: String) { /* .. */ } +/// # fn get_item(id: i32, track: Option) { /* .. */ } /// # /// // With unnamed parameters. /// uri!(get_item: 100, "inbound"); @@ -82,6 +82,11 @@ use {RawStr, ext::Normalize}; /// // With named parameters. /// uri!(get_item: id = 100, track = "inbound"); /// uri!(get_item: track = "inbound", id = 100); +/// +/// // Ignoring `track`. +/// uri!(get_item: 100, _); +/// uri!(get_item: id = 100, track = _); +/// uri!(get_item: track = _, id = 100); /// ``` /// /// After verifying parameters and their types, Rocket will generate code @@ -96,10 +101,12 @@ use {RawStr, ext::Normalize}; /// ``` /// /// For this expression to typecheck, `i32` must implement `UriDisplay` -/// and `Value` must implement `UriDisplay`. As can be seen, the -/// implementations will be used to display the value in a URI-safe manner. +/// and `&str` must implement `UriDisplay`. What's more, when `track` is +/// ignored, `Option` is required to implement [`Ignorable`]. As can be +/// seen, the implementations will be used to display the value in a URI-safe +/// manner. /// -/// [`uri!`]: /rocket_codegen/#typed-uris-uri +/// [`uri!`]: ../../../rocket_codegen/macro.uri.html /// /// # Provided Implementations /// @@ -161,6 +168,8 @@ use {RawStr, ext::Normalize}; /// If the `Result` is `Ok`, uses the implementation of `UriDisplay` for /// `T`. Otherwise, nothing is rendered. /// +/// [`FromUriParam`]: uri::FromUriParam +/// /// # Deriving /// /// Manually implementing `UriDisplay` should be done with care. For most use @@ -195,6 +204,7 @@ use {RawStr, ext::Normalize}; /// [`Formatter::write_value()`] for every unnamed field. See the [`UriDisplay` /// derive] documentation for full details. /// +/// [`Ignorable`]: uri::Ignorable /// [`UriDisplay` derive]: ../../../rocket_codegen/derive.UriDisplay.html /// [`Formatter::write_named_value()`]: uri::Formatter::write_named_value() /// [`Formatter::write_value()`]: uri::Formatter::write_value() @@ -250,19 +260,22 @@ use {RawStr, ext::Normalize}; /// } /// /// use std::fmt; -/// use rocket::http::uri::{Formatter, UriDisplay, Path}; +/// use rocket::http::impl_from_uri_param_identity; +/// use rocket::http::uri::{Formatter, FromUriParam, UriDisplay, Path}; /// use rocket::response::Redirect; /// /// impl UriDisplay 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. +/// // 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) -> fmt::Result { /// f.write_value(&format!("name:{}", self.0)) /// } /// } /// +/// impl_from_uri_param_identity!([Path] Name); +/// /// #[get("/name/")] /// fn redirector(name: Name) -> Redirect { /// Redirect::to(uri!(real: name)) @@ -281,26 +294,15 @@ pub trait UriDisplay { fn fmt(&self, f: &mut Formatter

) -> fmt::Result; } -impl<'a> fmt::Display for &'a UriDisplay { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - UriDisplay::fmt(*self, &mut >::new(f)) - } -} - -impl<'a> fmt::Display for &'a UriDisplay { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - UriDisplay::fmt(*self, &mut >::new(f)) - } -} - -/// Percent-encodes the raw string. -impl UriDisplay

for RawStr { +impl<'a, P: UriPart> fmt::Display for &'a UriDisplay

{ #[inline(always)] - fn fmt(&self, f: &mut Formatter

) -> fmt::Result { - f.write_raw(&Uri::percent_encode(self.as_str())) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + UriDisplay::fmt(*self, &mut >::new(f)) } } +// Direct implementations: these are the leaves of a call to `UriDisplay::fmt`. + /// Percent-encodes the raw string. impl UriDisplay

for str { #[inline(always)] @@ -309,37 +311,19 @@ impl UriDisplay

for str { } } -/// Percent-encodes the raw string. -impl<'a, P: UriPart> UriDisplay

for Cow<'a, str> { - #[inline(always)] - fn fmt(&self, f: &mut Formatter

) -> fmt::Result { - f.write_raw(&Uri::percent_encode(self)) - } -} - -/// Percent-encodes the raw string. -impl UriDisplay

for String { - #[inline(always)] - fn fmt(&self, f: &mut Formatter

) -> fmt::Result { - f.write_raw(&Uri::percent_encode(self.as_str())) - } -} - -/// Percent-encodes each segment in the path and normalizes separators. -impl UriDisplay for path::PathBuf { - #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.as_path().fmt(f) - } -} - /// Percent-encodes each segment in the path and normalizes separators. impl UriDisplay for path::Path { - #[inline] fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let string = self.normalized_str(); - let enc: Cow = utf8_percent_encode(&string, UNSAFE_PATH_ENCODE_SET).into(); - f.write_raw(&enc) + use std::path::Component; + + for component in self.components() { + match component { + Component::Prefix(_) | Component::RootDir => continue, + _ => f.write_value(&component.as_os_str().to_string_lossy())? + } + } + + Ok(()) } } @@ -365,38 +349,240 @@ impl_with_display! { IpAddr, Ipv4Addr, Ipv6Addr } -macro_rules! impl_for_ref { - ($($T:ty),+) => {$( - /// Uses the implementation of `UriDisplay` for `T`. - impl<'a, P: UriPart, T: UriDisplay

+ ?Sized> UriDisplay

for $T { - #[inline(always)] - fn fmt(&self, f: &mut Formatter

) -> fmt::Result { - UriDisplay::fmt(*self, f) - } - } - )+} +// These are second level implementations: they all defer to an existing +// implementation. + +/// Percent-encodes the raw string. Defers to `str`. +impl UriDisplay

for RawStr { + #[inline(always)] + fn fmt(&self, f: &mut Formatter

) -> fmt::Result { + self.as_str().fmt(f) + } } -impl_for_ref!(&'a mut T, &'a T); +/// Percent-encodes the raw string. Defers to `str`. +impl UriDisplay

for String { + #[inline(always)] + fn fmt(&self, f: &mut Formatter

) -> fmt::Result { + self.as_str().fmt(f) + } +} +/// Percent-encodes the raw string. Defers to `str`. +impl<'a, P: UriPart> UriDisplay

for Cow<'a, str> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter

) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +/// Percent-encodes each segment in the path and normalizes separators. +impl UriDisplay for path::PathBuf { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.as_path().fmt(f) + } +} + +/// Defers to the `UriDisplay

` implementation for `T`. +impl<'a, P: UriPart, T: UriDisplay

+ ?Sized> UriDisplay

for &'a T { + #[inline(always)] + fn fmt(&self, f: &mut Formatter

) -> fmt::Result { + UriDisplay::fmt(*self, f) + } +} + +/// Defers to the `UriDisplay

` implementation for `T`. +impl<'a, P: UriPart, T: UriDisplay

+ ?Sized> UriDisplay

for &'a mut T { + #[inline(always)] + fn fmt(&self, f: &mut Formatter

) -> fmt::Result { + UriDisplay::fmt(*self, f) + } +} + +/// Defers to the `UriDisplay` implementation for `T`. impl> UriDisplay for Option { #[inline(always)] fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(v) = self { - f.write_value(&v)?; + match self { + Some(v) => v.fmt(f), + None => Ok(()) } - - Ok(()) } } -impl> UriDisplay for Result { +/// Defers to the `UriDisplay` implementation for `T`. +impl, E> UriDisplay for Result { #[inline(always)] fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Ok(v) = self { - f.write_value(&v)?; + match self { + Ok(v) => v.fmt(f), + Err(_) => Ok(()) } - - Ok(()) + } +} + +// And finally, the `Ignorable` trait, which has sugar of `_` in the `uri!` +// macro, which expands to a typecheck. + +/// Trait implemented by types that can be ignored in `uri!`. +/// +/// When a parameter is explicitly ignored in `uri!` by supplying `_` as the +/// parameter's value, that parameter's type is required to implement this +/// trait for the corresponding `UriPart`. +/// +/// ```rust +/// # #![feature(proc_macro_hygiene, decl_macro)] +/// # #[macro_use] extern crate rocket; +/// #[get("/item/?")] +/// fn get_item(id: i32, track: Option) { /* .. */ } +/// +/// // Ignore the `track` parameter: `Option` must be `Ignorable`. +/// uri!(get_item: 100, _); +/// uri!(get_item: id = 100, track = _); +/// +/// // Provide a value for `track`. +/// uri!(get_item: 100, 4); +/// uri!(get_item: id = 100, track = 4); +/// ``` +/// +/// # Implementations +/// +/// Only `Option` and `Result` implement this trait. You may implement +/// this trait for your own ignorable types as well: +/// +/// ```rust +/// # #[macro_use] extern crate rocket; +/// use rocket::http::uri::{Ignorable, Query}; +/// +/// # struct MyType; +/// impl Ignorable for MyType { } +/// ``` +pub trait Ignorable { } + +impl Ignorable for Option { } +impl Ignorable for Result { } + +#[doc(hidden)] +pub fn assert_ignorable>() { } + +#[cfg(test)] +mod uri_display_tests { + use std::path; + use uri::{FromUriParam, UriDisplay, Query, Path}; + + macro_rules! uri_display { + (<$P:ident, $Target:ty> $source:expr) => ({ + let tmp = $source; + let target = <$Target as FromUriParam<$P, _>>::from_uri_param(tmp); + format!("{}", &target as &dyn UriDisplay<$P>) + }) + } + + macro_rules! assert_display { + (<$P:ident, $Target:ty> $source:expr, $expected:expr) => ({ + assert_eq!(uri_display!(<$P, $Target> $source), $expected); + }) + } + + #[test] + fn uri_display_encoding() { + assert_display!( "hello", "hello"); + assert_display!( "hi hi", "hi%20hi"); + assert_display!( "hi hi", "hi%20hi"); + assert_display!( &"hi hi", "hi%20hi"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + + assert_display!( "hello", "hello"); + assert_display!( "hi hi", "hi%20hi"); + assert_display!( "hi hi", "hi%20hi"); + assert_display!( &"hi hi", "hi%20hi"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + assert_display!( 10, "10"); + + assert_display!( &"hi there", "hi%20there"); + assert_display!( &10, "10"); + assert_display!( &10, "10"); + + assert_display!( &"hi there", "hi%20there"); + assert_display!( &10, "10"); + assert_display!( &10, "10"); + + assert_display!(> &"hi there", "hi%20there"); + assert_display!(> &10, "10"); + assert_display!(> &10, "10"); + assert_display!(> &"hi there", "hi%20there"); + assert_display!(> &10, "10"); + assert_display!(> &10, "10"); + + assert_display!(> &"hi there", "hi%20there"); + assert_display!(> &10, "10"); + assert_display!(> &10, "10"); + assert_display!(> &"hi there", "hi%20there"); + assert_display!(> &10, "10"); + assert_display!(> &10, "10"); + } + + #[test] + fn paths() { + assert_display!( "hello", "hello"); + assert_display!( "hi there", "hi%20there"); + assert_display!( "hello/world", "hello/world"); + assert_display!( "hello//world", "hello/world"); + assert_display!( "hello/ world", "hello/%20world"); + + assert_display!( "hi/wo rld", "hi/wo%20rld"); + + assert_display!( &"hi/wo rld", "hi/wo%20rld"); + assert_display!( &"hi there", "hi%20there"); + } + + struct Wrapper(T); + + impl> FromUriParam for Wrapper { + type Target = T::Target; + + #[inline(always)] + fn from_uri_param(param: A) -> Self::Target { + T::from_uri_param(param) + } + } + + impl FromUriParam for Wrapper { + type Target = usize; + + #[inline(always)] + fn from_uri_param(param: usize) -> Self::Target { + param + } + } + + #[test] + fn uri_display_encoding_wrapped() { + assert_display!(>> &"hi there", "hi%20there"); + assert_display!(>> "hi there", "hi%20there"); + + assert_display!(>> 10, "10"); + assert_display!(>> 18, "18"); + assert_display!(>> 238, "238"); + + assert_display!(>, usize>> 238, "238"); + assert_display!(, usize>>> 123, "123"); + } + + #[test] + fn check_ignorables() { + use uri::assert_ignorable; + + assert_ignorable::>(); + assert_ignorable::>>(); + assert_ignorable::, usize>>(); + assert_ignorable::, usize>>>(); + assert_ignorable::>, usize>>(); } }