diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index e413eccb..aabe94e5 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -673,3 +673,39 @@ fn test_route_uri_normalization_with_prefix() { uri!("/foo/bar/", world()) => "/foo/bar/world", } } + +#[test] +fn test_vec_in_query() { + #[post("/?")] + fn f(v: Vec) { } + + #[post("/?")] + fn g(v: Vec) { } + + #[post("/?")] + fn h(v: &[u8]) { } + + let bytes = vec![0u8, 1, 2]; + assert_uri_eq! { + uri!(f(v = vec![1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f(v = &vec![1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f(v = &[1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f(v = [1, 2, 3])) => "/?v=1&v=2&v=3", + + uri!(f(vec![1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f(&vec![1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f(&[1, 2, 3])) => "/?v=1&v=2&v=3", + uri!(f([1, 2, 3])) => "/?v=1&v=2&v=3", + + // TODO: Introduce `RawBytes` + FromUriParam + UriDisplay impls. + // uri!(f(v = &[1, 2, 3][..])) => "/?v=1&v=2&v=3", + + uri!(g(v = vec!["a", "b=c", "d"])) => "/?v=a&v=b%3Dc&v=d", + uri!(g(v = &vec!["a", "b=c", "d"])) => "/?v=a&v=b%3Dc&v=d", + uri!(g(v = ["a", "b=c", "d"])) => "/?v=a&v=b%3Dc&v=d", + uri!(g(v = &["a", "b=c", "d"])) => "/?v=a&v=b%3Dc&v=d", + + uri!(h(v = bytes.as_slice())) => "/?v=%00%01%02", + uri!(h(v = &[1, 2, 3][..])) => "/?v=%01%02%03", + } +} diff --git a/core/http/src/uri/fmt/from_uri_param.rs b/core/http/src/uri/fmt/from_uri_param.rs index ae162e9b..f1c5fc01 100644 --- a/core/http/src/uri/fmt/from_uri_param.rs +++ b/core/http/src/uri/fmt/from_uri_param.rs @@ -16,15 +16,16 @@ use crate::uri::fmt::{self, Part}; /// /// 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 [`impl_from_uri_param_identity`] to generate +/// be automated. Rocket provides [`impl_from_uri_param_identity!`] to generate /// the _identity_ implementations automatically. For a type `T`, these are: /// /// * `impl FromUriParam for T` /// * `impl<'x, P: Part> FromUriParam for T` /// * `impl<'x, P: Part> FromUriParam for T` /// -/// See [`impl_from_uri_param_identity!`](crate::impl_from_uri_param_identity!) -/// for usage details. +/// See [`impl_from_uri_param_identity!`] for usage details. +/// +/// [`impl_from_uri_param_identity!`]: crate::impl_from_uri_param_identity! /// /// # Code Generation /// @@ -316,7 +317,31 @@ impl_from_uri_param_identity!([fmt::Path] PathBuf); impl_conversion_ref! { [fmt::Path] ('a) &'a Path => PathBuf, - [fmt::Path] ('a) PathBuf => &'a Path + [fmt::Path] ('a) PathBuf => &'a Path, +} + +// TODO: A specialized `RawBytes` instead of `&[u8]`. Then impl [T] => Vec. +impl_from_uri_param_identity!([fmt::Query] ('a) &'a [u8]); + +impl_conversion_ref! { + [fmt::Query] (T, A: FromUriParam + UriDisplay) Vec => Vec, + [fmt::Query] ( + T, + A: FromUriParam + UriDisplay, + const N: usize + ) Vec => [T; N], + + [fmt::Query] ( + T, + A: FromUriParam + UriDisplay, + const N: usize + ) [A; N] => Vec, + + [fmt::Query] ( + T, + A: FromUriParam + UriDisplay, + const N: usize + ) [A; N] => [T; N], } /// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`. diff --git a/core/http/src/uri/fmt/uri_display.rs b/core/http/src/uri/fmt/uri_display.rs index b8e630e0..93866fde 100644 --- a/core/http/src/uri/fmt/uri_display.rs +++ b/core/http/src/uri/fmt/uri_display.rs @@ -453,11 +453,19 @@ impl, E> UriDisplay for Result { impl> UriDisplay for Vec { fn fmt(&self, f: &mut Formatter<'_, Query>) -> fmt::Result { - for value in self { - f.write_value(value)?; - } + self.iter().try_for_each(|v| f.write_value(v)) + } +} - Ok(()) +impl, const N: usize> UriDisplay for [T; N] { + fn fmt(&self, f: &mut Formatter<'_, Query>) -> fmt::Result { + self.iter().try_for_each(|v| f.write_value(v)) + } +} + +impl UriDisplay for [u8] { + fn fmt(&self, f: &mut Formatter<'_, Query>) -> fmt::Result { + f.write_raw(RawStr::percent_encode_bytes(self).as_str()) } }