diff --git a/src/extra.rs b/src/extra.rs index 6a197c9..def5d59 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -30,6 +30,7 @@ impl HasValidate for WithRejection { #[cfg(test)] mod tests { use crate::tests::{Rejection, ValidTest}; + use crate::Valid; use axum::http::StatusCode; use axum_extra::extract::{Cached, WithRejection}; use reqwest::RequestBuilder; @@ -52,7 +53,6 @@ mod tests { } impl ValidTest for WithRejection { - // just use conflict to test const ERROR_STATUS_CODE: StatusCode = R::STATUS_CODE; fn set_valid_request(builder: RequestBuilder) -> RequestBuilder { @@ -68,4 +68,28 @@ mod tests { T::set_invalid_request(builder) } } + + impl ValidTest for WithRejection, R> { + // just use `418 I'm a teapot` to test + const ERROR_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT; + // If `WithRejection` is the outermost extractor, + // the error code returned will always be the one provided by WithRejection. + const INVALID_STATUS_CODE: StatusCode = StatusCode::IM_A_TEAPOT; + // If `WithRejection` is the outermost extractor, + // the returned body may not be in JSON format. + const JSON_SERIALIZABLE: bool = false; + + fn set_valid_request(builder: RequestBuilder) -> RequestBuilder { + T::set_valid_request(builder) + } + + fn set_error_request(builder: RequestBuilder) -> RequestBuilder { + // invalid requests will cause the Valid extractor to fail. + T::set_invalid_request(builder) + } + + fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder { + T::set_invalid_request(builder) + } + } } diff --git a/src/lib.rs b/src/lib.rs index 5d8c6eb..73fd39d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,8 +155,13 @@ pub mod tests { /// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code. /// pub trait ValidTest { - /// Http status code when inner extractor failed + /// The HTTP status code returned when inner extractor failed. const ERROR_STATUS_CODE: StatusCode; + /// The HTTP status code returned when the outer extractor fails. + /// Use crate::VALIDATION_ERROR_STATUS by default. + const INVALID_STATUS_CODE: StatusCode = crate::VALIDATION_ERROR_STATUS; + /// If the response body can be serialized into JSON format + const JSON_SERIALIZABLE: bool = true; /// Build a valid request, the server should return `200 OK`. fn set_valid_request(builder: RequestBuilder) -> RequestBuilder; /// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE` diff --git a/src/test.rs b/src/test.rs index 85d7a70..48c6594 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,6 @@ +use crate::test::extra::{ParametersRejection, WithRejectionValidRejection}; use crate::tests::{ValidTest, ValidTestParameter}; -use crate::{Valid, VALIDATION_ERROR_STATUS}; +use crate::{HasValidate, Valid, VALIDATION_ERROR_STATUS}; use axum::extract::{Path, Query}; use axum::routing::{get, post}; use axum::{Form, Json, Router}; @@ -47,6 +48,14 @@ impl ValidTestParameter for Parameters { } } +impl HasValidate for Parameters { + type Validate = Parameters; + + fn get_validate(&self) -> &Self::Validate { + self + } +} + #[tokio::test] async fn test_main() -> anyhow::Result<()> { let router = Router::new() @@ -67,6 +76,10 @@ async fn test_main() -> anyhow::Result<()> { .route( extra::route::WITH_REJECTION, post(extra::extract_with_rejection), + ) + .route( + extra::route::WITH_REJECTION_VALID, + post(extra::extract_with_rejection_valid), ); #[cfg(feature = "extra_query")] @@ -182,16 +195,22 @@ async fn test_main() -> anyhow::Result<()> { #[cfg(feature = "extra")] { use axum_extra::extract::{Cached, WithRejection}; - use extra::TestRejection; + use extra::ValidWithRejectionRejection; test_executor .execute::>(Method::POST, extra::route::CACHED) .await?; test_executor - .execute::>( + .execute::>( Method::POST, extra::route::WITH_REJECTION, ) .await?; + test_executor + .execute::, WithRejectionValidRejection>>( + Method::POST, + extra::route::WITH_REJECTION_VALID, + ) + .await?; } #[cfg(feature = "extra_query")] @@ -290,12 +309,15 @@ impl TestExecutor { let invalid_response = T::set_invalid_request(invalid_builder).send().await?; assert_eq!( invalid_response.status(), - VALIDATION_ERROR_STATUS, + T::INVALID_STATUS_CODE, "Invalid '{}' test failed.", type_name ); #[cfg(feature = "into_json")] - check_json(type_name, invalid_response).await; + if T::JSON_SERIALIZABLE { + check_json(type_name, invalid_response).await; + } + println!("All '{}' tests passed.", type_name); Ok(()) @@ -424,7 +446,7 @@ mod typed_header { mod extra { use crate::test::{validate_again, Parameters}; use crate::tests::{Rejection, ValidTest, ValidTestParameter}; - use crate::Valid; + use crate::{Valid, ValidRejection}; use axum::extract::FromRequestParts; use axum::http::request::Parts; use axum::http::StatusCode; @@ -435,6 +457,7 @@ mod extra { pub mod route { pub const CACHED: &str = "/cached"; pub const WITH_REJECTION: &str = "/with_rejection"; + pub const WITH_REJECTION_VALID: &str = "/with_rejection_valid"; } pub const PARAMETERS_HEADER: &str = "parameters-header"; pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN; @@ -503,25 +526,27 @@ mod extra { } } - pub struct TestRejection { - _inner: ParametersRejection, + pub struct ValidWithRejectionRejection { + inner: ParametersRejection, } - impl Rejection for TestRejection { + impl Rejection for ValidWithRejectionRejection { const STATUS_CODE: StatusCode = StatusCode::CONFLICT; } - impl IntoResponse for TestRejection { + impl IntoResponse for ValidWithRejectionRejection { fn into_response(self) -> Response { - Self::STATUS_CODE.into_response() + let mut response = self.inner.into_response(); + *response.status_mut() = Self::STATUS_CODE; + response } } // satisfy the `WithRejection`'s extractor trait bound // R: From + IntoResponse - impl From for TestRejection { - fn from(_inner: ParametersRejection) -> Self { - Self { _inner } + impl From for ValidWithRejectionRejection { + fn from(inner: ParametersRejection) -> Self { + Self { inner } } } @@ -532,7 +557,43 @@ mod extra { } pub async fn extract_with_rejection( - Valid(WithRejection(parameters, _)): Valid>, + Valid(WithRejection(parameters, _)): Valid< + WithRejection, + >, + ) -> StatusCode { + validate_again(parameters) + } + + pub struct WithRejectionValidRejection { + inner: ValidRejection, + } + + impl From> for WithRejectionValidRejection { + fn from(inner: ValidRejection) -> Self { + Self { inner } + } + } + + impl IntoResponse for WithRejectionValidRejection { + fn into_response(self) -> Response { + match self.inner { + ValidRejection::Valid(v) => { + (StatusCode::IM_A_TEAPOT, v.to_string()).into_response() + } + ValidRejection::Inner(i) => { + let mut res = i.into_response(); + *res.status_mut() = StatusCode::IM_A_TEAPOT; + res + } + } + } + } + + pub async fn extract_with_rejection_valid( + WithRejection(Valid(parameters), _): WithRejection< + Valid, + WithRejectionValidRejection, + >, ) -> StatusCode { validate_again(parameters) }