Allow displaying [T; N], Vec<T>, [u8] via 'uri!'.

Resolves #2750.
This commit is contained in:
Sergio Benitez 2024-03-14 15:24:34 -07:00
parent 7c702a5b01
commit 50c44e8fdc
3 changed files with 77 additions and 8 deletions

View File

@ -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("/?<v>")]
fn f(v: Vec<usize>) { }
#[post("/?<v>")]
fn g(v: Vec<String>) { }
#[post("/?<v>")]
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",
}
}

View File

@ -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<P: Part> FromUriParam<P, T> for T`
/// * `impl<'x, P: Part> FromUriParam<P, &'x T> for T`
/// * `impl<'x, P: Part> FromUriParam<P, &'x mut T> 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<T>.
impl_from_uri_param_identity!([fmt::Query] ('a) &'a [u8]);
impl_conversion_ref! {
[fmt::Query] (T, A: FromUriParam<fmt::Query, T> + UriDisplay<fmt::Query>) Vec<A> => Vec<T>,
[fmt::Query] (
T,
A: FromUriParam<fmt::Query, T> + UriDisplay<fmt::Query>,
const N: usize
) Vec<A> => [T; N],
[fmt::Query] (
T,
A: FromUriParam<fmt::Query, T> + UriDisplay<fmt::Query>,
const N: usize
) [A; N] => Vec<T>,
[fmt::Query] (
T,
A: FromUriParam<fmt::Query, T> + UriDisplay<fmt::Query>,
const N: usize
) [A; N] => [T; N],
}
/// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`.

View File

@ -453,11 +453,19 @@ impl<T: UriDisplay<Query>, E> UriDisplay<Query> for Result<T, E> {
impl<T: UriDisplay<Query>> UriDisplay<Query> for Vec<T> {
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<T: UriDisplay<Query>, const N: usize> UriDisplay<Query> for [T; N] {
fn fmt(&self, f: &mut Formatter<'_, Query>) -> fmt::Result {
self.iter().try_for_each(|v| f.write_value(v))
}
}
impl UriDisplay<Query> for [u8] {
fn fmt(&self, f: &mut Formatter<'_, Query>) -> fmt::Result {
f.write_raw(RawStr::percent_encode_bytes(self).as_str())
}
}