feat: add support extractors from axum-serde

This commit is contained in:
gengteng
2023-11-30 15:50:33 +08:00
parent 587d8dc086
commit 566382f34a
10 changed files with 833 additions and 16 deletions

View File

@@ -35,6 +35,10 @@ version = "0.9.0"
default-features = false
optional = true
[dependencies.axum-serde]
version = "0.1.0"
optional = true
[dependencies.serde]
version = "1.0.193"
optional = true
@@ -48,7 +52,9 @@ reqwest = { version = "0.11.22", features = ["json", "multipart"] }
serde = { version = "1.0.189", features = ["derive"] }
validator = { version = "0.16.1", features = ["derive"] }
serde_json = "1.0.107"
serde_yaml = "0.9.25"
serde_yaml = "0.9.27"
quick-xml = { version = "0.31.0", features = ["serialize"] }
toml = "0.8.8"
mime = "0.3.17"
prost = "0.12.1"
once_cell = "1.18.0"
@@ -64,6 +70,10 @@ json = ["axum/json"]
form = ["axum/form"]
query = ["axum/query"]
typed_header = ["extra", "axum-extra/typed-header"]
msgpack = ["dep:axum-serde", "axum-serde/msgpack"]
yaml = ["dep:axum-serde", "axum-serde/yaml"]
xml = ["dep:axum-serde", "axum-serde/xml"]
toml = ["dep:axum-serde", "axum-serde/toml"]
into_json = ["json", "dep:serde"]
422 = []
extra = ["dep:axum-extra"]
@@ -72,7 +82,7 @@ extra_query = ["extra", "axum-extra/query"]
extra_form = ["extra", "axum-extra/form"]
extra_protobuf = ["extra", "axum-extra/protobuf"]
all_extra_types = ["extra", "typed_header", "extra_typed_path", "extra_query", "extra_form", "extra_protobuf"]
all_types = ["json", "form", "query", "all_extra_types"]
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "all_extra_types"]
full_validator = ["validator", "all_types", "422", "into_json"]
full_garde = ["garde", "all_types", "422", "into_json"]
full_validify = ["validify", "all_types", "422", "into_json"]

View File

@@ -440,6 +440,10 @@ Current module documentation predominantly showcases `Valid` examples, the usage
| query | Enables support for `Query` | [`query`] | ✅ | ✅ | ✅ |
| form | Enables support for `Form` | [`form`] | ✅ | ✅ | ✅ |
| typed_header | Enables support for `TypedHeader` from `axum-extra` | [`typed_header`] | ❌ | ✅ | ✅ |
| msgpack | Enables support for `MsgPack` and `MsgPackRaw` from `axum-serde` | [`msgpack`] | ❌ | ✅ | ✅ |
| yaml | Enables support for `Yaml` from `axum-serde` | [`yaml`] | ❌ | ✅ | ✅ |
| xml | Enables support for `Xml` from `axum-serde` | [`xml`] | ❌ | ✅ | ✅ |
| toml | Enables support for `Toml` from `axum-serde` | [`toml`] | ❌ | ✅ | ✅ |
| 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`] | ❌ | ✅ | ✅ |
@@ -472,6 +476,5 @@ This project is licensed under the MIT License.
* [validify](https://crates.io/crates/validify)
* [serde](https://crates.io/crates/serde)
* [axum-extra](https://crates.io/crates/axum-extra)
* [axum-yaml](https://crates.io/crates/axum-yaml)
* [axum-msgpack](https://crates.io/crates/axum-msgpack)
* [axum-serde](https://crates.io/crates/axum-serde)
* [axum_typed_multipart](https://crates.io/crates/axum_typed_multipart)

View File

@@ -371,7 +371,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "yaml")]
{
use axum_yaml::Yaml;
use axum_serde::Yaml;
test_executor
.execute::<Yaml<ParametersGarde>>(Method::POST, yaml::route::YAML)
.await?;
@@ -379,7 +379,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "msgpack")]
{
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
test_executor
.execute::<MsgPack<ParametersGarde>>(Method::POST, msgpack::route::MSGPACK)
.await?;
@@ -853,7 +853,7 @@ mod yaml {
use super::{validate_again, ParametersGarde};
use crate::Garde;
use axum::http::StatusCode;
use axum_yaml::Yaml;
use axum_serde::Yaml;
pub mod route {
pub const YAML: &str = "/yaml";
@@ -869,7 +869,7 @@ mod msgpack {
use super::{validate_again, ParametersGarde};
use crate::Garde;
use axum::http::StatusCode;
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
pub mod route {
pub const MSGPACK: &str = "/msgpack";

View File

@@ -9,6 +9,8 @@ pub mod form;
pub mod garde;
#[cfg(feature = "json")]
pub mod json;
#[cfg(feature = "msgpack")]
pub mod msgpack;
pub mod path;
#[cfg(feature = "query")]
pub mod query;
@@ -18,6 +20,13 @@ pub mod typed_header;
pub mod validator;
#[cfg(feature = "validify")]
pub mod validify;
#[cfg(feature = "yaml")]
pub mod yaml;
#[cfg(feature = "toml")]
pub mod toml;
#[cfg(feature = "xml")]
pub mod xml;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};

265
src/msgpack.rs Normal file
View File

@@ -0,0 +1,265 @@
//! # Support for `MsgPack<T>` and `MsgPackRaw<T>` from `axum-serde`
//!
//! ## Feature
//!
//! Enable the `msgpack` feature to use `Valid<MsgPack<T>>` and `Valid<MsgPackRaw<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<MsgPack<T>>` or `Valid<MsgPackRaw<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! #[cfg(feature = "validator")]
//! mod validator_example {
//! use axum::routing::post;
//! use axum::Json;
//! use axum::Router;
//! use axum_serde::{MsgPack, MsgPackRaw};
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! pub fn router() -> Router {
//! Router::new()
//! .route("/msgpack", post(handler))
//! .route("/msgpackraw", post(raw_handler))
//! }
//! async fn handler(Valid(MsgPack(parameter)): Valid<MsgPack<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//!
//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid<MsgPackRaw<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! }
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! #[cfg(feature = "garde")]
//! mod garde_example {
//! use axum::routing::post;
//! use axum::Router;
//! use axum_serde::{MsgPack, MsgPackRaw};
//! use axum_valid::Garde;
//! use serde::Deserialize;
//! use garde::Validate;
//!
//! pub fn router() -> Router {
//! Router::new()
//! .route("/msgpack", post(handler))
//! .route("/msgpackraw", post(raw_handler))
//! }
//!
//! async fn handler(Garde(MsgPack(parameter)): Garde<MsgPack<Parameter>>) {
//! assert!(parameter.validate(&()).is_ok());
//! }
//!
//! async fn raw_handler(Garde(MsgPackRaw(parameter)): Garde<MsgPackRaw<Parameter>>) {
//! assert!(parameter.validate(&()).is_ok());
//! }
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[garde(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[garde(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # use std::net::SocketAddr;
//! # use axum::Router;
//! # use tokio::net::TcpListener;
//! # let router = Router::new();
//! # #[cfg(feature = "validator")]
//! # let router = router.nest("/validator", validator_example::router());
//! # #[cfg(feature = "garde")]
//! # let router = router.nest("/garde", garde_example::router());
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
//! # axum::serve(listener, router.into_make_service())
//! # .await?;
//! # Ok(())
//! # }
//! ```
//!
use crate::HasValidate;
#[cfg(feature = "validator")]
use crate::HasValidateArgs;
use axum_serde::{MsgPack, MsgPackRaw};
#[cfg(feature = "validator")]
use validator::ValidateArgs;
impl<T> HasValidate for MsgPack<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(feature = "validator")]
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPack<T> {
type ValidateArgs = T;
fn get_validate_args(&self) -> &Self::ValidateArgs {
&self.0
}
}
#[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
}
}
#[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 {
&self.0
}
}
#[cfg(feature = "validator")]
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPackRaw<T> {
type ValidateArgs = T;
fn get_validate_args(&self) -> &Self::ValidateArgs {
&self.0
}
}
#[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(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};
use axum::http::StatusCode;
use axum_serde::{MsgPack, MsgPackRaw};
use reqwest::RequestBuilder;
use serde::Serialize;
impl<T: ValidTestParameter + Serialize> ValidTest for MsgPack<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::BAD_REQUEST;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec_named(T::valid())
.expect("Failed to serialize parameters to msgpack"),
)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
#[derive(Serialize, Default)]
struct ErrorData {
error_field0: i32,
error_field1: Option<String>,
}
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec(&ErrorData::default())
.expect("Failed to serialize parameters to msgpack"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec_named(T::invalid())
.expect("Failed to serialize parameters to msgpack"),
)
}
}
impl<T: ValidTestParameter + Serialize> ValidTest for MsgPackRaw<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::BAD_REQUEST;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec(T::valid())
.expect("Failed to serialize parameters to msgpack"),
)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
#[derive(Serialize, Default)]
struct ErrorData {
error_field0: i32,
error_field1: Option<String>,
}
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec(&ErrorData::default())
.expect("Failed to serialize parameters to msgpack"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/msgpack")
.body(
rmp_serde::to_vec(T::invalid())
.expect("Failed to serialize parameters to msgpack"),
)
}
}
}

174
src/toml.rs Normal file
View File

@@ -0,0 +1,174 @@
//! # Support for `Toml<T>` from `axum-serde`
//!
//! ## Feature
//!
//! Enable the `toml` feature to use `Valid<Toml<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Toml<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! #[cfg(feature = "validator")]
//! mod validator_example {
//! use axum::routing::post;
//! use axum_serde::Toml;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/toml", post(handler))
//! }
//!
//! async fn handler(Valid(Toml(parameter)): Valid<Toml<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! #[cfg(feature = "garde")]
//! mod garde_example {
//! use axum::routing::post;
//! use axum_serde::Toml;
//! use axum::Router;
//! use axum_valid::Garde;
//! use serde::Deserialize;
//! use garde::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/toml", post(handler))
//! }
//!
//! async fn handler(Garde(Toml(parameter)): Garde<Toml<Parameter>>) {
//! assert!(parameter.validate(&()).is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[garde(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[garde(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # use std::net::SocketAddr;
//! # use axum::Router;
//! # use tokio::net::TcpListener;
//! # let router = Router::new();
//! # #[cfg(feature = "validator")]
//! # let router = router.nest("/validator", validator_example::router());
//! # #[cfg(feature = "garde")]
//! # let router = router.nest("/garde", garde_example::router());
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
//! # axum::serve(listener, router.into_make_service())
//! # .await?;
//! # Ok(())
//! # }
//! ```
use crate::HasValidate;
#[cfg(feature = "validator")]
use crate::HasValidateArgs;
use axum_serde::Toml;
#[cfg(feature = "validator")]
use validator::ValidateArgs;
impl<T> HasValidate for Toml<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(feature = "validator")]
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Toml<T> {
type ValidateArgs = T;
fn get_validate_args(&self) -> &Self::ValidateArgs {
&self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Modify> crate::HasModify for Toml<T> {
type Modify = T;
fn get_modify(&mut self) -> &mut Self::Modify {
&mut self.0
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Toml<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Toml<T> {
type Validify = T;
type PayloadExtractor = Toml<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Toml(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::http::StatusCode;
use axum_serde::Toml;
use reqwest::RequestBuilder;
use serde::Serialize;
impl<T: ValidTestParameter + Serialize> ValidTest for Toml<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/toml")
.body(toml::to_string(&T::valid()).expect("Failed to serialize parameters to toml"))
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
#[derive(Serialize, Default)]
struct ErrorData {
error_field: i32,
}
builder
.header(reqwest::header::CONTENT_TYPE, "application/toml")
.body(
toml::to_string(&ErrorData::default())
.expect("Failed to serialize parameters to toml"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/toml")
.body(
toml::to_string(&T::invalid()).expect("Failed to serialize parameters to toml"),
)
}
}
}

View File

@@ -550,7 +550,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "yaml")]
{
use axum_yaml::Yaml;
use axum_serde::Yaml;
test_executor
.execute::<Yaml<Parameters>>(Method::POST, yaml::route::YAML)
.await?;
@@ -561,7 +561,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "msgpack")]
{
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
test_executor
.execute::<MsgPack<Parameters>>(Method::POST, msgpack::route::MSGPACK)
.await?;
@@ -1284,7 +1284,7 @@ mod yaml {
};
use crate::{Arguments, Valid, ValidEx};
use axum::http::StatusCode;
use axum_yaml::Yaml;
use axum_serde::Yaml;
pub mod route {
pub const YAML: &str = "/yaml";
@@ -1313,7 +1313,7 @@ mod msgpack {
};
use crate::{Arguments, Valid, ValidEx};
use axum::http::StatusCode;
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
pub mod route {
pub const MSGPACK: &str = "/msgpack";

View File

@@ -828,7 +828,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "yaml")]
{
use axum_yaml::Yaml;
use axum_serde::Yaml;
// Validated
test_executor
@@ -853,7 +853,7 @@ async fn test_main() -> anyhow::Result<()> {
#[cfg(feature = "msgpack")]
{
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
// Validated
test_executor
.execute::<MsgPack<ParametersValidify>>(Method::POST, msgpack::route::MSGPACK)
@@ -1770,7 +1770,7 @@ mod yaml {
use super::{check_modified, check_validated, check_validified, ParametersValidify};
use crate::{Modified, Validated, Validified, ValidifiedByRef};
use axum::http::StatusCode;
use axum_yaml::Yaml;
use axum_serde::Yaml;
pub mod route {
pub const YAML: &str = "/yaml";
@@ -1809,7 +1809,7 @@ mod msgpack {
use super::{check_modified, check_validated, check_validified, ParametersValidify};
use crate::{Modified, Validated, Validified, ValidifiedByRef};
use axum::http::StatusCode;
use axum_msgpack::{MsgPack, MsgPackRaw};
use axum_serde::{MsgPack, MsgPackRaw};
pub mod route {
pub const MSGPACK: &str = "/msgpack";

178
src/xml.rs Normal file
View File

@@ -0,0 +1,178 @@
//! # Support for `Xml<T>` from `axum-serde`
//!
//! ## Feature
//!
//! Enable the `Xml` feature to use `Valid<Xml<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Xml<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! #[cfg(feature = "validator")]
//! mod validator_example {
//! use axum::routing::post;
//! use axum_serde::Xml;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/Xml", post(handler))
//! }
//!
//! async fn handler(Valid(Xml(parameter)): Valid<Xml<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! #[cfg(feature = "garde")]
//! mod garde_example {
//! use axum::routing::post;
//! use axum_serde::Xml;
//! use axum::Router;
//! use axum_valid::Garde;
//! use serde::Deserialize;
//! use garde::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/Xml", post(handler))
//! }
//!
//! async fn handler(Garde(Xml(parameter)): Garde<Xml<Parameter>>) {
//! assert!(parameter.validate(&()).is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[garde(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[garde(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # use std::net::SocketAddr;
//! # use axum::Router;
//! # use tokio::net::TcpListener;
//! # let router = Router::new();
//! # #[cfg(feature = "validator")]
//! # let router = router.nest("/validator", validator_example::router());
//! # #[cfg(feature = "garde")]
//! # let router = router.nest("/garde", garde_example::router());
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
//! # axum::serve(listener, router.into_make_service())
//! # .await?;
//! # Ok(())
//! # }
//! ```
use crate::HasValidate;
#[cfg(feature = "validator")]
use crate::HasValidateArgs;
use axum_serde::Xml;
#[cfg(feature = "validator")]
use validator::ValidateArgs;
impl<T> HasValidate for Xml<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(feature = "validator")]
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Xml<T> {
type ValidateArgs = T;
fn get_validate_args(&self) -> &Self::ValidateArgs {
&self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Modify> crate::HasModify for Xml<T> {
type Modify = T;
fn get_modify(&mut self) -> &mut Self::Modify {
&mut self.0
}
}
#[cfg(feature = "validify")]
impl<T> crate::PayloadExtractor for Xml<T> {
type Payload = T;
fn get_payload(self) -> Self::Payload {
self.0
}
}
#[cfg(feature = "validify")]
impl<T: validify::Validify> crate::HasValidify for Xml<T> {
type Validify = T;
type PayloadExtractor = Xml<T::Payload>;
fn from_validified(v: Self::Validify) -> Self {
Xml(v)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{ValidTest, ValidTestParameter};
use axum::http::StatusCode;
use axum_serde::Xml;
use reqwest::RequestBuilder;
use serde::Serialize;
impl<T: ValidTestParameter + Serialize> ValidTest for Xml<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/xml")
.body(
quick_xml::se::to_string(&T::valid())
.expect("Failed to serialize parameters to xml"),
)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
#[derive(Serialize, Default)]
struct ErrorData {
error_field: i32,
}
builder
.header(reqwest::header::CONTENT_TYPE, "application/xml")
.body(
quick_xml::se::to_string(&ErrorData::default())
.expect("Failed to serialize parameters to xml"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/xml")
.body(
quick_xml::se::to_string(&T::invalid())
.expect("Failed to serialize parameters to xml"),
)
}
}
}

178
src/yaml.rs Normal file
View File

@@ -0,0 +1,178 @@
//! # Support for `Yaml<T>` from `axum-serde`
//!
//! ## Feature
//!
//! Enable the `yaml` feature to use `Valid<Yaml<T>>`.
//!
//! ## Usage
//!
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
//! 2. In your handler function, use `Valid<Yaml<T>>` as some parameter's type.
//!
//! ## Example
//!
//! ```no_run
//! #[cfg(feature = "validator")]
//! mod validator_example {
//! use axum::routing::post;
//! use axum_serde::Yaml;
//! use axum::Router;
//! use axum_valid::Valid;
//! use serde::Deserialize;
//! use validator::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/yaml", post(handler))
//! }
//!
//! async fn handler(Valid(Yaml(parameter)): Valid<Yaml<Parameter>>) {
//! assert!(parameter.validate().is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[validate(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[validate(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! #[cfg(feature = "garde")]
//! mod garde_example {
//! use axum::routing::post;
//! use axum_serde::Yaml;
//! use axum::Router;
//! use axum_valid::Garde;
//! use serde::Deserialize;
//! use garde::Validate;
//!
//! pub fn router() -> Router {
//! Router::new().route("/yaml", post(handler))
//! }
//!
//! async fn handler(Garde(Yaml(parameter)): Garde<Yaml<Parameter>>) {
//! assert!(parameter.validate(&()).is_ok());
//! // Support automatic dereferencing
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
//! }
//!
//! #[derive(Validate, Deserialize)]
//! pub struct Parameter {
//! #[garde(range(min = 5, max = 10))]
//! pub v0: i32,
//! #[garde(length(min = 1, max = 10))]
//! pub v1: String,
//! }
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! # use std::net::SocketAddr;
//! # use axum::Router;
//! # use tokio::net::TcpListener;
//! # let router = Router::new();
//! # #[cfg(feature = "validator")]
//! # let router = router.nest("/validator", validator_example::router());
//! # #[cfg(feature = "garde")]
//! # let router = router.nest("/garde", garde_example::router());
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
//! # axum::serve(listener, router.into_make_service())
//! # .await?;
//! # Ok(())
//! # }
//! ```
use crate::HasValidate;
#[cfg(feature = "validator")]
use crate::HasValidateArgs;
use axum_serde::Yaml;
#[cfg(feature = "validator")]
use validator::ValidateArgs;
impl<T> HasValidate for Yaml<T> {
type Validate = T;
fn get_validate(&self) -> &T {
&self.0
}
}
#[cfg(feature = "validator")]
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Yaml<T> {
type ValidateArgs = T;
fn get_validate_args(&self) -> &Self::ValidateArgs {
&self.0
}
}
#[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(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};
use axum::http::StatusCode;
use axum_serde::Yaml;
use reqwest::RequestBuilder;
use serde::Serialize;
impl<T: ValidTestParameter + Serialize> ValidTest for Yaml<T> {
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/yaml")
.body(
serde_yaml::to_string(&T::valid())
.expect("Failed to serialize parameters to yaml"),
)
}
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
#[derive(Serialize, Default)]
struct ErrorData {
error_field: i32,
}
builder
.header(reqwest::header::CONTENT_TYPE, "application/yaml")
.body(
serde_yaml::to_string(&ErrorData::default())
.expect("Failed to serialize parameters to yaml"),
)
}
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
builder
.header(reqwest::header::CONTENT_TYPE, "application/yaml")
.body(
serde_yaml::to_string(&T::invalid())
.expect("Failed to serialize parameters to yaml"),
)
}
}
}