remove redundant trait bound; add support for validify
This commit is contained in:
@@ -27,6 +27,7 @@ features = ["all_types", "garde"]
|
||||
axum = { version = "0.6.20", default-features = false }
|
||||
garde = { version = "0.15.0", optional = true }
|
||||
validator = { version = "0.16.1", optional = true}
|
||||
validify = { version = "1.0.12", optional = true }
|
||||
|
||||
[dependencies.axum_typed_multipart]
|
||||
version = "0.10.0"
|
||||
@@ -72,6 +73,7 @@ default = ["basic", "validator"]
|
||||
basic = ["json", "form", "query"]
|
||||
garde = ["dep:garde"]
|
||||
validator = ["dep:validator"]
|
||||
validify = ["dep:validify"]
|
||||
json = ["axum/json"]
|
||||
form = ["axum/form"]
|
||||
query = ["axum/query"]
|
||||
@@ -90,3 +92,4 @@ all_extra_types = ["extra", "extra_typed_path", "extra_query", "extra_form", "ex
|
||||
all_types = ["json", "form", "query", "typed_header", "typed_multipart", "msgpack", "yaml", "all_extra_types"]
|
||||
full = ["validator", "all_types", "422", "into_json"]
|
||||
full_garde = ["garde", "all_types", "422", "into_json"]
|
||||
full_validify = ["validify", "all_types", "422", "into_json"]
|
||||
|
||||
50
README.md
50
README.md
@@ -274,30 +274,32 @@ Current module documentation predominantly showcases `Valid` examples, the usage
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Description | Module | Default | Example | Tests |
|
||||
|------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|---------|---------|-------|
|
||||
| default | Enables `validator` and support for `Query`, `Json` and `Form` | [`validator`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
|
||||
| validator | Enables `validator` | [`validator`] | ✅ | ✅ | ✅ |
|
||||
| garde | Enables `garde` | [`garde`] | ❌ | ✅ | ✅ |
|
||||
| basic | Enables support for `Query`, `Json` and `Form` | [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
|
||||
| json | Enables support for `Json` | [`json`] | ✅ | ✅ | ✅ |
|
||||
| query | Enables support for `Query` | [`query`] | ✅ | ✅ | ✅ |
|
||||
| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ |
|
||||
| typed_header | Enables support for `TypedHeader` | [`typed_header`] | ❌ | ✅ | ✅ |
|
||||
| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | ❌ | ✅ | ✅ |
|
||||
| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | ❌ | ✅ | ✅ |
|
||||
| yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ |
|
||||
| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ |
|
||||
| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ |
|
||||
| extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ |
|
||||
| extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ |
|
||||
| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ |
|
||||
| all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ |
|
||||
| all_types | Enables support for all extractors above | N/A | ❌ | ✅ | ✅ |
|
||||
| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | [`VALIDATION_ERROR_STATUS`] | ❌ | ✅ | ✅ |
|
||||
| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | N/A | ❌ | ✅ | ✅ |
|
||||
| full | Enables `all_types`, `422` and `into_json` | N/A | ❌ | ✅ | ✅ |
|
||||
| full_garde | Enables `garde`, `all_types`, `422` and `into_json`. Consider using `default-features = false` to exclude default `validator` support | N/A | ❌ | ✅ | ✅ |
|
||||
| Feature | Description | Module | Default | Example | Tests |
|
||||
|------------------|------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|---------|---------|-------|
|
||||
| default | Enables `validator` and support for `Query`, `Json` and `Form` | [`validator`], [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
|
||||
| validator | Enables `validator` (`Valid`, `ValidEx`) | [`validator`] | ✅ | ✅ | ✅ |
|
||||
| garde | Enables `garde` (`Garde`) | [`garde`] | ❌ | ✅ | ✅ |
|
||||
| validify | Enables `validify` (`Validated`, `Modified`, `Validified`) | [`validify`] | ❌ | ✅ | ✅ |
|
||||
| basic | Enables support for `Query`, `Json` and `Form` | [`query`], [`json`], [`form`] | ✅ | ✅ | ✅ |
|
||||
| json | Enables support for `Json` | [`json`] | ✅ | ✅ | ✅ |
|
||||
| query | Enables support for `Query` | [`query`] | ✅ | ✅ | ✅ |
|
||||
| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ |
|
||||
| typed_header | Enables support for `TypedHeader` | [`typed_header`] | ❌ | ✅ | ✅ |
|
||||
| typed_multipart | Enables support for `TypedMultipart` and `BaseMultipart` from `axum_typed_multipart` | [`typed_multipart`] | ❌ | ✅ | ✅ |
|
||||
| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-msgpack` | [`msgpack`] | ❌ | ✅ | ✅ |
|
||||
| yaml | Enables support for `Yaml` from `axum-yaml` | [`yaml`] | ❌ | ✅ | ✅ |
|
||||
| extra | Enables support for `Cached`, `WithRejection` from `axum-extra` | [`extra`] | ❌ | ✅ | ✅ |
|
||||
| extra_typed_path | Enables support for `T: TypedPath` from `axum-extra` | [`extra::typed_path`] | ❌ | ✅ | ✅ |
|
||||
| extra_query | Enables support for `Query` from `axum-extra` | [`extra::query`] | ❌ | ✅ | ✅ |
|
||||
| extra_form | Enables support for `Form` from `axum-extra` | [`extra::form`] | ❌ | ✅ | ✅ |
|
||||
| extra_protobuf | Enables support for `Protobuf` from `axum-extra` | [`extra::protobuf`] | ❌ | ✅ | ✅ |
|
||||
| all_extra_types | Enables support for all extractors above from `axum-extra` | N/A | ❌ | ✅ | ✅ |
|
||||
| all_types | Enables support for all extractors above | N/A | ❌ | ✅ | ✅ |
|
||||
| 422 | Use `422 Unprocessable Entity` instead of `400 Bad Request` as the status code when validation fails | [`VALIDATION_ERROR_STATUS`] | ❌ | ✅ | ✅ |
|
||||
| into_json | Validation errors will be serialized into JSON format and returned as the HTTP body | N/A | ❌ | ✅ | ✅ |
|
||||
| full | Enables `all_types`, `422` and `into_json` | N/A | ❌ | ✅ | ✅ |
|
||||
| full_garde | Enables `garde`, `all_types`, `422` and `into_json`. Consider using `default-features = false` to exclude default `validator` support | N/A | ❌ | ✅ | ✅ |
|
||||
| full_garde | Enables `validify`, `all_types`, `422` and `into_json`. Consider using `default-features = false` to exclude default `validator` support | N/A | ❌ | ✅ | ✅ |
|
||||
|
||||
## Compatibility
|
||||
|
||||
|
||||
45
src/extra.rs
45
src/extra.rs
@@ -463,6 +463,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Cached<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Cached<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> HasValidate for WithRejection<T, R> {
|
||||
type Validate = T;
|
||||
fn get_validate(&self) -> &T {
|
||||
@@ -478,6 +487,15 @@ impl<'v, T: ValidateArgs<'v>, R> HasValidateArgs<'v> for WithRejection<T, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify, R> crate::HasModify for WithRejection<T, R> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{Rejection, ValidTest};
|
||||
@@ -485,6 +503,8 @@ mod tests {
|
||||
use crate::Garde;
|
||||
#[cfg(feature = "validator")]
|
||||
use crate::Valid;
|
||||
#[cfg(feature = "validify")]
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_extra::extract::{Cached, WithRejection};
|
||||
use reqwest::RequestBuilder;
|
||||
@@ -572,4 +592,29 @@ mod tests {
|
||||
T::set_invalid_request(builder)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: ValidTest, R> ValidTest for WithRejection<Validated<T>, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Form<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Form<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -109,6 +109,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Protobuf<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Protobuf<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -105,6 +105,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Query<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Query<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -99,9 +99,11 @@
|
||||
use crate::Garde;
|
||||
#[cfg(feature = "validator")]
|
||||
use crate::{Valid, ValidEx};
|
||||
#[cfg(any(feature = "validator", feature = "garde"))]
|
||||
#[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"))]
|
||||
#[cfg(any(feature = "validator", feature = "garde", feature = "validify"))]
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(feature = "validator")]
|
||||
@@ -118,3 +120,13 @@ impl<T: TypedPath + Display, A> TypedPath for ValidEx<T, A> {
|
||||
impl<T: TypedPath + Display> TypedPath for Garde<T> {
|
||||
const PATH: &'static str = T::PATH;
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: TypedPath + Display> TypedPath for Validated<T> {
|
||||
const PATH: &'static str = T::PATH;
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: TypedPath + Display> TypedPath for Validified<T> {
|
||||
const PATH: &'static str = T::PATH;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Form<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Form<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
27
src/json.rs
27
src/json.rs
@@ -105,6 +105,33 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Json<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Json<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T> crate::PayloadExtractor for Json<T> {
|
||||
type Payload = T;
|
||||
|
||||
fn get_payload(self) -> Self::Payload {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Validify> crate::HasValidify for Json<T> {
|
||||
type Validify = T;
|
||||
type PayloadExtractor = Json<T::Payload>;
|
||||
fn from_validified(v: Self::Validify) -> Self {
|
||||
Json(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -20,6 +20,8 @@ pub mod typed_header;
|
||||
pub mod typed_multipart;
|
||||
#[cfg(feature = "validator")]
|
||||
pub mod validator;
|
||||
#[cfg(feature = "validify")]
|
||||
pub mod validify;
|
||||
#[cfg(feature = "yaml")]
|
||||
pub mod yaml;
|
||||
|
||||
@@ -37,7 +39,7 @@ pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::BAD_REQUEST;
|
||||
|
||||
/// Trait for types that can supply a reference that can be validated.
|
||||
///
|
||||
/// Extractor types `T` that implement this trait can be used with `Valid` or `Garde`.
|
||||
/// Extractor types `T` that implement this trait can be used with `Valid`, `Garde` or `Validated`.
|
||||
///
|
||||
pub trait HasValidate {
|
||||
/// Inner type that can be validated for correctness
|
||||
@@ -52,6 +54,11 @@ pub use crate::validator::{Arguments, HasValidateArgs, Valid, ValidEx, ValidReje
|
||||
#[cfg(feature = "garde")]
|
||||
pub use crate::garde::{Garde, GardeRejection};
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
pub use crate::validify::{
|
||||
HasModify, HasValidify, Modified, PayloadExtractor, Validated, Validified, ValidifyRejection,
|
||||
};
|
||||
|
||||
/// `ValidationRejection` is returned when the validation extractor fails.
|
||||
///
|
||||
/// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation
|
||||
|
||||
@@ -112,6 +112,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPack<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for MsgPack<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HasValidate for MsgPackRaw<T> {
|
||||
type Validate = T;
|
||||
fn get_validate(&self) -> &T {
|
||||
@@ -127,6 +136,14 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPackRaw<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for MsgPackRaw<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
28
src/path.rs
28
src/path.rs
@@ -100,3 +100,31 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Path<T> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Path<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T> crate::PayloadExtractor for Path<T> {
|
||||
type Payload = T;
|
||||
|
||||
fn get_payload(self) -> Self::Payload {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Validify> crate::HasValidify for Path<T> {
|
||||
type Validify = T;
|
||||
type PayloadExtractor = Path<T::Payload>;
|
||||
|
||||
fn from_validified(v: Self::Validify) -> Self {
|
||||
Path(v)
|
||||
}
|
||||
}
|
||||
|
||||
28
src/query.rs
28
src/query.rs
@@ -105,6 +105,34 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Query<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Query<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[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};
|
||||
|
||||
@@ -141,6 +141,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for TypedHeader<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for TypedHeader<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -124,6 +124,40 @@ impl<'v, T: ValidateArgs<'v>, R> HasValidateArgs<'v> for BaseMultipart<T, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify, R> crate::HasModify for BaseMultipart<T, R> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
@@ -139,6 +173,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for TypedMultipart<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for TypedMultipart<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -201,7 +201,6 @@ where
|
||||
+ FromRef<State>
|
||||
+ for<'a> Arguments<'a, T = <Extractor as HasValidateArgs<'a>>::ValidateArgs>,
|
||||
Extractor: for<'v> HasValidateArgs<'v> + FromRequest<State, Body>,
|
||||
for<'v> <Extractor as HasValidateArgs<'v>>::ValidateArgs: ValidateArgs<'v>,
|
||||
{
|
||||
type Rejection = ValidRejection<<Extractor as FromRequest<State, Body>>::Rejection>;
|
||||
|
||||
@@ -225,7 +224,6 @@ where
|
||||
+ FromRef<State>
|
||||
+ for<'a> Arguments<'a, T = <Extractor as HasValidateArgs<'a>>::ValidateArgs>,
|
||||
Extractor: for<'v> HasValidateArgs<'v> + FromRequestParts<State>,
|
||||
for<'v> <Extractor as HasValidateArgs<'v>>::ValidateArgs: ValidateArgs<'v>,
|
||||
{
|
||||
type Rejection = ValidRejection<<Extractor as FromRequestParts<State>>::Rejection>;
|
||||
|
||||
|
||||
284
src/validify.rs
Normal file
284
src/validify.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
//! # Validify support
|
||||
//!
|
||||
//! ## Feature
|
||||
//!
|
||||
//! Enable the `validify` feature to use `Validated<E>`, `Modified<E>` and `Validified<E>`.
|
||||
//!
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
|
||||
use crate::{HasValidate, ValidationRejection};
|
||||
use axum::async_trait;
|
||||
use axum::extract::{FromRequest, FromRequestParts};
|
||||
use axum::http::request::Parts;
|
||||
use axum::http::Request;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use validify::{Modify, Validate, ValidationErrors, Validify};
|
||||
|
||||
/// # `Validated` data extractor
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Validated<E>(pub E);
|
||||
|
||||
impl<E> Deref for Validated<E> {
|
||||
type Target = E;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> DerefMut for Validated<E> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for Validated<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Validated<E> {
|
||||
/// Consumes the `Validified` and returns the 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
|
||||
}
|
||||
}
|
||||
|
||||
/// # `Modified` data extractor / response
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Modified<E>(pub E);
|
||||
|
||||
impl<E> Deref for Modified<E> {
|
||||
type Target = E;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> DerefMut for Modified<E> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for Modified<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Modified<E> {
|
||||
/// Consumes the `Modified` and returns the modified data within.
|
||||
///
|
||||
/// This returns the `E` type which represents the data that has been
|
||||
/// modified.
|
||||
pub fn into_inner(self) -> E {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: IntoResponse + HasModify> IntoResponse for Modified<E> {
|
||||
fn into_response(mut self) -> Response {
|
||||
self.get_modify().modify();
|
||||
self.0.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// # `Validified` data extractor
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Validified<E>(pub E);
|
||||
|
||||
impl<E> Deref for Validified<E> {
|
||||
type Target = E;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> DerefMut for Validified<E> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for Validified<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Validified<E> {
|
||||
/// Consumes the `ValidifiedMut` 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 `Validified` / `ValidifiedMut` / `Modified` extractor fails.
|
||||
///
|
||||
pub type ValidifyRejection<E> = ValidationRejection<ValidationErrors, E>;
|
||||
|
||||
impl<E> From<ValidationErrors> for ValidifyRejection<E> {
|
||||
fn from(value: ValidationErrors) -> Self {
|
||||
Self::Valid(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can supply a reference that can be modified.
|
||||
///
|
||||
/// Extractor types `T` that implement this trait can be used with `Modified`.
|
||||
///
|
||||
pub trait HasModify {
|
||||
/// Inner type that can be modified
|
||||
type Modify: Modify;
|
||||
/// Get the inner value
|
||||
fn get_modify(&mut self) -> &mut Self::Modify;
|
||||
}
|
||||
|
||||
/// Extractor to extract payload for constructing data
|
||||
pub trait PayloadExtractor {
|
||||
/// Type of payload for constructing data
|
||||
type Payload;
|
||||
/// Get payload from the extractor
|
||||
fn get_payload(self) -> Self::Payload;
|
||||
}
|
||||
|
||||
/// Trait for types that can supply a reference that can be 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;
|
||||
///
|
||||
type PayloadExtractor: PayloadExtractor<Payload = <Self::Validify as Validify>::Payload>;
|
||||
///
|
||||
fn from_validified(v: Self::Validify) -> Self;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Body, Extractor> FromRequest<State, Body> for Validated<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Body: Send + Sync + 'static,
|
||||
Extractor: HasValidate + FromRequest<State, Body>,
|
||||
Extractor::Validate: validify::Validate,
|
||||
{
|
||||
type Rejection = ValidifyRejection<<Extractor as FromRequest<State, Body>>::Rejection>;
|
||||
|
||||
async fn from_request(req: Request<Body>, state: &State) -> Result<Self, Self::Rejection> {
|
||||
let inner = Extractor::from_request(req, state)
|
||||
.await
|
||||
.map_err(ValidifyRejection::Inner)?;
|
||||
inner.get_validate().validate()?;
|
||||
Ok(Validated(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Extractor> FromRequestParts<State> for Validated<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Extractor: HasValidate + 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 inner = Extractor::from_request_parts(parts, state)
|
||||
.await
|
||||
.map_err(ValidifyRejection::Inner)?;
|
||||
inner.get_validate().validate()?;
|
||||
Ok(Validated(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Body, Extractor> FromRequest<State, Body> for Modified<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Body: Send + Sync + 'static,
|
||||
Extractor: HasModify + FromRequest<State, Body>,
|
||||
{
|
||||
type Rejection = <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?;
|
||||
inner.get_modify().modify();
|
||||
Ok(Modified(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Extractor> FromRequestParts<State> for Modified<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Extractor: HasModify + FromRequestParts<State>,
|
||||
{
|
||||
type Rejection = <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?;
|
||||
inner.get_modify().modify();
|
||||
Ok(Modified(inner))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Body, Extractor> FromRequest<State, Body> for Validified<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Body: Send + Sync + 'static,
|
||||
Extractor: HasValidify,
|
||||
Extractor::Validify: Validify,
|
||||
Extractor::PayloadExtractor: FromRequest<State, Body>,
|
||||
{
|
||||
type Rejection =
|
||||
ValidifyRejection<<Extractor::PayloadExtractor as FromRequest<State, Body>>::Rejection>;
|
||||
|
||||
async fn from_request(req: Request<Body>, state: &State) -> Result<Self, Self::Rejection> {
|
||||
let payload = Extractor::PayloadExtractor::from_request(req, state)
|
||||
.await
|
||||
.map_err(ValidifyRejection::Inner)?;
|
||||
Ok(Validified(Extractor::from_validified(
|
||||
Extractor::Validify::validify(payload.get_payload())?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State, Extractor> FromRequestParts<State> for Validified<Extractor>
|
||||
where
|
||||
State: Send + Sync,
|
||||
Extractor: HasValidify,
|
||||
Extractor::Validify: Validify,
|
||||
Extractor::PayloadExtractor: FromRequestParts<State>,
|
||||
{
|
||||
type Rejection =
|
||||
ValidifyRejection<<Extractor::PayloadExtractor as FromRequestParts<State>>::Rejection>;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &State) -> Result<Self, Self::Rejection> {
|
||||
let payload = Extractor::PayloadExtractor::from_request_parts(parts, state)
|
||||
.await
|
||||
.map_err(ValidifyRejection::Inner)?;
|
||||
Ok(Validified(Extractor::from_validified(
|
||||
Extractor::Validify::validify(payload.get_payload())?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
902
src/validify/test.rs
Normal file
902
src/validify/test.rs
Normal file
@@ -0,0 +1,902 @@
|
||||
#![cfg(feature = "validify")]
|
||||
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
use crate::{HasValidate, Validated, VALIDATION_ERROR_STATUS};
|
||||
use axum::extract::{FromRef, Path, Query};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Form, Json, Router};
|
||||
use hyper::Method;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{StatusCode, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::type_name;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use validify::Validate;
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Validate, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "extra_protobuf", derive(prost::Message))]
|
||||
#[cfg_attr(
|
||||
feature = "typed_multipart",
|
||||
derive(axum_typed_multipart::TryFromMultipart)
|
||||
)]
|
||||
pub struct ParametersValidify {
|
||||
#[validate(range(min = 5.0, max = 10.0))]
|
||||
#[cfg_attr(feature = "extra_protobuf", prost(int32, tag = "1"))]
|
||||
v0: i32,
|
||||
#[validate(length(min = 1, max = 10))]
|
||||
#[cfg_attr(feature = "extra_protobuf", prost(string, tag = "2"))]
|
||||
v1: String,
|
||||
}
|
||||
|
||||
static VALID_PARAMETERS: Lazy<ParametersValidify> = Lazy::new(|| ParametersValidify {
|
||||
v0: 5,
|
||||
v1: String::from("0123456789"),
|
||||
});
|
||||
|
||||
static INVALID_PARAMETERS: Lazy<ParametersValidify> = Lazy::new(|| ParametersValidify {
|
||||
v0: 6,
|
||||
v1: String::from("01234567890"),
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, FromRef, Default)]
|
||||
struct MyState {
|
||||
no_argument_context: (),
|
||||
}
|
||||
|
||||
impl ValidTestParameter for ParametersValidify {
|
||||
fn valid() -> &'static Self {
|
||||
VALID_PARAMETERS.deref()
|
||||
}
|
||||
|
||||
fn error() -> &'static [(&'static str, &'static str)] {
|
||||
&[("not_v0_or_v1", "value")]
|
||||
}
|
||||
|
||||
fn invalid() -> &'static Self {
|
||||
INVALID_PARAMETERS.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasValidate for ParametersValidify {
|
||||
type Validate = ParametersValidify;
|
||||
|
||||
fn get_validate(&self) -> &Self::Validate {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_main() -> anyhow::Result<()> {
|
||||
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));
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
let router = router.route(
|
||||
typed_header::route::TYPED_HEADER,
|
||||
post(typed_header::extract_typed_header),
|
||||
);
|
||||
|
||||
#[cfg(feature = "typed_multipart")]
|
||||
let router = router
|
||||
.route(
|
||||
typed_multipart::route::TYPED_MULTIPART,
|
||||
post(typed_multipart::extract_typed_multipart),
|
||||
)
|
||||
.route(
|
||||
typed_multipart::route::BASE_MULTIPART,
|
||||
post(typed_multipart::extract_base_multipart),
|
||||
);
|
||||
|
||||
#[cfg(feature = "extra")]
|
||||
let router = router
|
||||
.route(extra::route::CACHED, post(extra::extract_cached))
|
||||
.route(
|
||||
extra::route::WITH_REJECTION,
|
||||
post(extra::extract_with_rejection),
|
||||
)
|
||||
.route(
|
||||
extra::route::WITH_REJECTION_VALIDIFY,
|
||||
post(extra::extract_with_rejection_valid),
|
||||
);
|
||||
|
||||
#[cfg(feature = "extra_typed_path")]
|
||||
let router = router.route(
|
||||
extra_typed_path::route::EXTRA_TYPED_PATH,
|
||||
get(extra_typed_path::extract_extra_typed_path),
|
||||
);
|
||||
|
||||
#[cfg(feature = "extra_query")]
|
||||
let router = router.route(
|
||||
extra_query::route::EXTRA_QUERY,
|
||||
post(extra_query::extract_extra_query),
|
||||
);
|
||||
|
||||
#[cfg(feature = "extra_form")]
|
||||
let router = router.route(
|
||||
extra_form::route::EXTRA_FORM,
|
||||
post(extra_form::extract_extra_form),
|
||||
);
|
||||
|
||||
#[cfg(feature = "extra_protobuf")]
|
||||
let router = router.route(
|
||||
extra_protobuf::route::EXTRA_PROTOBUF,
|
||||
post(extra_protobuf::extract_extra_protobuf),
|
||||
);
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
let router = router.route(yaml::route::YAML, post(yaml::extract_yaml));
|
||||
|
||||
#[cfg(feature = "msgpack")]
|
||||
let router = router
|
||||
.route(msgpack::route::MSGPACK, post(msgpack::extract_msgpack))
|
||||
.route(
|
||||
msgpack::route::MSGPACK_RAW,
|
||||
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();
|
||||
println!("Axum server address: {}.", server_addr);
|
||||
|
||||
let (server_guard, close) = tokio::sync::oneshot::channel::<()>();
|
||||
let server_handle = tokio::spawn(server.with_graceful_shutdown(async move {
|
||||
let _ = close.await;
|
||||
}));
|
||||
|
||||
let server_url = format!("http://{}", server_addr);
|
||||
let test_executor = TestExecutor::from(Url::parse(&format!("http://{}", server_addr))?);
|
||||
|
||||
async fn test_extra_path(
|
||||
test_executor: &TestExecutor,
|
||||
route: &str,
|
||||
server_url: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let path_type_name = type_name::<Path<ParametersValidify>>();
|
||||
let valid_path_response = test_executor
|
||||
.client()
|
||||
.get(format!(
|
||||
"{}/{route}/{}/{}",
|
||||
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
valid_path_response.status(),
|
||||
StatusCode::OK,
|
||||
"Valid '{}' test failed.",
|
||||
path_type_name
|
||||
);
|
||||
|
||||
let error_path_response = test_executor
|
||||
.client()
|
||||
.get(format!("{}/{route}/not_i32/path", server_url))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
error_path_response.status(),
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Error '{}' test failed.",
|
||||
path_type_name
|
||||
);
|
||||
|
||||
let invalid_path_response = test_executor
|
||||
.client()
|
||||
.get(format!(
|
||||
"{}/{route}/{}/{}",
|
||||
server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
invalid_path_response.status(),
|
||||
VALIDATION_ERROR_STATUS,
|
||||
"Invalid '{}' test failed.",
|
||||
path_type_name
|
||||
);
|
||||
#[cfg(feature = "into_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?;
|
||||
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<Query<ParametersValidify>>(Method::GET, route::QUERY)
|
||||
.await?;
|
||||
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<Form<ParametersValidify>>(Method::POST, route::FORM)
|
||||
.await?;
|
||||
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<Json<ParametersValidify>>(Method::POST, route::JSON)
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
{
|
||||
use axum::TypedHeader;
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<TypedHeader<ParametersValidify>>(
|
||||
Method::POST,
|
||||
typed_header::route::TYPED_HEADER,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "typed_multipart")]
|
||||
{
|
||||
use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError};
|
||||
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<BaseMultipart<ParametersValidify, TypedMultipartError>>(
|
||||
Method::POST,
|
||||
typed_multipart::route::BASE_MULTIPART,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Validified
|
||||
test_executor
|
||||
.execute::<TypedMultipart<ParametersValidify>>(
|
||||
Method::POST,
|
||||
typed_multipart::route::TYPED_MULTIPART,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra")]
|
||||
{
|
||||
use axum_extra::extract::{Cached, WithRejection};
|
||||
use extra::{
|
||||
ParametersRejection, ValidifyWithRejectionRejection, WithRejectionValidifyRejection,
|
||||
};
|
||||
test_executor
|
||||
.execute::<Cached<ParametersValidify>>(Method::POST, extra::route::CACHED)
|
||||
.await?;
|
||||
test_executor
|
||||
.execute::<WithRejection<ParametersValidify, ValidifyWithRejectionRejection>>(
|
||||
Method::POST,
|
||||
extra::route::WITH_REJECTION,
|
||||
)
|
||||
.await?;
|
||||
test_executor
|
||||
.execute::<WithRejection<
|
||||
Validated<ParametersValidify>,
|
||||
WithRejectionValidifyRejection<ParametersRejection>,
|
||||
>>(Method::POST, extra::route::WITH_REJECTION_VALIDIFY)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[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!(
|
||||
"{}/{route}/{}/{}",
|
||||
server_url, VALID_PARAMETERS.v0, VALID_PARAMETERS.v1
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
valid_extra_typed_path_response.status(),
|
||||
StatusCode::OK,
|
||||
"Validified '{}' test failed.",
|
||||
extra_typed_path_type_name
|
||||
);
|
||||
|
||||
let error_extra_typed_path_response = test_executor
|
||||
.client()
|
||||
.get(format!("{}/{route}/not_i32/path", server_url))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
error_extra_typed_path_response.status(),
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Error '{}' test failed.",
|
||||
extra_typed_path_type_name
|
||||
);
|
||||
|
||||
let invalid_extra_typed_path_response = test_executor
|
||||
.client()
|
||||
.get(format!(
|
||||
"{}/{route}/{}/{}",
|
||||
server_url, INVALID_PARAMETERS.v0, INVALID_PARAMETERS.v1
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(
|
||||
invalid_extra_typed_path_response.status(),
|
||||
VALIDATION_ERROR_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;
|
||||
println!("All {} tests passed.", extra_typed_path_type_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
test_extra_typed_path(&test_executor, "extra_typed_path", &server_url).await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_query")]
|
||||
{
|
||||
use axum_extra::extract::Query;
|
||||
test_executor
|
||||
.execute::<Query<ParametersValidify>>(Method::POST, extra_query::route::EXTRA_QUERY)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_form")]
|
||||
{
|
||||
use axum_extra::extract::Form;
|
||||
test_executor
|
||||
.execute::<Form<ParametersValidify>>(Method::POST, extra_form::route::EXTRA_FORM)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_protobuf")]
|
||||
{
|
||||
use axum_extra::protobuf::Protobuf;
|
||||
test_executor
|
||||
.execute::<Protobuf<ParametersValidify>>(
|
||||
Method::POST,
|
||||
extra_protobuf::route::EXTRA_PROTOBUF,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
{
|
||||
use axum_yaml::Yaml;
|
||||
test_executor
|
||||
.execute::<Yaml<ParametersValidify>>(Method::POST, yaml::route::YAML)
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "msgpack")]
|
||||
{
|
||||
use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||
test_executor
|
||||
.execute::<MsgPack<ParametersValidify>>(Method::POST, msgpack::route::MSGPACK)
|
||||
.await?;
|
||||
test_executor
|
||||
.execute::<MsgPackRaw<ParametersValidify>>(Method::POST, msgpack::route::MSGPACK_RAW)
|
||||
.await?;
|
||||
}
|
||||
|
||||
drop(server_guard);
|
||||
server_handle.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestExecutor {
|
||||
client: reqwest::Client,
|
||||
server_url: Url,
|
||||
}
|
||||
|
||||
impl From<Url> for TestExecutor {
|
||||
fn from(server_url: Url) -> Self {
|
||||
Self {
|
||||
client: Default::default(),
|
||||
server_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestExecutor {
|
||||
/// Execute all tests
|
||||
pub async fn execute<T: ValidTest>(&self, method: Method, route: &str) -> anyhow::Result<()> {
|
||||
let url = {
|
||||
let mut url_builder = self.server_url.clone();
|
||||
url_builder.set_path(route);
|
||||
url_builder
|
||||
};
|
||||
|
||||
let type_name = type_name::<T>();
|
||||
|
||||
let valid_builder = self.client.request(method.clone(), url.clone());
|
||||
let valid_response = T::set_valid_request(valid_builder).send().await?;
|
||||
assert_eq!(
|
||||
valid_response.status(),
|
||||
StatusCode::OK,
|
||||
"Validified '{}' test failed.",
|
||||
type_name
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
#[cfg(feature = "into_json")]
|
||||
if T::JSON_SERIALIZABLE {
|
||||
check_json(type_name, invalid_response).await;
|
||||
}
|
||||
|
||||
println!("All '{}' tests passed.", type_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &reqwest::Client {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the response is a json response
|
||||
#[cfg(feature = "into_json")]
|
||||
pub async fn check_json(type_name: &'static str, response: reqwest::Response) {
|
||||
assert_eq!(
|
||||
response.headers()[axum::http::header::CONTENT_TYPE],
|
||||
axum::http::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
"'{}' rejection into json test failed",
|
||||
type_name
|
||||
);
|
||||
assert!(response.json::<serde_json::Value>().await.is_ok());
|
||||
}
|
||||
|
||||
mod route {
|
||||
pub const PATH: &str = "/path/:v0/:v1";
|
||||
pub const QUERY: &str = "/query";
|
||||
pub const FORM: &str = "/form";
|
||||
pub const JSON: &str = "/json";
|
||||
}
|
||||
|
||||
async fn extract_path(
|
||||
Validated(Path(parameters)): Validated<Path<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
async fn extract_query(
|
||||
Validated(Query(parameters)): Validated<Query<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
async fn extract_form(
|
||||
Validated(Form(parameters)): Validated<Form<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
async fn extract_json(
|
||||
Validated(Json(parameters)): Validated<Json<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
fn validate_again<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.
|
||||
// If it works properly, this function will never return `500 INTERNAL SERVER ERROR`
|
||||
match validate.validate() {
|
||||
Ok(_) => StatusCode::OK,
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "typed_header")]
|
||||
mod typed_header {
|
||||
|
||||
pub(crate) mod route {
|
||||
pub const TYPED_HEADER: &str = "/typed_header";
|
||||
}
|
||||
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::headers::{Error, Header, HeaderName, HeaderValue};
|
||||
use axum::http::StatusCode;
|
||||
use axum::TypedHeader;
|
||||
|
||||
pub static AXUM_VALID_PARAMETERS: HeaderName = HeaderName::from_static("axum-valid-parameters");
|
||||
|
||||
pub(super) async fn extract_typed_header(
|
||||
Validated(TypedHeader(parameters)): Validated<TypedHeader<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
impl Header for ParametersValidify {
|
||||
fn name() -> &'static HeaderName {
|
||||
&AXUM_VALID_PARAMETERS
|
||||
}
|
||||
|
||||
fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized,
|
||||
I: Iterator<Item = &'i HeaderValue>,
|
||||
{
|
||||
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::<Vec<_>>();
|
||||
match split.as_slice() {
|
||||
[v0, v1] => Ok(ParametersValidify {
|
||||
v0: v0.parse().map_err(|_| Error::invalid())?,
|
||||
v1: v1.to_string(),
|
||||
}),
|
||||
_ => Err(Error::invalid()),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode<E: Extend<HeaderValue>>(&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 = ParametersValidify {
|
||||
v0: 123456,
|
||||
v1: "111111".to_string(),
|
||||
};
|
||||
let mut vec = Vec::new();
|
||||
parameter.encode(&mut vec);
|
||||
let mut iter = vec.iter();
|
||||
assert_eq!(parameter, ParametersValidify::decode(&mut iter)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "typed_multipart")]
|
||||
mod typed_multipart {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_typed_multipart::{BaseMultipart, TypedMultipart, TypedMultipartError};
|
||||
|
||||
pub mod route {
|
||||
pub const TYPED_MULTIPART: &str = "/typed_multipart";
|
||||
pub const BASE_MULTIPART: &str = "/base_multipart";
|
||||
}
|
||||
|
||||
impl From<&ParametersValidify> for reqwest::multipart::Form {
|
||||
fn from(value: &ParametersValidify) -> Self {
|
||||
reqwest::multipart::Form::new()
|
||||
.text("v0", value.v0.to_string())
|
||||
.text("v1", value.v1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn extract_typed_multipart(
|
||||
Validated(TypedMultipart(parameters)): Validated<TypedMultipart<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
pub(super) async fn extract_base_multipart(
|
||||
Validated(BaseMultipart { data, .. }): Validated<
|
||||
BaseMultipart<ParametersValidify, TypedMultipartError>,
|
||||
>,
|
||||
) -> StatusCode {
|
||||
validate_again(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra")]
|
||||
mod extra {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::tests::{Rejection, ValidTest, ValidTestParameter};
|
||||
use crate::{Validated, ValidifyRejection};
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::request::Parts;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum_extra::extract::{Cached, WithRejection};
|
||||
use reqwest::RequestBuilder;
|
||||
|
||||
pub mod route {
|
||||
pub const CACHED: &str = "/cached";
|
||||
pub const WITH_REJECTION: &str = "/with_rejection";
|
||||
pub const WITH_REJECTION_VALIDIFY: &str = "/with_rejection_validify";
|
||||
}
|
||||
pub const PARAMETERS_HEADER: &str = "parameters-header";
|
||||
pub const CACHED_REJECTION_STATUS: StatusCode = StatusCode::FORBIDDEN;
|
||||
|
||||
// 1.2. Define you own `Rejection` type and implement `IntoResponse` for it.
|
||||
pub enum ParametersRejection {
|
||||
Null,
|
||||
InvalidJson(serde_json::error::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for ParametersRejection {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
ParametersRejection::Null => {
|
||||
(CACHED_REJECTION_STATUS, "My-Data header is missing").into_response()
|
||||
}
|
||||
ParametersRejection::InvalidJson(e) => (
|
||||
CACHED_REJECTION_STATUS,
|
||||
format!("My-Data is not valid json string: {e}"),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1.3. Implement your extractor (`FromRequestParts` or `FromRequest`)
|
||||
#[axum::async_trait]
|
||||
impl<S> FromRequestParts<S> for ParametersValidify
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = ParametersRejection;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||
let Some(value) = parts.headers.get(PARAMETERS_HEADER) else {
|
||||
return Err(ParametersRejection::Null);
|
||||
};
|
||||
|
||||
serde_json::from_slice(value.as_bytes()).map_err(ParametersRejection::InvalidJson)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidTest for ParametersValidify {
|
||||
const ERROR_STATUS_CODE: StatusCode = CACHED_REJECTION_STATUS;
|
||||
|
||||
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.header(
|
||||
PARAMETERS_HEADER,
|
||||
serde_json::to_string(ParametersValidify::valid())
|
||||
.expect("Failed to serialize parameters"),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.header(
|
||||
PARAMETERS_HEADER,
|
||||
serde_json::to_string(ParametersValidify::error())
|
||||
.expect("Failed to serialize parameters"),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
builder.header(
|
||||
PARAMETERS_HEADER,
|
||||
serde_json::to_string(ParametersValidify::invalid())
|
||||
.expect("Failed to serialize parameters"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValidifyWithRejectionRejection {
|
||||
inner: ParametersRejection,
|
||||
}
|
||||
|
||||
impl Rejection for ValidifyWithRejectionRejection {
|
||||
const STATUS_CODE: StatusCode = StatusCode::CONFLICT;
|
||||
}
|
||||
|
||||
impl IntoResponse for ValidifyWithRejectionRejection {
|
||||
fn into_response(self) -> Response {
|
||||
let mut response = self.inner.into_response();
|
||||
*response.status_mut() = Self::STATUS_CODE;
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
// satisfy the `WithRejection`'s extractor trait bound
|
||||
// R: From<E::Rejection> + IntoResponse
|
||||
impl From<ParametersRejection> for ValidifyWithRejectionRejection {
|
||||
fn from(inner: ParametersRejection) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extract_cached(
|
||||
Validated(Cached(parameters)): Validated<Cached<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
pub async fn extract_with_rejection(
|
||||
Validated(WithRejection(parameters, _)): Validated<
|
||||
WithRejection<ParametersValidify, ValidifyWithRejectionRejection>,
|
||||
>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
pub struct WithRejectionValidifyRejection<E> {
|
||||
inner: ValidifyRejection<E>,
|
||||
}
|
||||
|
||||
impl<E> From<ValidifyRejection<E>> for WithRejectionValidifyRejection<E> {
|
||||
fn from(inner: ValidifyRejection<E>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: IntoResponse> IntoResponse for WithRejectionValidifyRejection<E> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.inner.into_response();
|
||||
*res.status_mut() = StatusCode::IM_A_TEAPOT;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extract_with_rejection_valid(
|
||||
WithRejection(Validated(parameters), _): WithRejection<
|
||||
Validated<ParametersValidify>,
|
||||
WithRejectionValidifyRejection<ParametersRejection>,
|
||||
>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_typed_path")]
|
||||
mod extra_typed_path {
|
||||
use super::validate_again;
|
||||
use crate::{HasValidate, Validated};
|
||||
use axum::http::StatusCode;
|
||||
use axum_extra::routing::TypedPath;
|
||||
use serde::Deserialize;
|
||||
use validify::Validate;
|
||||
|
||||
pub mod route {
|
||||
pub const EXTRA_TYPED_PATH: &str = "/extra_typed_path/:v0/:v1";
|
||||
}
|
||||
|
||||
#[derive(Validate, TypedPath, Deserialize)]
|
||||
#[typed_path("/extra_typed_path/:v0/:v1")]
|
||||
pub struct TypedPathParam {
|
||||
#[validate(range(min = 5.0, max = 10.0))]
|
||||
v0: i32,
|
||||
#[validate(length(min = 1, max = 10))]
|
||||
v1: String,
|
||||
}
|
||||
|
||||
impl HasValidate for TypedPathParam {
|
||||
type Validate = Self;
|
||||
|
||||
fn get_validate(&self) -> &Self::Validate {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn extract_extra_typed_path(
|
||||
Validated(param): Validated<TypedPathParam>,
|
||||
) -> StatusCode {
|
||||
validate_again(param)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_query")]
|
||||
mod extra_query {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_extra::extract::Query;
|
||||
|
||||
pub mod route {
|
||||
pub const EXTRA_QUERY: &str = "/extra_query";
|
||||
}
|
||||
|
||||
pub async fn extract_extra_query(
|
||||
Validated(Query(parameters)): Validated<Query<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_form")]
|
||||
mod extra_form {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_extra::extract::Form;
|
||||
|
||||
pub mod route {
|
||||
pub const EXTRA_FORM: &str = "/extra_form";
|
||||
}
|
||||
|
||||
pub async fn extract_extra_form(
|
||||
Validated(Form(parameters)): Validated<Form<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "extra_protobuf")]
|
||||
mod extra_protobuf {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_extra::protobuf::Protobuf;
|
||||
|
||||
pub mod route {
|
||||
pub const EXTRA_PROTOBUF: &str = "/extra_protobuf";
|
||||
}
|
||||
|
||||
pub async fn extract_extra_protobuf(
|
||||
Validated(Protobuf(parameters)): Validated<Protobuf<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
mod yaml {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_yaml::Yaml;
|
||||
|
||||
pub mod route {
|
||||
pub const YAML: &str = "/yaml";
|
||||
}
|
||||
|
||||
pub async fn extract_yaml(
|
||||
Validated(Yaml(parameters)): Validated<Yaml<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "msgpack")]
|
||||
mod msgpack {
|
||||
use super::{validate_again, ParametersValidify};
|
||||
use crate::Validated;
|
||||
use axum::http::StatusCode;
|
||||
use axum_msgpack::{MsgPack, MsgPackRaw};
|
||||
|
||||
pub mod route {
|
||||
pub const MSGPACK: &str = "/msgpack";
|
||||
pub const MSGPACK_RAW: &str = "/msgpack_raw";
|
||||
}
|
||||
|
||||
pub async fn extract_msgpack(
|
||||
Validated(MsgPack(parameters)): Validated<MsgPack<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
pub async fn extract_msgpack_raw(
|
||||
Validated(MsgPackRaw(parameters)): Validated<MsgPackRaw<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,15 @@ impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Yaml<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Yaml<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[feature_default]
|
||||
|
||||
[feature_all_types_into_json]
|
||||
features = "validator garde all_types into_json"
|
||||
features = "validator garde validify all_types into_json"
|
||||
|
||||
[feature_all_types_422]
|
||||
features = "validator garde all_types 422"
|
||||
features = "validator garde validify all_types 422"
|
||||
|
||||
[feature_all_types_422_into_json]
|
||||
features = "validator garde all_types 422 into_json"
|
||||
features = "validator garde validify all_types 422 into_json"
|
||||
Reference in New Issue
Block a user