Implement ignorable 'uri!' expressions.

Closes #840.
This commit is contained in:
Sergio Benitez 2018-12-05 04:20:22 -08:00
parent 50a635ed8e
commit d7933dd6fd
13 changed files with 923 additions and 374 deletions

View File

@ -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;
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<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);

View File

@ -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<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)),
}
}
}

View File

@ -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 {

View File

@ -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))

View File

@ -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",
}
}

View File

@ -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>
// For queries, we need to know the exact variant.
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>
// 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 = _);
}

View File

@ -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: aborting due to 12 previous errors
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 13 previous errors
For more information about this error, try `rustc --explain E0277`.

View File

@ -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
}

View File

@ -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

View File

@ -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())
}
});

View File

@ -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}};
#[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<P>> FromUriParam<P, &'a T> 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<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); )*);
}
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 }
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<P, &'a str> 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<P, &'a str> 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<P, String> 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<P, String> for &'a str {
type Target = String;
#[inline(always)]
fn from_uri_param(param: String) -> String { param }
}
/// A no cost conversion allowing an `&Path` to be used in place of a `PathBuf`.
impl<'a> FromUriParam<uri::Path, &'a Path> 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<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)]

View File

@ -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 = '&';
}

View File

@ -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,26 +294,15 @@ 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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
UriDisplay::fmt(*self, &mut <Formatter<Query>>::new(f))
}
}
/// Percent-encodes the raw string.
impl<P: UriPart> UriDisplay<P> for RawStr {
impl<'a, P: UriPart> fmt::Display for &'a UriDisplay<P> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
f.write_raw(&Uri::percent_encode(self.as_str()))
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
UriDisplay::fmt(*self, &mut <Formatter<P>>::new(f))
}
}
// Direct implementations: these are the leaves of a call to `UriDisplay::fmt`.
/// Percent-encodes the raw string.
impl<P: UriPart> UriDisplay<P> for str {
#[inline(always)]
@ -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>>();
}
}