Rocket: return JSON with errors showing missing fields, etc. on 400 (or 422) error ?

Created on 10 Jan 2017  ·  18Comments  ·  Source: SergioBenitez/Rocket

If someone posts JSON that doesn't match the requirements, Rocket prints a generic message, but, it would be AWESOME if it printed out a JSON response describing what fields are missing -- the information is there, it just needs to be exposed.

For example:
I have code like this:

#[derive(Deserialize)]
struct Demo {
    value: Option<bool>,
    some_string: String
}

#[post("/", format = "application/json", data = "<demo>")]
fn pindex(demo: JSON<Demo>) -> &'static str {
    "POST Hello, world!"
}

~/c/rust ❯❯❯ curl -X POST -d '{"bad": "input" }' -H "Content-Type: application/json" localhost:8100/

generates an error log that lists the missing field:

    => Matched: POST / application/json
    => Error: Couldn't parse JSON body: Syntax(MissingField("some_string"), 1, 74)
    => Outcome: Failure
    => Warning: Responding with 400 Bad Request catcher.

But it just outputs a generic HTML message.


            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="utf-8">
                <title>400 Bad Request</title>
            </head>
            <body align="center">
                <div align="center">
                    <h1>400: Bad Request</h1>
                    <p>The request could not be understood by the server due
                to malformed syntax.</p>
                    <hr />
                    <small>Rocket</small>
                </div>
            </body>
            </html>

1) can we expose the response as JSON, and
2) can we expose the error message saying that something is missing?

Thanks!

question request

Most helpful comment

In 0.4 the error type for rocket_contrib::json::Json is now rocket_contrib::json::JsonError.

All 18 comments

To extend on this, I'd really like to be able to take the error (e.g. Syntax(MissingField("some_string"), 1, 74)) and convert it to custom JSON within my 422 error handler (in addition to improving the default error message as requested above). It may be that there's already a way to do this, but I haven't found a way to access the error object from within the error handler.

Yes, this would be rather useful.

If I could get some hints at where to start looking I wouldn't mind trying to implement this.

From what I gathered, the codegen does not save that fact anywhere

This could be fixed by adding a HashMap or Vector to the BadParse (or add a new error type) and aggregating the errors in there and return the whole if it's non-empty.

@SergioBenitez does that sound about right? (If the feature is to be added, imo it should)

Sorry, I might be missing something here, but doesn't simply changing the type of demo to Result<JSON<Demo>, serde::de::Error> suffice? Rocket should be exposing the SerdeError type to make this easier.

Oops, I managed to pass over the JSON Aspect (I was thinking as output, not input).

My post was about the FromForm trait, which doesn't expose this part

@TheNeikos At present, you can do this on a per-field basis by using a Result or Option as the field type. You can also do this on a full-form basis as well, by using a Result<Form, String>. The String contains the raw form. You can use FormItems to iterate through the form to identify what went wrong. Rocket doesn't figure out exactly what went wrong because it stops trying to parse the form as soon as something goes wrong. It _could_ capture what went wrong, but I don't believe it should try to parse the entire form to determine _all_ of the problems.

Is there a reason to that? Just from an UX perspective that seems like bad taste to have to submit your form 3 times because the password was too short, the Username was too long and the email had an illegal character. Which could all have been resolved at once.

You seem to have misinterpreted what I was trying to get across. Rocket only stops parsing a form when the form submission is incorrect. That is, when a field appears in the request that isn't part of the structure. Otherwise, every field is attempted to be parsed, and you can check if the parse failed by using a Result<T, T::Error> type for that field. If you want to be able to check _every_ field, then every field need to be of that type. That's not usual, however, as you shouldn't use a Result when the request can't be malformed under normal circumstances such as when the input type prevents value of a certain nature.

In your case, you can easily have a struct that looks like:

struct RegisterForm {
    email: Result<EmailAddress, EmailError>,
    password: Result<Password, PasswordError>
}

#[post("/register", data = "<reg_form>")]
fn register(reg_form: Form<RegisterForm>) {
   ...
}

As long as the request contains only the two fields email and password, the handler will be called.

@greglearns I've pushed a change that exposes SerdeError. You can now do:

use rocket_contrib::SerdeError;

#[post("/", format = "application/json", data = "<demo>")]
fn pindex(demo: Result<JSON<Demo>, SerdeError>) -> &'static str {
    "POST Hello, world!"
}

To be clear, you could do this before by using serde::de::Error instead of SerdeError.

Does this resolve the issue for you?

In your case, you can easily have a struct that looks like:

Alright, that seems good! Thanks

@greglearns Following up on this. Does the code I posted above resolve your question?

Closing this; hopefully this is answered! Feel free to reopen if you feel it's not.

@SergioBenitez Sorry that I wasn't able to follow up on this -- got slammed recently. I will be able to come back to this a bit later, and I will keep you posted on any positive progress. Thank you for your support on this topic.

Coming up on this myself, I've noticed no difference in the error output when trying rocket_contrib::SerdeError & demo: Result<JSON<Demo>, SerdeError>.

@SirDoctors The suggestion here is to get the raw parsing errors from Serde from the Err variant of Result<JSON<Demo>, SerdeError> and do with the errors as you wish, including returning them to the user in some way.

Ahh I misunderstood how to use it at first glance.

@SergioBenitez I followed the discussion above, but I'm new to Rocket as of September this year. I tried your suggestion above, but it didn't work for me. I also looked over the source code. Do you recommend a different way to do this? Thanks.

@SergioBenitez It seems rocket_contrib::SerdeError is no longer a thing (couldn't find it in the api docs), and using Result<JSON<Demo>, serde::de::Error> gives multiple build errors, (among other things the trait 'user::_IMPL_DESERIALIZE_FOR_User::_serde::de::Error' cannot be made into an object and that it doesn't have a size known at compile-time)

Has the way to do this in Rocket changed in v0.4? I'm quite new to Rocket and Rust so I'm not sure what the best and most up to date solutions are.
Thanks

In 0.4 the error type for rocket_contrib::json::Json is now rocket_contrib::json::JsonError.

Was this page helpful?
0 / 5 - 0 ratings