[Actix] How to reveal the JSON string when it fails to be extracted as Json?

Actix extractors make it simple to get the target data types to directly handle in handlers, like

#[post("/records")]
async fn post_records(
    service_data: Data<X>,
    changes: Json<Changes>,
) -> (String, StatusCode);

But when client posts wrong Json format, by default, all I got was an unable to parse error. I want to get the JSON for debugging.

Seeing in its document, there is a JsonConfig I can play a little. But following code actually could not compile since the handler is not async.

                .app_data(JsonConfig::default().error_handler(|err, req| {
                    let bytes = Bytes::extract(req).await.unwrap();
                    let str = String::from_utf8(bytes.to_vec()).unwrap();
                    log::warn!("{:?}", str);
                    err.into()
                }))

So what is the correct way? If I do not want to just get the raw body in handler and deserialize by myself?

Are we talking about one-off debugging during development or debugging in the sense that you want to log the body of faulty requests in production? If you want to accept a random JSON value in the body, you can use Json<serde_json::Value> as your extractor.

Hi, more like the first case, but not "one-off". The client to post is not under my control, it would happen that it uses another json format again.

So... more like the second case then? I.e. you want to use this debugging behaviour in production, but still would like to extract Json<Changes> in your endpoint, no? The client might not send a Changes object, in which case you want to see the body that the client has sent?

Sorry, I did not make it clear.

In most of the time, things go smoothly. Using Json<Change> is alright. But it may happen like once a year if the client got updated and changed the behavior. I have to follow that.

So instead of when it happens, "let's find out the client and see what it sends". I'd like the failed requests to be recorded with enough information so I can update my side ASAP.

I am not planning to take random inputs. In that case, I surely would just use the raw input in request handler.

You can set a custom error handler and inspect the error accordingly.

I was trying that. But the two parameters of error_handler, err does not (seem to me) contain the JSON string itself. And I need some async functions to get the request body from req, which does not work in error_handler.

As far as I recall, this design was intentional. By the time the request is in JsonConfg's error handler, the body has already been consumed.

1 Like

Using the Json extractor over Bytes still has benefits like header rejection.


I see two ways to implement this.

  1. Extract body: Json<serde_json::Value> and parse it with serde_json::from_value::<Changes>(body) in your endpoint, allowing you to handle parsing errors (but not general request errors, like wrong content-type) however you like. This would be the easier solution.

  2. A bit more elegant, or rather less visible in your endpoint code, would be to handle this in a middleware. Extracting the body from a request or a response in a middleware is not very convenient though. You'd have to tap into the Payload stream, which is comparatively low-level and you'd have to write some code yourself, which actix-web cleverly hides behind the extractor facade.

1 Like