Skip to content

Commit

Permalink
Show the errored path on JsonDataError
Browse files Browse the repository at this point in the history
Previously, it was difficult to find out the path in the deep JSON tree at
which a deserialization error occurred.  This patch makes an error message
to contain the errored path.  In order to find out the path,
I added serde_path_to_error, a new optional dependency.

Co-authored-by: Lee Dogeon <dev.moreal@gmail.com>
  • Loading branch information
dahlia and moreal committed Sep 13, 2022
1 parent 54d8439 commit 4d2efa1
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 4 deletions.
6 changes: 5 additions & 1 deletion axum/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

- None.
- **changed**: The inner error of a `JsonRejection` is now
`serde_path_to_error::Error<serde_json::Error>`. Previously it was
`serde_json::Error`. ([#1371])
- **changed**: `JsonRejection` now displays the path at which a deserialization
error occurred too. ([#1371])

# 0.6.0-rc.2 (10. September, 2022)

Expand Down
3 changes: 2 additions & 1 deletion axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default = ["form", "http1", "json", "matched-path", "original-uri", "query", "to
form = ["dep:serde_urlencoded"]
http1 = ["hyper/http1"]
http2 = ["hyper/http2"]
json = ["dep:serde_json"]
json = ["dep:serde_json", "dep:serde_path_to_error"]
macros = ["dep:axum-macros"]
matched-path = []
multipart = ["dep:multer"]
Expand Down Expand Up @@ -57,6 +57,7 @@ base64 = { version = "0.13", optional = true }
headers = { version = "0.3.7", optional = true }
multer = { version = "2.0.0", optional = true }
serde_json = { version = "1.0", features = ["raw_value"], optional = true }
serde_path_to_error = { version = "0.1.8", optional = true }
serde_urlencoded = { version = "0.7", optional = true }
sha-1 = { version = "0.10", optional = true }
tokio-tungstenite = { version = "0.17.2", optional = true }
Expand Down
41 changes: 39 additions & 2 deletions axum/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,12 @@ where
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
if json_content_type(req.headers()) {
let bytes = Bytes::from_request(req, state).await?;
let deserializer = &mut serde_json::Deserializer::from_slice(&bytes);

let value = match serde_json::from_slice(&bytes) {
let value = match serde_path_to_error::deserialize(deserializer) {
Ok(value) => value,
Err(err) => {
let rejection = match err.classify() {
let rejection = match err.inner().classify() {
serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
JsonSyntaxError::from_err(err).into()
Expand Down Expand Up @@ -296,4 +297,40 @@ mod tests {

assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}

#[derive(Deserialize)]
struct Foo {
#[allow(dead_code)]
a: i32,
#[allow(dead_code)]
b: Vec<Bar>,
}

#[derive(Deserialize)]
struct Bar {
#[allow(dead_code)]
x: i32,
#[allow(dead_code)]
y: i32,
}

#[tokio::test]
async fn invalid_json_data() {
let app = Router::new().route("/", post(|_: Json<Foo>| async {}));

let client = TestClient::new(app);
let res = client
.post("/")
.body("{\"a\": 1, \"b\": [{\"x\": 2}]}")
.header("content-type", "application/json")
.send()
.await;

assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
let body_text = res.text().await;
assert_eq!(
body_text,
"Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23"
);
}
}

0 comments on commit 4d2efa1

Please sign in to comment.