mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-26 17:17:22 +01:00
Show the errored path on JsonDataError (#1371)
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> Co-authored-by: Lee Dogeon <dev.moreal@gmail.com>
This commit is contained in:
parent
2abda4de88
commit
7476dd08cb
3 changed files with 48 additions and 4 deletions
|
@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
# Unreleased
|
# 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])
|
||||||
|
- **added**: `JsonRejection` now displays the path at which a deserialization
|
||||||
|
error occurred too ([#1371])
|
||||||
|
|
||||||
|
[#1371]: https://github.com/tokio-rs/axum/pull/1371
|
||||||
|
|
||||||
# 0.6.0-rc.2 (10. September, 2022)
|
# 0.6.0-rc.2 (10. September, 2022)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ default = ["form", "http1", "json", "matched-path", "original-uri", "query", "to
|
||||||
form = ["dep:serde_urlencoded"]
|
form = ["dep:serde_urlencoded"]
|
||||||
http1 = ["hyper/http1"]
|
http1 = ["hyper/http1"]
|
||||||
http2 = ["hyper/http2"]
|
http2 = ["hyper/http2"]
|
||||||
json = ["dep:serde_json"]
|
json = ["dep:serde_json", "dep:serde_path_to_error"]
|
||||||
macros = ["dep:axum-macros"]
|
macros = ["dep:axum-macros"]
|
||||||
matched-path = []
|
matched-path = []
|
||||||
multipart = ["dep:multer"]
|
multipart = ["dep:multer"]
|
||||||
|
@ -57,6 +57,7 @@ base64 = { version = "0.13", optional = true }
|
||||||
headers = { version = "0.3.7", optional = true }
|
headers = { version = "0.3.7", optional = true }
|
||||||
multer = { version = "2.0.0", optional = true }
|
multer = { version = "2.0.0", optional = true }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"], 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 }
|
serde_urlencoded = { version = "0.7", optional = true }
|
||||||
sha-1 = { version = "0.10", optional = true }
|
sha-1 = { version = "0.10", optional = true }
|
||||||
tokio-tungstenite = { version = "0.17.2", optional = true }
|
tokio-tungstenite = { version = "0.17.2", optional = true }
|
||||||
|
|
|
@ -113,11 +113,12 @@ where
|
||||||
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
if json_content_type(req.headers()) {
|
if json_content_type(req.headers()) {
|
||||||
let bytes = Bytes::from_request(req, state).await?;
|
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,
|
Ok(value) => value,
|
||||||
Err(err) => {
|
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::Data => JsonDataError::from_err(err).into(),
|
||||||
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
|
serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
|
||||||
JsonSyntaxError::from_err(err).into()
|
JsonSyntaxError::from_err(err).into()
|
||||||
|
@ -296,4 +297,40 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue