-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Rocket Version: 0.5.0-rc.3
Description
FromData implementation for Option<T> causes malformed bodies to be silently ignored.
To Reproduce
Sometimes a route handler needs to support an optional request body (for example, when making a backwards-compatible addition to an endpoint that previously required no request body).
So the definition of such a route looks like so:
#[post("/my_route", data = "<body>")]
pub async fn my_route_handler(body: Option<Json<Foo>>) {
/* ... */
}The desired behavior is that, if no request data is present, we will get None for the body, and if some data is provided, we'll try to parse it. This is mostly how it works except, in the case where the body does not represent a valid Json<Foo> that is, Json::<Foo>::from_data returns a Failure result. Rather than a failure, we get None.
Expected Behavior
If a request body is supplied, it should be validated.
Environment:
- OS Distribution and Kernel: all
- Rocket Version: 0.5.0-rc.3
Additional Context
For prior art, we can look at what serde does. In general, serde follows the model that optional values are validated if supplied, so the following fails:
use serde::Deserialize;
#[derive(Debug, Deserialize)]
enum Foo {
MyType
}
#[derive(Debug, Deserialize)]
struct Test {
#[serde(default)]
field: Option<Foo>
}
fn main() {
serde_json::from_str::<Test>(r#"{"field": "garbage"}"#).unwrap();
}That is, because a value was provided for field, an incorrect value results in an error, not a silent None.
It seems to me that rocket should follow this example because it provides stronger guarantees and more information.
If users want infallible parsing, they could use Result and just ignore any errors.
Proposal for updating FromData impl
Currently, the implementation of FromData for Option<T> clearly swallows all errors:
impl<'r, T: FromData<'r>> FromData<'r> for Option<T> {
type Error = std::convert::Infallible;
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
match T::from_data(req, data).await {
Success(v) => Success(Some(v)),
Failure(..) | Forward(..) => Success(None),
}
}
}I propose this safer implementation of FromData:
/// Unsure how to write this function body cuz I'm not as familiar with rocket internals.
fn is_empty(data: &Data<'_>) -> bool { /* ... */}
impl<'r, T: FromData<'r>> FromData<'r> for Option<T> {
type Error = T::Error;
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
if is_empty(data) {
return None;
}
T::from_data(req, data).await.map(Some)
}
}