add support for validify

This commit is contained in:
gengteng
2023-10-19 23:12:32 +08:00
parent 61bf3c5b23
commit b2d2c3d7ca
14 changed files with 1139 additions and 354 deletions

View File

@@ -114,6 +114,24 @@ impl<T: validify::Modify> crate::HasModify for Form<T> {
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Form<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Form<T> {
type Validify = T;
type PayloadExtractor = Form<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Form(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};

View File

@@ -114,6 +114,25 @@ impl<T: validify::Modify> crate::HasModify for Query<T> {
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Query<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Query<T> {
type Validify = T;
type PayloadExtractor = Query<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Query(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};

View File

@@ -97,10 +97,10 @@
#[cfg(feature = "garde")]
use crate::Garde;
#[cfg(feature = "validify")]
use crate::{Modified, Validated, ValidifiedByRef};
#[cfg(feature = "validator")]
use crate::{Valid, ValidEx};
#[cfg(feature = "validify")]
use crate::{Validated, Validified};
#[cfg(any(feature = "validator", feature = "garde", feature = "validify"))]
use axum_extra::routing::TypedPath;
#[cfg(any(feature = "validator", feature = "garde", feature = "validify"))]
@@ -127,6 +127,11 @@ impl<T: TypedPath + Display> TypedPath for Validated<T> {
}
#[cfg(feature = "validify")]
impl<T: TypedPath + Display> TypedPath for Validified<T> {
impl<T: TypedPath + Display> TypedPath for Modified<T> {
const PATH: &'static str = T::PATH;
}
#[cfg(feature = "validify")]
impl<T: TypedPath + Display> TypedPath for ValidifiedByRef<T> {
const PATH: &'static str = T::PATH;
}

View File

@@ -114,6 +114,24 @@ impl<T: validify::Modify> crate::HasModify for Form<T> {
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Form<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Form<T> {
type Validify = T;
type PayloadExtractor = Form<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Form(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};

View File

@@ -19,6 +19,12 @@ use std::ops::{Deref, DerefMut};
/// # `Garde` data extractor
///
/// Garde uses garde to validate data, supporting validation with or without arguments.
///
/// If not using arguments, its usage is similar to `Valid`. However, if your axum router uses a state, you need to implement `FromRef<StateType>` for `()`.
///
/// If using arguments, you must pass the arguments to Garde extractor via state, meaning implementing `FromRef<StateType>` for your validation arguments type.
///
#[derive(Debug, Clone, Copy, Default)]
pub struct Garde<E>(pub E);

View File

@@ -56,7 +56,8 @@ pub use crate::garde::{Garde, GardeRejection};
#[cfg(feature = "validify")]
pub use crate::validify::{
HasModify, HasValidify, Modified, PayloadExtractor, Validated, Validified, ValidifyRejection,
HasModify, HasValidify, Modified, PayloadExtractor, Validated, Validified, ValidifiedByRef,
ValidifyRejection,
};
/// `ValidationRejection` is returned when the validation extractor fails.

View File

@@ -121,6 +121,24 @@ impl<T: validify::Modify> crate::HasModify for MsgPack<T> {
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for MsgPack<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for MsgPack<T> {
type Validify = T;
type PayloadExtractor = MsgPack<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
MsgPack(v)
}
}
impl<T> HasValidate for MsgPackRaw<T> {
type Validate = T;
fn get_validate(&self) -> &T {
@@ -144,6 +162,25 @@ impl<T: validify::Modify> crate::HasModify for MsgPackRaw<T> {
&mut self.0
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for MsgPackRaw<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for MsgPackRaw<T> {
type Validify = T;
type PayloadExtractor = MsgPackRaw<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
MsgPackRaw(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};

View File

@@ -133,31 +133,6 @@ impl<T: validify::Modify, R> crate::HasModify for BaseMultipart<T, R> {
}
}
#[cfg(feature = "validify")]
impl<T, R> crate::PayloadExtractor for BaseMultipart<T, R> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.data
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify, R> crate::HasValidify for BaseMultipart<T, R> {
type Validify = T;
type PayloadExtractor = BaseMultipart<T::Payload, R>;
fn from_validified(_v: Self::Validify) -> Self {
// BaseMultipart {
// data,
// rejection: Default::default(), // ❌: need rejection to be pub
// }
// waiting for this issue to be resolved:
// https://github.com/murar8/axum_typed_multipart/issues/55
unimplemented!()
}
}
impl<T> HasValidate for TypedMultipart<T> {
type Validate = T;
fn get_validate(&self) -> &T {

View File

@@ -2,7 +2,7 @@
//!
//! ## Feature
//!
//! Enable the `validify` feature to use `Validated<E>`, `Modified<E>` and `Validified<E>`.
//! Enable the `validify` feature to use `Validated<E>`, `Modified<E>`, `Validified<E>` and `ValidifiedByRef<E>`.
//!
#[cfg(test)]
@@ -20,6 +20,10 @@ use validify::{Modify, Validate, ValidationErrors, Validify};
/// # `Validated` data extractor
///
/// `Validated` provides simple data validation based on `validify`.
///
/// It only does validation, usage is similar to `Valid`.
///
#[derive(Debug, Clone, Copy, Default)]
pub struct Validated<E>(pub E);
@@ -44,7 +48,7 @@ impl<T: Display> Display for Validated<T> {
}
impl<E> Validated<E> {
/// Consumes the `Validified` and returns the validated data within.
/// Consumes the `Validated` and returns the validated data within.
///
/// This returns the `E` type which represents the data that has been
/// successfully validated.
@@ -55,6 +59,20 @@ impl<E> Validated<E> {
/// # `Modified` data extractor / response
///
/// ## Extractor
///
/// `Modified` uses `validify`'s modification capabilities to alter data, without validation.
///
/// Operations like trimming and case modification can be done based on `modify` attributes.
///
/// ## Response
///
/// `Modified` also implements the `IntoResponse` trait. When its inner `IntoResponse` type also implements the `HasModify` trait:
///
/// `Modified` will call `validify`'s modify method to alter the inner data.
/// Then call the inner type's own `into_response` method to convert it into a HTTP response.
///
/// This allows applying modifications during response conversion by leveraging validify.
#[derive(Debug, Clone, Copy, Default)]
pub struct Modified<E>(pub E);
@@ -97,6 +115,12 @@ impl<E: IntoResponse + HasModify> IntoResponse for Modified<E> {
/// # `Validified` data extractor
///
/// `Validified` provides construction, modification and validation abilities based on `validify`.
///
/// It requires a serde-based inner extractor.
///
/// And can treat missing fields as validation errors.
///
#[derive(Debug, Clone, Copy, Default)]
pub struct Validified<E>(pub E);
@@ -121,7 +145,7 @@ impl<T: Display> Display for Validified<T> {
}
impl<E> Validified<E> {
/// Consumes the `ValidifiedMut` and returns the modified and validated data within.
/// Consumes the `Validified` and returns the modified and validated data within.
///
/// This returns the `E` type which represents the data that has been
/// successfully validated.
@@ -130,7 +154,46 @@ impl<E> Validified<E> {
}
}
/// `ValidifyRejection` is returned when the `Validified` / `ValidifiedMut` / `Modified` extractor fails.
/// # `ValidifiedByRef` data extractor
///
/// `ValidifiedByRef` is similar to `Validified`, but operates via reference.
///
/// Suitable for inner extractors not based on `serde`.
///
#[derive(Debug, Clone, Copy, Default)]
pub struct ValidifiedByRef<E>(pub E);
impl<E> Deref for ValidifiedByRef<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<E> DerefMut for ValidifiedByRef<E> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Display> Display for ValidifiedByRef<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<E> ValidifiedByRef<E> {
/// Consumes the `ValidifiedByRef` and returns the modified and validated data within.
///
/// This returns the `E` type which represents the data that has been
/// successfully validated.
pub fn into_inner(self) -> E {
self.0
}
}
/// `ValidifyRejection` is returned when the `Validated` / `Modified` / `Validified` / `ValidifiedByRef` extractor fails.
///
pub type ValidifyRejection<E> = ValidationRejection<ValidationErrors, E>;
@@ -159,16 +222,21 @@ pub trait PayloadExtractor {
fn get_payload(self) -> Self::Payload;
}
/// Trait for types that can supply a reference that can be modified and validated using `validify`.
/// Trait for extractors whose inner data type that can be constructed using some payload,
/// then modified and validated using `validify`.
///
/// Extractor types `T` that implement this trait can be used with `Validified`.
///
pub trait HasValidify: Sized {
/// Inner type that can be modified and validated using `validify`.
type Validify: Validify;
///
/// Extracts payload from the request,
/// which will be used to construct the `Self::Validify` type
/// and perform modification and validation on it.
type PayloadExtractor: PayloadExtractor<Payload = <Self::Validify as Validify>::Payload>;
///
/// Re-packages the validified data back into the inner Extractor type.
fn from_validified(v: Self::Validify) -> Self;
}
@@ -282,3 +350,42 @@ where
)))
}
}
#[async_trait]
impl<State, Body, Extractor> FromRequest<State, Body> for ValidifiedByRef<Extractor>
where
State: Send + Sync,
Body: Send + Sync + 'static,
Extractor: HasValidate + HasModify + FromRequest<State, Body>,
Extractor::Validate: Validate,
{
type Rejection = ValidifyRejection<<Extractor as FromRequest<State, Body>>::Rejection>;
async fn from_request(req: Request<Body>, state: &State) -> Result<Self, Self::Rejection> {
let mut inner = Extractor::from_request(req, state)
.await
.map_err(ValidifyRejection::Inner)?;
inner.get_modify().modify();
inner.get_validate().validate()?;
Ok(ValidifiedByRef(inner))
}
}
#[async_trait]
impl<State, Extractor> FromRequestParts<State> for ValidifiedByRef<Extractor>
where
State: Send + Sync,
Extractor: HasValidate + HasModify + FromRequestParts<State>,
Extractor::Validate: Validate,
{
type Rejection = ValidifyRejection<<Extractor as FromRequestParts<State>>::Rejection>;
async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
let mut inner = Extractor::from_request_parts(parts, state)
.await
.map_err(ValidifyRejection::Inner)?;
inner.get_modify().modify();
inner.get_validate().validate()?;
Ok(ValidifiedByRef(inner))
}
}

View File

@@ -1,8 +1,10 @@
#![cfg(feature = "validify")]
use crate::tests::{ValidTest, ValidTestParameter};
use crate::{HasValidate, Validated, VALIDATION_ERROR_STATUS};
use axum::extract::{FromRef, Path, Query};
use crate::{
HasValidate, Modified, Validated, Validified, ValidifiedByRef, VALIDATION_ERROR_STATUS,
};
use axum::extract::{Path, Query};
use axum::routing::{get, post};
use axum::{Form, Json, Router};
use hyper::Method;
@@ -12,9 +14,9 @@ use serde::{Deserialize, Serialize};
use std::any::type_name;
use std::net::SocketAddr;
use std::ops::Deref;
use validify::Validate;
use validify::{Modify, Validate, Validify};
#[derive(Clone, Deserialize, Serialize, Validate, Eq, PartialEq)]
#[derive(Clone, Deserialize, Serialize, Validify, Eq, PartialEq)]
#[cfg_attr(feature = "extra_protobuf", derive(prost::Message))]
#[cfg_attr(
feature = "typed_multipart",
@@ -24,26 +26,32 @@ pub struct ParametersValidify {
#[validate(range(min = 5.0, max = 10.0))]
#[cfg_attr(feature = "extra_protobuf", prost(int32, tag = "1"))]
v0: i32,
#[modify(lowercase)]
#[validate(length(min = 1, max = 10))]
#[cfg_attr(feature = "extra_protobuf", prost(string, tag = "2"))]
v1: String,
}
trait IsModified: Modify + Clone + PartialEq + Eq {
fn modified(&self) -> bool {
let mut cloned = self.clone();
cloned.modify();
cloned == *self
}
}
impl<T> IsModified for T where T: Modify + Clone + PartialEq + Eq {}
static VALID_PARAMETERS: Lazy<ParametersValidify> = Lazy::new(|| ParametersValidify {
v0: 5,
v1: String::from("0123456789"),
v1: String::from("ABCDEFG"),
});
static INVALID_PARAMETERS: Lazy<ParametersValidify> = Lazy::new(|| ParametersValidify {
v0: 6,
v1: String::from("01234567890"),
v1: String::from("ABCDEFGHIJKLMN"),
});
#[derive(Debug, Clone, FromRef, Default)]
struct MyState {
no_argument_context: (),
}
impl ValidTestParameter for ParametersValidify {
fn valid() -> &'static Self {
VALID_PARAMETERS.deref()
@@ -70,9 +78,33 @@ impl HasValidate for ParametersValidify {
async fn test_main() -> anyhow::Result<()> {
let router = Router::new()
.route(route::PATH, get(extract_path))
.route(route::PATH_MODIFIED, get(extract_path_modified))
.route(route::PATH_VALIDIFIED, get(extract_path_validified))
.route(
route::PATH_VALIDIFIED_BY_REF,
get(extract_path_validified_by_ref),
)
.route(route::QUERY, get(extract_query))
.route(route::QUERY_MODIFIED, get(extract_query_modified))
.route(route::QUERY_VALIDIFIED, get(extract_query_validified))
.route(
route::QUERY_VALIDIFIED_BY_REF,
get(extract_query_validified_by_ref),
)
.route(route::FORM, post(extract_form))
.route(route::JSON, post(extract_json));
.route(route::FORM_MODIFIED, post(extract_form_modified))
.route(route::FORM_VALIDIFIED, post(extract_form_validified))
.route(
route::FORM_VALIDIFIED_BY_REF,
post(extract_form_validified_by_ref),
)
.route(route::JSON, post(extract_json))
.route(route::JSON_MODIFIED, post(extract_json_modified))
.route(route::JSON_VALIDIFIED, post(extract_json_validified))
.route(
route::JSON_VALIDIFIED_BY_REF,
post(extract_json_validified_by_ref),
);
#[cfg(feature = "typed_header")]
let router = router.route(
@@ -104,10 +136,19 @@ async fn test_main() -> anyhow::Result<()> {
);
#[cfg(feature = "extra_typed_path")]
let router = router.route(
extra_typed_path::route::EXTRA_TYPED_PATH,
get(extra_typed_path::extract_extra_typed_path),
);
let router = router
.route(
extra_typed_path::route::EXTRA_TYPED_PATH,
get(extra_typed_path::extract_extra_typed_path),
)
.route(
extra_typed_path::route::EXTRA_TYPED_PATH_MODIFIED,
get(extra_typed_path::extract_extra_typed_path_modified),
)
.route(
extra_typed_path::route::EXTRA_TYPED_PATH_VALIDIFIED_BY_REF,
get(extra_typed_path::extract_extra_typed_path_validified_by_ref),
);
#[cfg(feature = "extra_query")]
let router = router.route(
@@ -122,10 +163,19 @@ async fn test_main() -> anyhow::Result<()> {
);
#[cfg(feature = "extra_protobuf")]
let router = router.route(
extra_protobuf::route::EXTRA_PROTOBUF,
post(extra_protobuf::extract_extra_protobuf),
);
let router = router
.route(
extra_protobuf::route::EXTRA_PROTOBUF,
post(extra_protobuf::extract_extra_protobuf),
)
.route(
extra_protobuf::route::EXTRA_PROTOBUF_MODIFIED,
post(extra_protobuf::extract_extra_protobuf_modified),
)
.route(
extra_protobuf::route::EXTRA_PROTOBUF_VALIDIFIED_BY_REF,
post(extra_protobuf::extract_extra_protobuf_validified_by_ref),
);
#[cfg(feature = "yaml")]
let router = router.route(yaml::route::YAML, post(yaml::extract_yaml));
@@ -138,8 +188,6 @@ async fn test_main() -> anyhow::Result<()> {
post(msgpack::extract_msgpack_raw),
);
let router = router.with_state(MyState::default());
let server = axum::Server::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16)))
.serve(router.into_make_service());
let server_addr = server.local_addr();
@@ -157,6 +205,61 @@ async fn test_main() -> anyhow::Result<()> {
test_executor: &TestExecutor,
route: &str,
server_url: &str,
) -> anyhow::Result<()> {
do_test_extra_path(
test_executor,
route,
server_url,
StatusCode::OK,
StatusCode::BAD_REQUEST,
VALIDATION_ERROR_STATUS,
true,
)
.await
}
async fn test_extra_path_modified(
test_executor: &TestExecutor,
route: &str,
server_url: &str,
) -> anyhow::Result<()> {
do_test_extra_path(
test_executor,
route,
server_url,
StatusCode::OK,
StatusCode::BAD_REQUEST,
StatusCode::OK,
false,
)
.await
}
async fn test_extra_path_validified(
test_executor: &TestExecutor,
route: &str,
server_url: &str,
) -> anyhow::Result<()> {
do_test_extra_path(
test_executor,
route,
server_url,
StatusCode::OK,
StatusCode::BAD_REQUEST,
VALIDATION_ERROR_STATUS,
true,
)
.await
}
async fn do_test_extra_path(
test_executor: &TestExecutor,
route: &str,
server_url: &str,
expected_valid_status: StatusCode,
expected_error_status: StatusCode,
expected_invalid_status: StatusCode,
should_check_json: bool,
) -> anyhow::Result<()> {
let path_type_name = type_name::<Path<ParametersValidify>>();
let valid_path_response = test_executor
@@ -169,7 +272,7 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
valid_path_response.status(),
StatusCode::OK,
expected_valid_status,
"Valid '{}' test failed.",
path_type_name
);
@@ -181,9 +284,10 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
error_path_response.status(),
StatusCode::BAD_REQUEST,
"Error '{}' test failed.",
path_type_name
expected_error_status,
"Error '{}' test failed: {}",
path_type_name,
error_path_response.text().await?
);
let invalid_path_response = test_executor
@@ -196,22 +300,39 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
invalid_path_response.status(),
VALIDATION_ERROR_STATUS,
expected_invalid_status,
"Invalid '{}' test failed.",
path_type_name
);
#[cfg(feature = "into_json")]
check_json(path_type_name, invalid_path_response).await;
if should_check_json {
check_json(path_type_name, invalid_path_response).await;
}
println!("All {} tests passed.", path_type_name);
Ok(())
}
test_extra_path(&test_executor, "path", &server_url).await?;
test_extra_path_modified(&test_executor, "path_modified", &server_url).await?;
test_extra_path_validified(&test_executor, "path_validified", &server_url).await?;
test_extra_path(&test_executor, "path_validified_by_ref", &server_url).await?;
// Validified
// Validated
test_executor
.execute::<Query<ParametersValidify>>(Method::GET, route::QUERY)
.await?;
// Modified
test_executor
.execute_modified::<Query<ParametersValidify>>(Method::GET, route::QUERY_MODIFIED)
.await?;
// ValidifiedByRef
test_executor
.execute::<Query<ParametersValidify>>(Method::GET, route::QUERY_VALIDIFIED_BY_REF)
.await?;
// Validified
test_executor
.execute_validified::<Query<ParametersValidify>>(Method::GET, route::QUERY_VALIDIFIED)
.await?;
// Validified
test_executor
@@ -285,6 +406,44 @@ async fn test_main() -> anyhow::Result<()> {
test_executor: &TestExecutor,
route: &str,
server_url: &str,
) -> anyhow::Result<()> {
do_test_extra_typed_path(
test_executor,
route,
server_url,
StatusCode::OK,
StatusCode::BAD_REQUEST,
VALIDATION_ERROR_STATUS,
true,
)
.await
}
async fn test_extra_typed_path_modified(
test_executor: &TestExecutor,
route: &str,
server_url: &str,
) -> anyhow::Result<()> {
do_test_extra_typed_path(
test_executor,
route,
server_url,
StatusCode::OK,
StatusCode::BAD_REQUEST,
StatusCode::OK,
false,
)
.await
}
async fn do_test_extra_typed_path(
test_executor: &TestExecutor,
route: &str,
server_url: &str,
expected_valid_status: StatusCode,
expected_error_status: StatusCode,
expected_invalid_status: StatusCode,
should_check_json: bool,
) -> anyhow::Result<()> {
let extra_typed_path_type_name = "T: TypedPath";
let valid_extra_typed_path_response = test_executor
@@ -297,7 +456,7 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
valid_extra_typed_path_response.status(),
StatusCode::OK,
expected_valid_status,
"Validified '{}' test failed.",
extra_typed_path_type_name
);
@@ -309,7 +468,7 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
error_extra_typed_path_response.status(),
StatusCode::BAD_REQUEST,
expected_error_status,
"Error '{}' test failed.",
extra_typed_path_type_name
);
@@ -324,21 +483,31 @@ async fn test_main() -> anyhow::Result<()> {
.await?;
assert_eq!(
invalid_extra_typed_path_response.status(),
VALIDATION_ERROR_STATUS,
expected_invalid_status,
"Invalid '{}' test failed.",
extra_typed_path_type_name
);
#[cfg(feature = "into_json")]
check_json(
extra_typed_path_type_name,
invalid_extra_typed_path_response,
)
.await;
if should_check_json {
check_json(
extra_typed_path_type_name,
invalid_extra_typed_path_response,
)
.await;
}
println!("All {} tests passed.", extra_typed_path_type_name);
Ok(())
}
test_extra_typed_path(&test_executor, "extra_typed_path", &server_url).await?;
test_extra_typed_path_modified(&test_executor, "extra_typed_path_modified", &server_url)
.await?;
test_extra_typed_path(
&test_executor,
"extra_typed_path_validified_by_ref",
&server_url,
)
.await?;
}
#[cfg(feature = "extra_query")]
@@ -360,12 +529,27 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "extra_protobuf")]
{
use axum_extra::protobuf::Protobuf;
// Validated
test_executor
.execute::<Protobuf<ParametersValidify>>(
Method::POST,
extra_protobuf::route::EXTRA_PROTOBUF,
)
.await?;
// Modified
test_executor
.execute_modified::<Protobuf<ParametersValidify>>(
Method::POST,
extra_protobuf::route::EXTRA_PROTOBUF_MODIFIED,
)
.await?;
// ValidifiedByRef
test_executor
.execute::<Protobuf<ParametersValidify>>(
Method::POST,
extra_protobuf::route::EXTRA_PROTOBUF_VALIDIFIED_BY_REF,
)
.await?;
}
#[cfg(feature = "yaml")]
@@ -410,6 +594,60 @@ impl From<Url> for TestExecutor {
impl TestExecutor {
/// Execute all tests
pub async fn execute<T: ValidTest>(&self, method: Method, route: &str) -> anyhow::Result<()> {
self.do_execute::<T>(
method,
route,
StatusCode::OK,
T::ERROR_STATUS_CODE,
T::INVALID_STATUS_CODE,
true,
)
.await
}
/// Execute all tests for `Modified` without validation
pub async fn execute_modified<T: ValidTest>(
&self,
method: Method,
route: &str,
) -> anyhow::Result<()> {
self.do_execute::<T>(
method,
route,
StatusCode::OK,
T::ERROR_STATUS_CODE,
StatusCode::OK,
false,
)
.await
}
/// Execute all tests for `Modified` without validation
pub async fn execute_validified<T: ValidTest>(
&self,
method: Method,
route: &str,
) -> anyhow::Result<()> {
self.do_execute::<T>(
method,
route,
StatusCode::OK,
T::INVALID_STATUS_CODE,
T::INVALID_STATUS_CODE,
false,
)
.await
}
async fn do_execute<T: ValidTest>(
&self,
method: Method,
route: &str,
expected_valid_status: StatusCode,
expected_error_status: StatusCode,
expected_invalid_status: StatusCode,
should_check_json: bool,
) -> anyhow::Result<()> {
let url = {
let mut url_builder = self.server_url.clone();
url_builder.set_path(route);
@@ -422,35 +660,37 @@ impl TestExecutor {
let valid_response = T::set_valid_request(valid_builder).send().await?;
assert_eq!(
valid_response.status(),
StatusCode::OK,
"Validified '{}' test failed.",
type_name
expected_valid_status,
"Validified '{}' test failed: {}.",
type_name,
valid_response.text().await?
);
let error_builder = self.client.request(method.clone(), url.clone());
let error_response = T::set_error_request(error_builder).send().await?;
assert_eq!(
error_response.status(),
T::ERROR_STATUS_CODE,
"Error '{}' test failed.",
type_name
expected_error_status,
"Error '{}' test failed: {}.",
type_name,
error_response.text().await?
);
let invalid_builder = self.client.request(method, url);
let invalid_response = T::set_invalid_request(invalid_builder).send().await?;
assert_eq!(
invalid_response.status(),
T::INVALID_STATUS_CODE,
"Invalid '{}' test failed.",
type_name
expected_invalid_status,
"Invalid '{}' test failed: {}.",
type_name,
invalid_response.text().await?
);
#[cfg(feature = "into_json")]
if T::JSON_SERIALIZABLE {
if should_check_json && T::JSON_SERIALIZABLE {
check_json(type_name, invalid_response).await;
}
println!("All '{}' tests passed.", type_name);
Ok(())
}
@@ -473,36 +713,121 @@ pub async fn check_json(type_name: &'static str, response: reqwest::Response) {
mod route {
pub const PATH: &str = "/path/:v0/:v1";
pub const PATH_MODIFIED: &str = "/path_modified/:v0/:v1";
pub const PATH_VALIDIFIED: &str = "/path_validified/:v0/:v1";
pub const PATH_VALIDIFIED_BY_REF: &str = "/path_validified_by_ref/:v0/:v1";
pub const QUERY: &str = "/query";
pub const QUERY_MODIFIED: &str = "/query_modified/:v0/:v1";
pub const QUERY_VALIDIFIED: &str = "/query_validified/:v0/:v1";
pub const QUERY_VALIDIFIED_BY_REF: &str = "/query_validified_by_ref/:v0/:v1";
pub const FORM: &str = "/form";
pub const FORM_MODIFIED: &str = "/form_modified/:v0/:v1";
pub const FORM_VALIDIFIED: &str = "/form_validified/:v0/:v1";
pub const FORM_VALIDIFIED_BY_REF: &str = "/form_validified_by_ref/:v0/:v1";
pub const JSON: &str = "/json";
pub const JSON_MODIFIED: &str = "/json_modified/:v0/:v1";
pub const JSON_VALIDIFIED: &str = "/json_validified/:v0/:v1";
pub const JSON_VALIDIFIED_BY_REF: &str = "/json_validified_by_ref/:v0/:v1";
}
async fn extract_path(
Validated(Path(parameters)): Validated<Path<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
async fn extract_path_modified(
Modified(Path(parameters)): Modified<Path<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
async fn extract_path_validified(
Validified(Path(parameters)): Validified<Path<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_path_validified_by_ref(
ValidifiedByRef(Path(parameters)): ValidifiedByRef<Path<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_query(
Validated(Query(parameters)): Validated<Query<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
async fn extract_query_modified(
Modified(Query(parameters)): Modified<Query<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
async fn extract_query_validified(
Validified(Query(parameters)): Validified<Query<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_query_validified_by_ref(
ValidifiedByRef(Query(parameters)): ValidifiedByRef<Query<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_form(
Validated(Form(parameters)): Validated<Form<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
async fn extract_form_modified(
Modified(Form(parameters)): Modified<Form<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
async fn extract_form_validified(
Validified(Form(parameters)): Validified<Form<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_form_validified_by_ref(
ValidifiedByRef(Form(parameters)): ValidifiedByRef<Form<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_json(
Validated(Json(parameters)): Validated<Json<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
fn validate_again<V: Validate>(validate: V) -> StatusCode {
async fn extract_json_modified(
Modified(Json(parameters)): Modified<Json<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
async fn extract_json_validified(
Validified(Json(parameters)): Validified<Json<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
async fn extract_json_validified_by_ref(
ValidifiedByRef(Json(parameters)): ValidifiedByRef<Json<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
fn check_validated<V: Validate>(validate: &V) -> StatusCode {
// The `Validified` extractor has validated the `parameters` once,
// it should have returned `400 BAD REQUEST` if the `parameters` were invalid,
// Let's validate them again to check if the `Validified` extractor works well.
@@ -513,6 +838,23 @@ fn validate_again<V: Validate>(validate: V) -> StatusCode {
}
}
fn check_modified<M: IsModified>(modify: &M) -> StatusCode {
if modify.modified() {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}
fn check_validified<D: IsModified + Validate>(data: &D) -> StatusCode {
let status = check_modified(data);
if status != StatusCode::OK {
return status;
}
check_validated(data)
}
#[cfg(feature = "typed_header")]
mod typed_header {
@@ -520,7 +862,7 @@ mod typed_header {
pub const TYPED_HEADER: &str = "/typed_header";
}
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::headers::{Error, Header, HeaderName, HeaderValue};
use axum::http::StatusCode;
@@ -531,7 +873,7 @@ mod typed_header {
pub(super) async fn extract_typed_header(
Validated(TypedHeader(parameters)): Validated<TypedHeader<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
impl Header for ParametersValidify {
@@ -583,7 +925,7 @@ mod typed_header {
#[cfg(feature = "typed_multipart")]
mod typed_multipart {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::http::StatusCode;
use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError};
@@ -604,7 +946,7 @@ mod typed_multipart {
pub(super) async fn extract_typed_multipart(
Validated(TypedMultipart(parameters)): Validated<TypedMultipart<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
pub(super) async fn extract_base_multipart(
@@ -612,13 +954,13 @@ mod typed_multipart {
BaseMultipart<ParametersValidify, TypedMultipartError>,
>,
) -> StatusCode {
validate_again(data)
check_validated(&data)
}
}
#[cfg(feature = "extra")]
mod extra {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::tests::{Rejection, ValidTest, ValidTestParameter};
use crate::{Validated, ValidifyRejection};
use axum::extract::FromRequestParts;
@@ -729,7 +1071,7 @@ mod extra {
pub async fn extract_cached(
Validated(Cached(parameters)): Validated<Cached<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
pub async fn extract_with_rejection(
@@ -737,7 +1079,7 @@ mod extra {
WithRejection<ParametersValidify, ValidifyWithRejectionRejection>,
>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
pub struct WithRejectionValidifyRejection<E> {
@@ -764,21 +1106,24 @@ mod extra {
WithRejectionValidifyRejection<ParametersRejection>,
>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
}
#[cfg(feature = "extra_typed_path")]
mod extra_typed_path {
use super::validate_again;
use crate::{HasValidate, Validated};
use super::{check_modified, check_validated, check_validified};
use crate::{HasModify, HasValidate, Modified, Validated, ValidifiedByRef};
use axum::http::StatusCode;
use axum_extra::routing::TypedPath;
use serde::Deserialize;
use validify::Validate;
use validify::{Validate, Validify};
pub mod route {
pub const EXTRA_TYPED_PATH: &str = "/extra_typed_path/:v0/:v1";
pub const EXTRA_TYPED_PATH_MODIFIED: &str = "/extra_typed_path_modified/:v0/:v1";
pub const EXTRA_TYPED_PATH_VALIDIFIED_BY_REF: &str =
"/extra_typed_path_validified_by_ref/:v0/:v1";
}
#[derive(Validate, TypedPath, Deserialize)]
@@ -798,16 +1143,72 @@ mod extra_typed_path {
}
}
#[derive(Validify, TypedPath, Deserialize, Clone, PartialEq, Eq)]
#[typed_path("/extra_typed_path_validified_by_ref/:v0/:v1")]
pub struct TypedPathParamValidifiedByRef {
#[validate(range(min = 5.0, max = 10.0))]
v0: i32,
#[modify(lowercase)]
#[validate(length(min = 1, max = 10))]
v1: String,
}
impl HasValidate for TypedPathParamValidifiedByRef {
type Validate = Self;
fn get_validate(&self) -> &Self::Validate {
self
}
}
impl HasModify for TypedPathParamValidifiedByRef {
type Modify = Self;
fn get_modify(&mut self) -> &mut Self::Modify {
self
}
}
#[derive(Validify, TypedPath, Deserialize, Clone, PartialEq, Eq)]
#[typed_path("/extra_typed_path_modified/:v0/:v1")]
pub struct TypedPathParamModified {
#[validate(range(min = 5.0, max = 10.0))]
v0: i32,
#[modify(lowercase)]
#[validate(length(min = 1, max = 10))]
v1: String,
}
impl HasModify for TypedPathParamModified {
type Modify = Self;
fn get_modify(&mut self) -> &mut Self::Modify {
self
}
}
pub async fn extract_extra_typed_path(
Validated(param): Validated<TypedPathParam>,
) -> StatusCode {
validate_again(param)
check_validated(&param)
}
pub async fn extract_extra_typed_path_modified(
Modified(param): Modified<TypedPathParamModified>,
) -> StatusCode {
check_modified(&param)
}
pub async fn extract_extra_typed_path_validified_by_ref(
ValidifiedByRef(param): ValidifiedByRef<TypedPathParamValidifiedByRef>,
) -> StatusCode {
check_validified(&param)
}
}
#[cfg(feature = "extra_query")]
mod extra_query {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::http::StatusCode;
use axum_extra::extract::Query;
@@ -819,13 +1220,13 @@ mod extra_query {
pub async fn extract_extra_query(
Validated(Query(parameters)): Validated<Query<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
}
#[cfg(feature = "extra_form")]
mod extra_form {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::http::StatusCode;
use axum_extra::extract::Form;
@@ -837,31 +1238,45 @@ mod extra_form {
pub async fn extract_extra_form(
Validated(Form(parameters)): Validated<Form<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
}
#[cfg(feature = "extra_protobuf")]
mod extra_protobuf {
use super::{validate_again, ParametersValidify};
use crate::Validated;
use super::{check_modified, check_validated, check_validified, ParametersValidify};
use crate::{Modified, Validated, ValidifiedByRef};
use axum::http::StatusCode;
use axum_extra::protobuf::Protobuf;
pub mod route {
pub const EXTRA_PROTOBUF: &str = "/extra_protobuf";
pub const EXTRA_PROTOBUF_MODIFIED: &str = "/extra_protobuf_modified";
pub const EXTRA_PROTOBUF_VALIDIFIED_BY_REF: &str = "/extra_protobuf_validified_by_ref";
}
pub async fn extract_extra_protobuf(
Validated(Protobuf(parameters)): Validated<Protobuf<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
pub async fn extract_extra_protobuf_modified(
Modified(Protobuf(parameters)): Modified<Protobuf<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
pub async fn extract_extra_protobuf_validified_by_ref(
ValidifiedByRef(Protobuf(parameters)): ValidifiedByRef<Protobuf<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
}
#[cfg(feature = "yaml")]
mod yaml {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::http::StatusCode;
use axum_yaml::Yaml;
@@ -873,13 +1288,13 @@ mod yaml {
pub async fn extract_yaml(
Validated(Yaml(parameters)): Validated<Yaml<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
}
#[cfg(feature = "msgpack")]
mod msgpack {
use super::{validate_again, ParametersValidify};
use super::{check_validated, ParametersValidify};
use crate::Validated;
use axum::http::StatusCode;
use axum_msgpack::{MsgPack, MsgPackRaw};
@@ -892,11 +1307,11 @@ mod msgpack {
pub async fn extract_msgpack(
Validated(MsgPack(parameters)): Validated<MsgPack<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
pub async fn extract_msgpack_raw(
Validated(MsgPackRaw(parameters)): Validated<MsgPackRaw<ParametersValidify>>,
) -> StatusCode {
validate_again(parameters)
check_validated(&parameters)
}
}

View File

@@ -114,6 +114,23 @@ impl<T: validify::Modify> crate::HasModify for Yaml<T> {
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Yaml<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Yaml<T> {
type Validify = T;
type PayloadExtractor = Yaml<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Yaml(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};