Make Path extractor work with Deserialize impls using &str (#990)

* `Path` extractor works with `Deserialize` impls using `&str`

Before this change the extractor `Path<Test>` would fail if the
`Deserialize` implementation of `Test` was calling
`Deserializer::deserialize_str()`.

Now we use `Visitor::visit_borrowed_str()` instead of
`Visitor::visit_str()` which is also recommended in the guide to
implement a deserializer [1].

[1]: https://serde.rs/impl-deserializer.html

* fixup! `Path` extractor works with `Deserialize` impls using `&str`

* add test for percent decoding

Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
This commit is contained in:
Thomas Scholtes 2022-05-03 20:44:58 +02:00 committed by GitHub
parent d1043db254
commit 8cc052f38b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 1 deletions

View file

@ -88,7 +88,7 @@ impl<'de> Deserializer<'de> for PathDeserializer<'de> {
.got(self.url_params.len())
.expected(1));
}
visitor.visit_str(&self.url_params[0].1)
visitor.visit_borrowed_str(&self.url_params[0].1)
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
@ -608,6 +608,8 @@ mod tests {
check_single_value!(f64, "123", 123.0);
check_single_value!(String, "abc", "abc");
check_single_value!(String, "one%20two", "one two");
check_single_value!(&str, "abc", "abc");
check_single_value!(&str, "one%20two", "one two");
check_single_value!(char, "a", 'a');
let url_params = create_url_params(vec![("a", "B")]);

View file

@ -514,4 +514,29 @@ mod tests {
"No paths parameters found for matched route. Are you also extracting `Request<_>`?"
);
}
#[tokio::test]
async fn str_reference_deserialize() {
struct Param(String);
impl<'de> serde::Deserialize<'de> for Param {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&str as serde::Deserialize>::deserialize(deserializer)?;
Ok(Param(s.to_owned()))
}
}
let app = Router::new().route("/:key", get(|param: Path<Param>| async move { param.0 .0 }));
let client = TestClient::new(app);
let res = client.get("/foo").send().await;
assert_eq!(res.text().await, "foo");
// percent decoding should also work
let res = client.get("/foo%20bar").send().await;
assert_eq!(res.text().await, "foo bar");
}
}