diff --git a/.gitignore b/.gitignore index 8eb581d..b8705c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /Cargo.lock /.idea +/lcov.info diff --git a/Cargo.toml b/Cargo.toml index 8d2b92a..72a9b52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,3 +44,4 @@ json = ["axum/json"] form = ["axum/form"] query = ["axum/query"] into_json = ["serde_json"] +422 = [] diff --git a/README.md b/README.md index b714164..719eaf9 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,11 @@ pub async fn get_page_by_json( } ``` +When validation errors occur, the extractor will automatically return 400 with validation errors as the HTTP message body. + For more usage examples, please refer to the `basic.rs` and `custom.rs` files in the `tests` directory. ## Features +`422`: Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails. `into_json`: When this feature is enabled, validation errors will be serialized into JSON format and returned as the HTTP body. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1fab405..f273af8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,13 @@ use axum::http::{Request, StatusCode}; use axum::response::{IntoResponse, Response}; use validator::{Validate, ValidationErrors}; +/// Http status code returned when there are validation errors. +#[cfg(feature = "422")] +pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::UNPROCESSABLE_ENTITY; +/// Http status code returned when there are validation errors. +#[cfg(not(feature = "422"))] +pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::BAD_REQUEST; + /// Valid entity extractor #[derive(Debug, Clone, Copy, Default)] pub struct Valid(pub T); @@ -41,14 +48,14 @@ impl IntoResponse for ValidRejection { ValidRejection::Valid(validate_error) => { #[cfg(feature = "into_json")] match serde_json::to_string(&validate_error) { - Ok(json) => (StatusCode::BAD_REQUEST, json), + Ok(json) => (VALIDATION_ERROR_STATUS, json), Err(error) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to serialize validation error into JSON ({validate_error}): {error}"), ), } #[cfg(not(feature = "into_json"))] - (StatusCode::BAD_REQUEST, validate_error.to_string()) + (VALIDATION_ERROR_STATUS, validate_error.to_string()) }.into_response(), ValidRejection::Inner(json_error) => json_error.into_response(), } diff --git a/tarpaulin.toml b/tarpaulin.toml index f7638a1..791c41f 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,4 +1,10 @@ [feature_default] [feature_into_json] -features = "into_json" \ No newline at end of file +features = "into_json" + +[feature_422] +features = "422" + +[feature_422_into_json] +features = "422 into_json" \ No newline at end of file diff --git a/tests/basic.rs b/tests/basic.rs index c5f1855..b967b53 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -9,7 +9,7 @@ use axum::extract::{Path, Query}; use axum::http::StatusCode; use axum::routing::{get, post}; use axum::{Form, Json, Router}; -use axum_valid::Valid; +use axum_valid::{Valid, VALIDATION_ERROR_STATUS}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::net::SocketAddr; @@ -76,7 +76,7 @@ async fn main() -> anyhow::Result<()> { )) .send() .await?; - assert_eq!(invalid_path_response.status(), StatusCode::BAD_REQUEST); + assert_eq!(invalid_path_response.status(), VALIDATION_ERROR_STATUS); #[cfg(feature = "into_json")] assert!(invalid_path_response .json::() @@ -105,7 +105,7 @@ async fn main() -> anyhow::Result<()> { .query(&invalid_parameters) .send() .await?; - assert_eq!(invalid_query_response.status(), StatusCode::BAD_REQUEST); + assert_eq!(invalid_query_response.status(), VALIDATION_ERROR_STATUS); #[cfg(feature = "into_json")] assert!(invalid_query_response .json::() @@ -137,7 +137,7 @@ async fn main() -> anyhow::Result<()> { .form(&invalid_parameters) .send() .await?; - assert_eq!(invalid_form_response.status(), StatusCode::BAD_REQUEST); + assert_eq!(invalid_form_response.status(), VALIDATION_ERROR_STATUS); #[cfg(feature = "into_json")] assert!(invalid_form_response .json::() @@ -169,7 +169,7 @@ async fn main() -> anyhow::Result<()> { .json(&invalid_parameters) .send() .await?; - assert_eq!(invalid_json_response.status(), StatusCode::BAD_REQUEST); + assert_eq!(invalid_json_response.status(), VALIDATION_ERROR_STATUS); #[cfg(feature = "into_json")] assert!(invalid_json_response .json::() diff --git a/tests/custom.rs b/tests/custom.rs index 3f46daf..bd6ecdd 100644 --- a/tests/custom.rs +++ b/tests/custom.rs @@ -6,7 +6,7 @@ use axum::http::request::Parts; use axum::response::{IntoResponse, Response}; use axum::routing::get; use axum::Router; -use axum_valid::{HasValidate, Valid, ValidRejection}; +use axum_valid::{HasValidate, Valid, ValidRejection, VALIDATION_ERROR_STATUS}; use hyper::StatusCode; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; @@ -122,7 +122,7 @@ async fn main() -> anyhow::Result<()> { .header(MY_DATA_HEADER, serde_json::to_string(&invalid_my_data)?) .send() .await?; - assert_eq!(invalid_my_data_response.status(), StatusCode::BAD_REQUEST); + assert_eq!(invalid_my_data_response.status(), VALIDATION_ERROR_STATUS); #[cfg(feature = "into_json")] assert!(invalid_my_data_response .json::()