From e4f54a5cb92101560c478f3cabbb1edee55380bf Mon Sep 17 00:00:00 2001 From: gengteng Date: Thu, 28 Sep 2023 23:59:42 +0800 Subject: [PATCH] add tests --- src/lib.rs | 5 +- src/test.rs | 388 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 339 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 34acf93..05cf6c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,14 +159,13 @@ impl ValidationContext { /// # Arguments /// -/// `T`: data type to validate -/// `Self::A`: dependent arguments +/// * `T`: The data type to validate using arguments /// pub trait Arguments<'a, T> where T: ValidateArgs<'a>, { - /// Get dependent arguments + /// Get arguments from `self` fn get(&'a self) -> T::Args; } diff --git a/src/test.rs b/src/test.rs index d80ce46..effc373 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,3 +1,4 @@ +use crate::test::extra_typed_path::TypedPathParamExValidationArguments; use crate::tests::{ValidTest, ValidTestParameter}; use crate::{Arguments, HasValidate, Valid, ValidEx, ValidationContext, VALIDATION_ERROR_STATUS}; use axum::extract::{FromRef, Path, Query}; @@ -109,32 +110,37 @@ impl HasValidate for Parameters { #[derive(Debug, Clone, FromRef)] struct MyState { - validation_ctx: ValidationContext, + param_validation_ctx: ValidationContext, + typed_path_validation_ctx: ValidationContext, } #[tokio::test] async fn test_main() -> anyhow::Result<()> { + let state = MyState { + param_validation_ctx: ValidationContext::::default(), + typed_path_validation_ctx: + ValidationContext::::default(), + }; + let router = Router::new() .route(route::PATH, get(extract_path)) .route(route::QUERY, get(extract_query)) .route(route::FORM, post(extract_form)) - .route(route::JSON, post(extract_json)); - - let router_ex = Router::new() + .route(route::JSON, post(extract_json)) .route(route::QUERY_EX, get(extract_query_ex)) .route(route::FORM_EX, post(extract_form_ex)) - .route(route::JSON_EX, post(extract_json_ex)) - .with_state(MyState { - validation_ctx: ValidationContext::::default(), - }); - - let router = router.merge(router_ex); + .route(route::JSON_EX, post(extract_json_ex)); #[cfg(feature = "typed_header")] - let router = router.route( - typed_header::route::TYPED_HEADER, - post(typed_header::extract_typed_header), - ); + let router = router + .route( + typed_header::route::TYPED_HEADER, + post(typed_header::extract_typed_header), + ) + .route( + typed_header::route::TYPED_HEADER_EX, + post(typed_header::extract_typed_header_ex), + ); #[cfg(feature = "typed_multipart")] let router = router @@ -142,9 +148,17 @@ async fn test_main() -> anyhow::Result<()> { typed_multipart::route::TYPED_MULTIPART, post(typed_multipart::extract_typed_multipart), ) + .route( + typed_multipart::route::TYPED_MULTIPART_EX, + post(typed_multipart::extract_typed_multipart_ex), + ) .route( typed_multipart::route::BASE_MULTIPART, post(typed_multipart::extract_base_multipart), + ) + .route( + typed_multipart::route::BASE_MULTIPART_EX, + post(typed_multipart::extract_base_multipart_ex), ); #[cfg(feature = "extra")] @@ -160,40 +174,72 @@ 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_EX, + get(extra_typed_path::extract_extra_typed_path_ex), + ); #[cfg(feature = "extra_query")] - let router = router.route( - extra_query::route::EXTRA_QUERY, - post(extra_query::extract_extra_query), - ); + let router = router + .route( + extra_query::route::EXTRA_QUERY, + post(extra_query::extract_extra_query), + ) + .route( + extra_query::route::EXTRA_QUERY_EX, + post(extra_query::extract_extra_query_ex), + ); #[cfg(feature = "extra_form")] - let router = router.route( - extra_form::route::EXTRA_FORM, - post(extra_form::extract_extra_form), - ); + let router = router + .route( + extra_form::route::EXTRA_FORM, + post(extra_form::extract_extra_form), + ) + .route( + extra_form::route::EXTRA_FORM_EX, + post(extra_form::extract_extra_form_ex), + ); #[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_EX, + post(extra_protobuf::extract_extra_protobuf_ex), + ); #[cfg(feature = "yaml")] - let router = router.route(yaml::route::YAML, post(yaml::extract_yaml)); + let router = router + .route(yaml::route::YAML, post(yaml::extract_yaml)) + .route(yaml::route::YAML_EX, post(yaml::extract_yaml_ex)); #[cfg(feature = "msgpack")] let router = router .route(msgpack::route::MSGPACK, post(msgpack::extract_msgpack)) + .route( + msgpack::route::MSGPACK_EX, + post(msgpack::extract_msgpack_ex), + ) .route( msgpack::route::MSGPACK_RAW, post(msgpack::extract_msgpack_raw), + ) + .route( + msgpack::route::MSGPACK_RAW_EX, + post(msgpack::extract_msgpack_raw_ex), ); + let router = router.with_state(state); + let server = axum::Server::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))) .serve(router.into_make_service()); let server_addr = server.local_addr(); @@ -288,26 +334,52 @@ async fn test_main() -> anyhow::Result<()> { #[cfg(feature = "typed_header")] { use axum::TypedHeader; + // Valid test_executor .execute::>(Method::POST, typed_header::route::TYPED_HEADER) .await?; + + // ValidEx + test_executor + .execute::>(Method::POST, typed_header::route::TYPED_HEADER_EX) + .await?; } #[cfg(feature = "typed_multipart")] { use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError}; + + // Valid test_executor .execute::>( Method::POST, typed_multipart::route::BASE_MULTIPART, ) .await?; + + // ValidEx + test_executor + .execute::>( + Method::POST, + typed_multipart::route::BASE_MULTIPART_EX, + ) + .await?; + + // Valid test_executor .execute::>( Method::POST, typed_multipart::route::TYPED_MULTIPART, ) .await?; + + // ValidEx + test_executor + .execute::>( + Method::POST, + typed_multipart::route::TYPED_MULTIPART_EX, + ) + .await?; } #[cfg(feature = "extra")] @@ -335,12 +407,16 @@ async fn test_main() -> anyhow::Result<()> { #[cfg(feature = "extra_typed_path")] { - { + async fn test_extra_typed_path( + test_executor: &TestExecutor, + route: &str, + server_url: &str, + ) -> anyhow::Result<()> { let extra_typed_path_type_name = "T: TypedPath"; let valid_extra_typed_path_response = test_executor .client() .get(format!( - "{}/extra_typed_path/{}/{}", + "{}/{route}/{}/{}", server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1 )) .send() @@ -354,7 +430,7 @@ async fn test_main() -> anyhow::Result<()> { let error_extra_typed_path_response = test_executor .client() - .get(format!("{}/extra_typed_path/not_i32/path", server_url)) + .get(format!("{}/{route}/not_i32/path", server_url)) .send() .await?; assert_eq!( @@ -367,7 +443,7 @@ async fn test_main() -> anyhow::Result<()> { let invalid_extra_typed_path_response = test_executor .client() .get(format!( - "{}/extra_typed_path/{}/{}", + "{}/{route}/{}/{}", server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1 )) .send() @@ -385,7 +461,11 @@ async fn test_main() -> anyhow::Result<()> { ) .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(&test_executor, "extra_typed_path_ex", &server_url).await?; } #[cfg(feature = "extra_query")] @@ -394,6 +474,9 @@ async fn test_main() -> anyhow::Result<()> { test_executor .execute::>(Method::POST, extra_query::route::EXTRA_QUERY) .await?; + test_executor + .execute::>(Method::POST, extra_query::route::EXTRA_QUERY_EX) + .await?; } #[cfg(feature = "extra_form")] @@ -402,6 +485,9 @@ async fn test_main() -> anyhow::Result<()> { test_executor .execute::>(Method::POST, extra_form::route::EXTRA_FORM) .await?; + test_executor + .execute::>(Method::POST, extra_form::route::EXTRA_FORM_EX) + .await?; } #[cfg(feature = "extra_protobuf")] @@ -410,6 +496,9 @@ async fn test_main() -> anyhow::Result<()> { test_executor .execute::>(Method::POST, extra_protobuf::route::EXTRA_PROTOBUF) .await?; + test_executor + .execute::>(Method::POST, extra_protobuf::route::EXTRA_PROTOBUF_EX) + .await?; } #[cfg(feature = "yaml")] @@ -418,6 +507,9 @@ async fn test_main() -> anyhow::Result<()> { test_executor .execute::>(Method::POST, yaml::route::YAML) .await?; + test_executor + .execute::>(Method::POST, yaml::route::YAML_EX) + .await?; } #[cfg(feature = "msgpack")] @@ -426,9 +518,15 @@ async fn test_main() -> anyhow::Result<()> { test_executor .execute::>(Method::POST, msgpack::route::MSGPACK) .await?; + test_executor + .execute::>(Method::POST, msgpack::route::MSGPACK_EX) + .await?; test_executor .execute::>(Method::POST, msgpack::route::MSGPACK_RAW) .await?; + test_executor + .execute::>(Method::POST, msgpack::route::MSGPACK_RAW_EX) + .await?; } drop(server_guard); @@ -589,10 +687,12 @@ mod typed_header { pub(crate) mod route { pub const TYPED_HEADER: &str = "/typed_header"; + pub const TYPED_HEADER_EX: &str = "/typed_header_ex"; } use super::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{validate_again_ex, ParametersEx, ParametersExValidationArguments}; + use crate::{Arguments, Valid, ValidEx}; use axum::headers::{Error, Header, HeaderName, HeaderValue}; use axum::http::StatusCode; use axum::TypedHeader; @@ -605,6 +705,15 @@ mod typed_header { validate_again(parameters) } + pub(super) async fn extract_typed_header_ex( + ValidEx(TypedHeader(parameters), args): ValidEx< + TypedHeader, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } + impl Header for Parameters { fn name() -> &'static HeaderName { &AXUM_VALID_PARAMETERS @@ -638,6 +747,39 @@ mod typed_header { } } + impl Header for ParametersEx { + fn name() -> &'static HeaderName { + &AXUM_VALID_PARAMETERS + } + + fn decode<'i, I>(values: &mut I) -> Result + where + Self: Sized, + I: Iterator, + { + let value = values.next().ok_or_else(Error::invalid)?; + let src = std::str::from_utf8(value.as_bytes()).map_err(|_| Error::invalid())?; + let split = src.split(',').collect::>(); + match split.as_slice() { + [v0, v1] => Ok(ParametersEx { + v0: v0.parse().map_err(|_| Error::invalid())?, + v1: v1.to_string(), + }), + _ => Err(Error::invalid()), + } + } + + fn encode>(&self, values: &mut E) { + let v0 = self.v0.to_string(); + let mut vec = Vec::with_capacity(v0.len() + 1 + self.v1.len()); + vec.extend_from_slice(v0.as_bytes()); + vec.push(b','); + vec.extend_from_slice(self.v1.as_bytes()); + let value = HeaderValue::from_bytes(&vec).expect("Failed to build header"); + values.extend(::std::iter::once(value)); + } + } + #[test] fn parameter_is_header() -> anyhow::Result<()> { let parameter = Parameters { @@ -654,14 +796,19 @@ mod typed_header { #[cfg(feature = "typed_multipart")] mod typed_multipart { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError}; pub mod route { pub const TYPED_MULTIPART: &str = "/typed_multipart"; + pub const TYPED_MULTIPART_EX: &str = "/typed_multipart_ex"; pub const BASE_MULTIPART: &str = "/base_multipart"; + pub const BASE_MULTIPART_EX: &str = "/base_multipart_ex"; } impl From<&Parameters> for reqwest::multipart::Form { @@ -678,11 +825,29 @@ mod typed_multipart { validate_again(parameters) } + pub(super) async fn extract_typed_multipart_ex( + ValidEx(TypedMultipart(parameters), args): ValidEx< + TypedMultipart, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } + pub(super) async fn extract_base_multipart( Valid(BaseMultipart { data, .. }): Valid>, ) -> StatusCode { validate_again(data) } + + pub(super) async fn extract_base_multipart_ex( + ValidEx(BaseMultipart { data, .. }, args): ValidEx< + BaseMultipart, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(data, args.get()) + } } #[cfg(feature = "extra")] @@ -837,15 +1002,17 @@ mod extra { #[cfg(feature = "extra_typed_path")] mod extra_typed_path { - use crate::test::validate_again; - use crate::{HasValidate, Valid}; + use crate::test::{validate_again, validate_again_ex}; + use crate::{Arguments, HasValidate, HasValidateArgs, Valid, ValidEx}; use axum::http::StatusCode; use axum_extra::routing::TypedPath; use serde::Deserialize; - use validator::Validate; + use std::ops::RangeInclusive; + use validator::{Validate, ValidateArgs}; pub mod route { pub const EXTRA_TYPED_PATH: &str = "/extra_typed_path/:v0/:v1"; + pub const EXTRA_TYPED_PATH_EX: &str = "/extra_typed_path_ex/:v0/:v1"; } #[derive(Validate, TypedPath, Deserialize)] @@ -868,17 +1035,65 @@ mod extra_typed_path { pub async fn extract_extra_typed_path(Valid(param): Valid) -> StatusCode { validate_again(param) } + + #[derive(Validate, TypedPath, Deserialize)] + #[typed_path("/extra_typed_path_ex/:v0/:v1")] + pub struct TypedPathParamEx { + #[validate(custom(function = "super::validate_v0", arg = "&'v_a RangeInclusive"))] + v0: i32, + #[validate(custom(function = "super::validate_v1", arg = "&'v_a RangeInclusive"))] + v1: String, + } + + impl<'v> HasValidateArgs<'v> for TypedPathParamEx { + type ValidateArgs = Self; + + fn get_validate_args(&self) -> &Self::ValidateArgs { + self + } + } + + #[derive(Debug, Clone)] + pub struct TypedPathParamExValidationArguments { + v0_range: RangeInclusive, + v1_length_range: RangeInclusive, + } + + impl Default for TypedPathParamExValidationArguments { + fn default() -> Self { + Self { + v0_range: 5..=10, + v1_length_range: 1..=10, + } + } + } + + impl<'a> Arguments<'a, TypedPathParamEx> for TypedPathParamExValidationArguments { + fn get(&'a self) -> >::Args { + (&self.v0_range, &self.v1_length_range) + } + } + + pub async fn extract_extra_typed_path_ex( + ValidEx(param, args): ValidEx, + ) -> StatusCode { + validate_again_ex(param, args.get()) + } } #[cfg(feature = "extra_query")] mod extra_query { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_extra::extract::Query; pub mod route { pub const EXTRA_QUERY: &str = "/extra_query"; + pub const EXTRA_QUERY_EX: &str = "/extra_query_ex"; } pub async fn extract_extra_query( @@ -886,17 +1101,30 @@ mod extra_query { ) -> StatusCode { validate_again(parameters) } + + pub async fn extract_extra_query_ex( + ValidEx(Query(parameters), args): ValidEx< + Query, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } } #[cfg(feature = "extra_form")] mod extra_form { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_extra::extract::Form; pub mod route { pub const EXTRA_FORM: &str = "/extra_form"; + pub const EXTRA_FORM_EX: &str = "/extra_form_ex"; } pub async fn extract_extra_form( @@ -904,17 +1132,30 @@ mod extra_form { ) -> StatusCode { validate_again(parameters) } + + pub async fn extract_extra_form_ex( + ValidEx(Form(parameters), args): ValidEx< + Form, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } } #[cfg(feature = "extra_protobuf")] mod extra_protobuf { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_extra::protobuf::Protobuf; pub mod route { pub const EXTRA_PROTOBUF: &str = "/extra_protobuf"; + pub const EXTRA_PROTOBUF_EX: &str = "/extra_protobuf_ex"; } pub async fn extract_extra_protobuf( @@ -922,34 +1163,61 @@ mod extra_protobuf { ) -> StatusCode { validate_again(parameters) } + + pub async fn extract_extra_protobuf_ex( + ValidEx(Protobuf(parameters), args): ValidEx< + Protobuf, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } } #[cfg(feature = "yaml")] mod yaml { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_yaml::Yaml; pub mod route { pub const YAML: &str = "/yaml"; + pub const YAML_EX: &str = "/yaml_ex"; } pub async fn extract_yaml(Valid(Yaml(parameters)): Valid>) -> StatusCode { validate_again(parameters) } + + pub async fn extract_yaml_ex( + ValidEx(Yaml(parameters), args): ValidEx< + Yaml, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } } #[cfg(feature = "msgpack")] mod msgpack { - use crate::test::{validate_again, Parameters}; - use crate::Valid; + use crate::test::{ + validate_again, validate_again_ex, Parameters, ParametersEx, + ParametersExValidationArguments, + }; + use crate::{Arguments, Valid, ValidEx}; use axum::http::StatusCode; use axum_msgpack::{MsgPack, MsgPackRaw}; pub mod route { pub const MSGPACK: &str = "/msgpack"; + pub const MSGPACK_EX: &str = "/msgpack_ex"; pub const MSGPACK_RAW: &str = "/msgpack_raw"; + pub const MSGPACK_RAW_EX: &str = "/msgpack_raw_ex"; } pub async fn extract_msgpack( @@ -958,9 +1226,27 @@ mod msgpack { validate_again(parameters) } + pub async fn extract_msgpack_ex( + ValidEx(MsgPack(parameters), args): ValidEx< + MsgPack, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } + pub async fn extract_msgpack_raw( Valid(MsgPackRaw(parameters)): Valid>, ) -> StatusCode { validate_again(parameters) } + + pub async fn extract_msgpack_raw_ex( + ValidEx(MsgPackRaw(parameters), args): ValidEx< + MsgPackRaw, + ParametersExValidationArguments, + >, + ) -> StatusCode { + validate_again_ex(parameters, args.get()) + } }