Rocket

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

commit d7933dd6fd863503a21a5c343ee9f4bc863a586a
parent 50a635ed8e7ccf06f412bf2b8bfffaef448cff74
Author: Sergio Benitez <sb@sergio.bz>
Date:   Wed,  5 Dec 2018 04:20:22 -0800

Implement ignorable 'uri!' expressions.

Closes #840.

Diffstat:
Mcore/codegen/src/bang/uri.rs | 93+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mcore/codegen/src/bang/uri_parsing.rs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcore/codegen/src/derive/uri_display.rs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mcore/codegen/src/lib.rs | 56++++++++++++++++++++++++++++++++++++++++----------------
Mcore/codegen/tests/typed-uris.rs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mcore/codegen/tests/ui-fail/typed-uri-bad-type.rs | 32++++++++++++++++++++------------
Mcore/codegen/tests/ui-fail/typed-uri-bad-type.stderr | 122+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mcore/codegen/tests/ui-fail/typed-uris-bad-params.rs | 11++++++++++-
Mcore/codegen/tests/ui-fail/typed-uris-bad-params.stderr | 146+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcore/http/src/uri/formatter.rs | 34++++++++++------------------------
Mcore/http/src/uri/from_uri_param.rs | 214++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcore/http/src/uri/mod.rs | 13++++++++++---
Mcore/http/src/uri/uri_display.rs | 344+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
13 files changed, 904 insertions(+), 355 deletions(-)

diff --git 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<TokenStream> { Ok(quote!(#path!(#input2)).into()) } -fn extract_exprs(internal: &InternalUriParams) -> Result<Vec<&Expr>> { +fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<( + impl Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>, + impl Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>, + )> +{ 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<Item = (&'a Ident, &'a Type, &'a Expr)>>( quote!(#uri_mod::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])) } -fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>( +fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a ArgExpr)>>( uri: &Origin, bindings: &mut Vec<TokenStream2>, mut items: I @@ -137,30 +162,38 @@ fn explode_query<'a, I: Iterator<Item = (&'a Ident, &'a Type, &'a Expr)>>( 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; + if segment.kind == Kind::Static { + let string = &segment.string; + return Some(quote!(#query_arg::Raw(#string))); + } - 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) - ) + 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<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 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 @@ -13,9 +13,15 @@ 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<Self> { + if input.peek(Token![_]) { + return Ok(ArgExpr::Ignored(input.parse::<Token![_]>()?)); + } + + input.parse::<Expr>().map(ArgExpr::Expr) + } +} + impl Parse for Arg { fn parse(input: ParseStream) -> parse::Result<Self> { let has_key = input.peek2(Token![=]); if has_key { let ident = input.parse::<Ident>()?; let eq_token = input.parse::<Token![=]>()?; - let expr = input.parse::<Expr>()?; + let expr = input.parse::<ArgExpr>()?; Ok(Arg::Named(ident, eq_token, expr)) } else { - let expr = input.parse::<Expr>()?; + let expr = input.parse::<ArgExpr>()?; 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<impl Iterator<Item = (&Ident, &Expr)>> { + fn named(&self) -> Option<impl Iterator<Item = (&Ident, &ArgExpr)>> { match self { Args::Named(args) => Some(args.iter().map(|arg| arg.named())), _ => None } } - fn unnamed(&self) -> Option<impl Iterator<Item = &Expr>> { + fn unnamed(&self) -> Option<impl Iterator<Item = &ArgExpr>> { 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 @@ -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 @@ -858,9 +858,11 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// # #![feature(proc_macro_hygiene, decl_macro)] /// # #[macro_use] extern crate rocket; /// # -/// #[get("/person/<name>/<age>")] -/// fn person(name: String, age: u8) -> String { -/// format!("Hello {}! You're {} years old.", name, age) +/// #[get("/person/<name>?<age>")] +/// fn person(name: String, age: Option<u8>) -> 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/<name>/<age>")] -/// # fn person(name: String, age: u8) { } +/// # #[get("/person/<name>?<age>")] +/// # fn person(name: String, age: Option<u8>) { } /// # /// // 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<S>` 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 @@ -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("/<foo>/<bar>?<q1>&<rest..>")] +fn optionals( + foo: Option<usize>, + bar: Result<String, &RawStr>, + q1: Result<usize, &RawStr>, + rest: Option<Form<Third>> +) { } + +#[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 @@ -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("/<id>")] -fn simple(id: i32) { } +fn simple(id: usize) { } #[post("/<id>/<name>")] fn not_uri_display(id: i32, name: S) { } @@ -32,7 +35,7 @@ impl<'q> FromQuery<'q> for S { } #[post("/?<id>")] -fn simple_q(id: i32) { } +fn simple_q(id: isize) { } #[post("/?<id>&<rest..>")] fn other_q(id: usize, rest: S) { } @@ -42,13 +45,13 @@ fn optionals_q(id: Option<i32>, name: Result<String, &RawStr>) { } fn main() { uri!(simple: id = "hi"); - //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> + //~^ ERROR usize: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> uri!(simple: "hello"); - //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> + //~^ ERROR usize: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str> uri!(simple: id = 239239i64); - //~^ ERROR i32: rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64> + //~^ ERROR usize: 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, _> @@ -61,10 +64,10 @@ fn main() { //~^^ 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> + //~^ ERROR isize: 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> + //~^ ERROR isize: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str> uri!(other_q: 100, S); //~^ ERROR S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _> @@ -72,11 +75,16 @@ fn main() { 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())); + uri!(other_q: rest = _, id = 100); + //~^ ERROR S: rocket::http::uri::Ignorable<rocket::http::uri::Query> + + uri!(other_q: rest = S, id = _); + //~^ ERROR S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _> + //~^^ ERROR usize: rocket::http::uri::Ignorable<rocket::http::uri::Query> - // For queries, we need to know the exact variant. + // These are all okay. + uri!(optionals_q: _, _); 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> + 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 @@ -1,99 +1,129 @@ -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 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:47:23 | -44 | uri!(simple: id = "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `i32` +47 | uri!(simple: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` 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<rocket::http::uri::Path, &str>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:47:18 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:50:18 | -47 | uri!(simple: "hello"); - | ^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `i32` +50 | uri!(simple: "hello"); + | ^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, &str>` 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<rocket::http::uri::Path, i64>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:50:23 +error[E0277]: the trait bound `usize: rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:53:23 | -50 | uri!(simple: id = 239239i64); - | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `i32` +53 | uri!(simple: id = 239239i64); + | ^^^^^^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Path, i64>` 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<rocket::http::uri::Path, _>` 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<rocket::http::uri::Path, _>` is not implemented for `S` 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 + --> $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<rocket::http::uri::Path, std::option::Option<{integer}>>` 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<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 + --> $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<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>> + 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<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 +error[E0277]: the trait bound `isize: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:66:20 | -63 | uri!(simple_q: "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `i32` +66 | uri!(simple_q: "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` 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<rocket::http::uri::Query, &str>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:66:25 +error[E0277]: the trait bound `isize: rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:69:25 | -66 | uri!(simple_q: id = "hi"); - | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `i32` +69 | uri!(simple_q: id = "hi"); + | ^^^^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, &str>` 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<rocket::http::uri::Query, _>` 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<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 + --> $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<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 +error[E0277]: the trait bound `S: rocket::http::uri::Ignorable<rocket::http::uri::Query>` 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<rocket::http::uri::Query, {integer}>` is not implemented for `std::option::Option<i32>` +78 | uri!(other_q: rest = _, id = 100); + | ^ the trait `rocket::http::uri::Ignorable<rocket::http::uri::Query>` is not implemented for `S` | - = 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` + = note: required by `rocket::http::uri::assert_ignorable` -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 +error[E0277]: the trait bound `usize: rocket::http::uri::Ignorable<rocket::http::uri::Query>` 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<rocket::http::uri::Query, std::string::String>` is not implemented for `std::result::Result<std::string::String, &rocket::http::RawStr>` +81 | uri!(other_q: rest = S, id = _); + | ^ the trait `rocket::http::uri::Ignorable<rocket::http::uri::Query>` is not implemented for `usize` | - = 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` + = note: required by `rocket::http::uri::assert_ignorable` + +error[E0277]: the trait bound `S: rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not satisfied + --> $DIR/typed-uri-bad-type.rs:81:26 + | +81 | uri!(other_q: rest = S, id = _); + | ^ the trait `rocket::http::uri::FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S` -error: aborting due to 12 previous errors +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 @@ -4,7 +4,7 @@ use std::fmt; -use rocket::http::Cookies; +use rocket::http::{Cookies, RawStr}; #[post("/<id>")] fn has_one(id: i32) { } @@ -15,6 +15,9 @@ fn has_one_guarded(cookies: Cookies, id: i32) { } #[post("/<id>?<name>")] fn has_two(cookies: Cookies, id: i32, name: String) { } +#[post("/<id>/<name>")] +fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { } + 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 @@ -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 + --> $DIR/typed-uris-bad-params.rs:44:19 | -41 | 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:41:29 + --> $DIR/typed-uris-bad-params.rs:44:29 | -41 | 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:44:19 + --> $DIR/typed-uris-bad-params.rs:47:19 | -44 | uri!(has_one: id = 100, id = 100, ); //~ 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:44:29 + --> $DIR/typed-uris-bad-params.rs:47:29 | -44 | uri!(has_one: id = 100, id = 100, ); //~ ERROR invalid parameters +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: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 | ^^^^^^^^^^^ | = 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 + --> $DIR/typed-uris-bad-params.rs:54:27 | -51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ 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:51:27 + --> $DIR/typed-uris-bad-params.rs:54:27 | -51 | uri!(has_one_guarded: cookies = "hi", id = 100); //~ 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:54:27 + --> $DIR/typed-uris-bad-params.rs:57:27 | -54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters +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:54:37 + --> $DIR/typed-uris-bad-params.rs:57:37 | -54 | uri!(has_one_guarded: id = 100, cookies = "hi"); //~ ERROR invalid parameters +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 @@ -153,29 +153,14 @@ pub struct Formatter<'i, P: UriPart> { 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, 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<Cow<'static, str>> = self.query.map(|query| match query { - Static(query) => query.into(), + let query: Option<Cow<'_, str>> = 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 @@ -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<P: UriPart> FromUriParam<P, T> for T` +/// * `impl<'x, P: UriPart> FromUriParam<P, &'x T> for T` +/// * `impl<'x, P: UriPart> FromUriParam<P, &'x mut T> 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`, `<T as FromUriParam<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<str>` +/// +/// 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<P: UriPart, T> { /// The resulting type of this conversion. type Target: UriDisplay<P>; @@ -155,57 +197,121 @@ pub trait FromUriParam<P: UriPart, T> { fn from_uri_param(param: T) -> Self::Target; } -impl<P: UriPart, T: UriDisplay<P>> FromUriParam<P, T> for T { - type Target = T; - #[inline(always)] - fn from_uri_param(param: T) -> T { param } -} +use std::{borrow::Cow, net::{IpAddr, Ipv4Addr, Ipv6Addr}}; -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 } -} +#[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),*); ); -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 } + (@_ $(($($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 } + } + ); } -/// A no cost conversion allowing an `&str` to be used in place of a `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 } +/// Macro to automatically generated _identity_ [`FromUriParam`] trait +/// implementations. +/// +/// For a type `T`, the _identity_ implementations of `FromUriParam` are: +/// +/// * `impl UriPart> FromUriParam<P, T> for T` +/// * `impl<'x> FromUriParam<P, &'x T> for T` +/// * `impl<'x> FromUriParam<P, &'x mut T> 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<P: UriPart> FromUriParam<P, _> 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<P, _> 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<Path, _> 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<Path, _> 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); )*); } -/// A no cost conversion allowing an `&str` to be used in place of an `&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 } +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 a `String` to be used in place of an `&RawStr`. -impl<'a, P: UriPart> FromUriParam<P, String> for &'a RawStr { - type Target = String; - #[inline(always)] - fn from_uri_param(param: String) -> String { param } +impl_from_uri_param_identity! { + ('a) &'a str, + ('a) &'a RawStr, + ('a) Cow<'a, str> } -/// A no cost conversion allowing a `String` to be used in place of an `&str`. -impl<'a, P: UriPart> FromUriParam<P, String> for &'a str { - type Target = String; - #[inline(always)] - fn from_uri_param(param: String) -> String { 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 an `&Path` to be used in place of a `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 } +impl_from_uri_param_identity!([uri::Path] ('a) &'a Path); +impl_from_uri_param_identity!([uri::Path] PathBuf); + +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<uri::Path, &'a str> for PathBuf { } } -/// 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> { +/// A no cost conversion allowing an `&&str` to be used in place of a `PathBuf`. +impl<'a, 'b> FromUriParam<uri::Path, &'a &'b str> 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<T>`. +impl<P: UriPart, A, T: FromUriParam<P, A>> FromUriParam<P, A> for Option<T> { type Target = T::Target; #[inline(always)] @@ -229,9 +344,8 @@ impl<A, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Option<T> } } -/// 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> { +/// A no cost conversion allowing `T` to be used in place of an `Result<T, E>`. +impl<P: UriPart, A, E, T: FromUriParam<P, A>> FromUriParam<P, A> for Result<T, E> { type Target = T::Target; #[inline(always)] diff --git 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 @@ -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<Path>` vs. `UriDisplay<Query>` +/// # Marker Generic: `Path`, `Query` /// /// The [`UriPart`] parameter `P` in `UriDisplay<P>` 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<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. +/// 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/<id>?<track>")] -/// fn get_item(id: i32, track: String) { /* .. */ } +/// fn get_item(id: i32, track: Option<String>) { /* .. */ } /// ``` /// /// 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/<id>?<track>")] -/// # fn get_item(id: i32, track: String) { /* .. */ } +/// # fn get_item(id: i32, track: Option<String>) { /* .. */ } /// # /// // 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<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. +/// and `&str` must implement `UriDisplay<Query>`. What's more, when `track` is +/// ignored, `Option<String>` 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<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. +/// // 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)) /// } /// } /// +/// impl_from_uri_param_identity!([Path] Name); +/// /// #[get("/name/<name>")] /// fn redirector(name: Name) -> Redirect { /// Redirect::to(uri!(real: name)) @@ -281,25 +294,14 @@ pub trait UriDisplay<P: UriPart> { 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<Query> { +impl<'a, P: UriPart> fmt::Display for &'a UriDisplay<P> { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - UriDisplay::fmt(*self, &mut <Formatter<Query>>::new(f)) + UriDisplay::fmt(*self, &mut <Formatter<P>>::new(f)) } } -/// Percent-encodes the raw string. -impl<P: UriPart> UriDisplay<P> for RawStr { - #[inline(always)] - fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { - f.write_raw(&Uri::percent_encode(self.as_str())) - } -} +// Direct implementations: these are the leaves of a call to `UriDisplay::fmt`. /// Percent-encodes the raw string. impl<P: UriPart> UriDisplay<P> for str { @@ -309,37 +311,19 @@ impl<P: UriPart> UriDisplay<P> for str { } } -/// Percent-encodes the raw string. -impl<'a, P: UriPart> UriDisplay<P> for Cow<'a, str> { - #[inline(always)] - fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { - f.write_raw(&Uri::percent_encode(self)) - } -} - -/// Percent-encodes the raw string. -impl<P: UriPart> UriDisplay<P> for String { - #[inline(always)] - 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<Path> for path::PathBuf { - #[inline] - 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<Path> for path::Path { - #[inline] 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) + 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<P> + ?Sized> UriDisplay<P> for $T { - #[inline(always)] - fn fmt(&self, f: &mut Formatter<P>) -> 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<P: UriPart> UriDisplay<P> for RawStr { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { + self.as_str().fmt(f) + } } -impl_for_ref!(&'a mut T, &'a T); +/// Percent-encodes the raw string. Defers to `str`. +impl<P: UriPart> UriDisplay<P> for String { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { + self.as_str().fmt(f) + } +} +/// Percent-encodes the raw string. Defers to `str`. +impl<'a, P: UriPart> UriDisplay<P> for Cow<'a, str> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { + self.as_ref().fmt(f) + } +} + +/// Percent-encodes each segment in the path and normalizes separators. +impl UriDisplay<Path> for path::PathBuf { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result { + self.as_path().fmt(f) + } +} + +/// Defers to the `UriDisplay<P>` implementation for `T`. +impl<'a, P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &'a T { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { + UriDisplay::fmt(*self, f) + } +} + +/// Defers to the `UriDisplay<P>` implementation for `T`. +impl<'a, P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &'a mut T { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { + UriDisplay::fmt(*self, f) + } +} + +/// Defers to the `UriDisplay<Query>` implementation for `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)?; + match self { + Some(v) => v.fmt(f), + None => Ok(()) } - - Ok(()) } } -impl<E, T: UriDisplay<Query>> UriDisplay<Query> for Result<T, E> { +/// Defers to the `UriDisplay<Query>` implementation for `T`. +impl<T: UriDisplay<Query>, E> 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)?; + 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/<id>?<track>")] +/// fn get_item(id: i32, track: Option<u8>) { /* .. */ } +/// +/// // Ignore the `track` parameter: `Option<u8>` 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<T>` and `Result<T, E>` 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<Query> for MyType { } +/// ``` +pub trait Ignorable<P: UriPart> { } + +impl<T> Ignorable<Query> for Option<T> { } +impl<T, E> Ignorable<Query> for Result<T, E> { } + +#[doc(hidden)] +pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { } + +#[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!(<Query, String> "hello", "hello"); + assert_display!(<Query, String> "hi hi", "hi%20hi"); + assert_display!(<Query, &str> "hi hi", "hi%20hi"); + assert_display!(<Query, &str> &"hi hi", "hi%20hi"); + assert_display!(<Query, usize> 10, "10"); + assert_display!(<Query, u8> 10, "10"); + assert_display!(<Query, i32> 10, "10"); + assert_display!(<Query, isize> 10, "10"); + + assert_display!(<Path, String> "hello", "hello"); + assert_display!(<Path, String> "hi hi", "hi%20hi"); + assert_display!(<Path, &str> "hi hi", "hi%20hi"); + assert_display!(<Path, &str> &"hi hi", "hi%20hi"); + assert_display!(<Path, usize> 10, "10"); + assert_display!(<Path, u8> 10, "10"); + assert_display!(<Path, i32> 10, "10"); + assert_display!(<Path, isize> 10, "10"); + + assert_display!(<Query, &str> &"hi there", "hi%20there"); + assert_display!(<Query, isize> &10, "10"); + assert_display!(<Query, u8> &10, "10"); + + assert_display!(<Path, &str> &"hi there", "hi%20there"); + assert_display!(<Path, isize> &10, "10"); + assert_display!(<Path, u8> &10, "10"); + + assert_display!(<Path, Option<&str>> &"hi there", "hi%20there"); + assert_display!(<Path, Option<isize>> &10, "10"); + assert_display!(<Path, Option<u8>> &10, "10"); + assert_display!(<Query, Option<&str>> &"hi there", "hi%20there"); + assert_display!(<Query, Option<isize>> &10, "10"); + assert_display!(<Query, Option<u8>> &10, "10"); + + assert_display!(<Path, Result<&str, usize>> &"hi there", "hi%20there"); + assert_display!(<Path, Result<isize, &str>> &10, "10"); + assert_display!(<Path, Result<u8, String>> &10, "10"); + assert_display!(<Query, Result<&str, usize>> &"hi there", "hi%20there"); + assert_display!(<Query, Result<isize, &str>> &10, "10"); + assert_display!(<Query, Result<u8, String>> &10, "10"); + } + + #[test] + fn paths() { + assert_display!(<Path, path::PathBuf> "hello", "hello"); + assert_display!(<Path, path::PathBuf> "hi there", "hi%20there"); + assert_display!(<Path, path::PathBuf> "hello/world", "hello/world"); + assert_display!(<Path, path::PathBuf> "hello//world", "hello/world"); + assert_display!(<Path, path::PathBuf> "hello/ world", "hello/%20world"); + + assert_display!(<Path, path::PathBuf> "hi/wo rld", "hi/wo%20rld"); + + assert_display!(<Path, path::PathBuf> &"hi/wo rld", "hi/wo%20rld"); + assert_display!(<Path, path::PathBuf> &"hi there", "hi%20there"); + } + + struct Wrapper<T>(T); + + impl<A, T: FromUriParam<Query, A>> FromUriParam<Query, A> for Wrapper<T> { + type Target = T::Target; + + #[inline(always)] + fn from_uri_param(param: A) -> Self::Target { + T::from_uri_param(param) + } + } + + impl FromUriParam<Path, usize> for Wrapper<usize> { + type Target = usize; + + #[inline(always)] + fn from_uri_param(param: usize) -> Self::Target { + param + } + } + + #[test] + fn uri_display_encoding_wrapped() { + assert_display!(<Query, Option<Wrapper<&str>>> &"hi there", "hi%20there"); + assert_display!(<Query, Option<Wrapper<&str>>> "hi there", "hi%20there"); + + assert_display!(<Query, Option<Wrapper<isize>>> 10, "10"); + assert_display!(<Query, Option<Wrapper<usize>>> 18, "18"); + assert_display!(<Path, Option<Wrapper<usize>>> 238, "238"); + + assert_display!(<Path, Result<Option<Wrapper<usize>>, usize>> 238, "238"); + assert_display!(<Path, Option<Result<Wrapper<usize>, usize>>> 123, "123"); + } + + #[test] + fn check_ignorables() { + use uri::assert_ignorable; + + assert_ignorable::<Query, Option<usize>>(); + assert_ignorable::<Query, Option<Wrapper<usize>>>(); + assert_ignorable::<Query, Result<Wrapper<usize>, usize>>(); + assert_ignorable::<Query, Option<Result<Wrapper<usize>, usize>>>(); + assert_ignorable::<Query, Result<Option<Wrapper<usize>>, usize>>(); } }