diff --git a/Cargo.toml b/Cargo.toml index 8092ac4..d10fa98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/README.md b/README.md index 7037ef3..c11ff8b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/src/garde/test.rs b/src/garde/test.rs index f0e8656..994974b 100644 --- a/src/garde/test.rs +++ b/src/garde/test.rs @@ -371,7 +371,7 @@ async fn test_main() -> anyhow::Result<()> { #[cfg(feature = "yaml")] { - use axum_yaml::Yaml; + use axum_serde::Yaml; test_executor .execute::>(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::>(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"; diff --git a/src/lib.rs b/src/lib.rs index 6209bcd..6637cc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/msgpack.rs b/src/msgpack.rs new file mode 100644 index 0000000..ce44639 --- /dev/null +++ b/src/msgpack.rs @@ -0,0 +1,265 @@ +//! # Support for `MsgPack` and `MsgPackRaw` from `axum-serde` +//! +//! ## Feature +//! +//! Enable the `msgpack` feature to use `Valid>` and `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` or `Valid>` 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>) { +//! assert!(parameter.validate().is_ok()); +//! } +//! +//! async fn raw_handler(Valid(MsgPackRaw(parameter)): Valid>) { +//! 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>) { +//! assert!(parameter.validate(&()).is_ok()); +//! } +//! +//! async fn raw_handler(Garde(MsgPackRaw(parameter)): Garde>) { +//! 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 HasValidate for MsgPack { + type Validate = T; + fn get_validate(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "validator")] +impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPack { + type ValidateArgs = T; + fn get_validate_args(&self) -> &Self::ValidateArgs { + &self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasModify for MsgPack { + type Modify = T; + + fn get_modify(&mut self) -> &mut Self::Modify { + &mut self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::PayloadExtractor for MsgPack { + type Payload = T; + + fn get_payload(self) -> Self::Payload { + self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasValidify for MsgPack { + type Validify = T; + type PayloadExtractor = MsgPack; + fn from_validified(v: Self::Validify) -> Self { + MsgPack(v) + } +} + +impl HasValidate for MsgPackRaw { + type Validate = T; + fn get_validate(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "validator")] +impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for MsgPackRaw { + type ValidateArgs = T; + fn get_validate_args(&self) -> &Self::ValidateArgs { + &self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasModify for MsgPackRaw { + type Modify = T; + + fn get_modify(&mut self) -> &mut Self::Modify { + &mut self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::PayloadExtractor for MsgPackRaw { + type Payload = T; + + fn get_payload(self) -> Self::Payload { + self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasValidify for MsgPackRaw { + type Validify = T; + type PayloadExtractor = MsgPackRaw; + 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 ValidTest for MsgPack { + 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, + } + 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 ValidTest for MsgPackRaw { + 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, + } + 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"), + ) + } + } +} diff --git a/src/toml.rs b/src/toml.rs new file mode 100644 index 0000000..363113b --- /dev/null +++ b/src/toml.rs @@ -0,0 +1,174 @@ +//! # Support for `Toml` from `axum-serde` +//! +//! ## Feature +//! +//! Enable the `toml` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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>) { +//! 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 HasValidate for Toml { + type Validate = T; + fn get_validate(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "validator")] +impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Toml { + type ValidateArgs = T; + fn get_validate_args(&self) -> &Self::ValidateArgs { + &self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasModify for Toml { + type Modify = T; + + fn get_modify(&mut self) -> &mut Self::Modify { + &mut self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::PayloadExtractor for Toml { + type Payload = T; + + fn get_payload(self) -> Self::Payload { + self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasValidify for Toml { + type Validify = T; + type PayloadExtractor = Toml; + 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 ValidTest for Toml { + 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"), + ) + } + } +} diff --git a/src/validator/test.rs b/src/validator/test.rs index bdcb44d..a337a1f 100644 --- a/src/validator/test.rs +++ b/src/validator/test.rs @@ -550,7 +550,7 @@ async fn test_main() -> anyhow::Result<()> { #[cfg(feature = "yaml")] { - use axum_yaml::Yaml; + use axum_serde::Yaml; test_executor .execute::>(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::>(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"; diff --git a/src/validify/test.rs b/src/validify/test.rs index 034cf78..eb75418 100644 --- a/src/validify/test.rs +++ b/src/validify/test.rs @@ -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::>(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"; diff --git a/src/xml.rs b/src/xml.rs new file mode 100644 index 0000000..67acbf8 --- /dev/null +++ b/src/xml.rs @@ -0,0 +1,178 @@ +//! # Support for `Xml` from `axum-serde` +//! +//! ## Feature +//! +//! Enable the `Xml` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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>) { +//! 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 HasValidate for Xml { + type Validate = T; + fn get_validate(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "validator")] +impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Xml { + type ValidateArgs = T; + fn get_validate_args(&self) -> &Self::ValidateArgs { + &self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasModify for Xml { + type Modify = T; + + fn get_modify(&mut self) -> &mut Self::Modify { + &mut self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::PayloadExtractor for Xml { + type Payload = T; + + fn get_payload(self) -> Self::Payload { + self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasValidify for Xml { + type Validify = T; + type PayloadExtractor = Xml; + 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 ValidTest for Xml { + 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"), + ) + } + } +} diff --git a/src/yaml.rs b/src/yaml.rs new file mode 100644 index 0000000..88f53b6 --- /dev/null +++ b/src/yaml.rs @@ -0,0 +1,178 @@ +//! # Support for `Yaml` from `axum-serde` +//! +//! ## Feature +//! +//! Enable the `yaml` feature to use `Valid>`. +//! +//! ## Usage +//! +//! 1. Implement `Deserialize` and `Validate` for your data type `T`. +//! 2. In your handler function, use `Valid>` 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>) { +//! 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>) { +//! 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 HasValidate for Yaml { + type Validate = T; + fn get_validate(&self) -> &T { + &self.0 + } +} + +#[cfg(feature = "validator")] +impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Yaml { + type ValidateArgs = T; + fn get_validate_args(&self) -> &Self::ValidateArgs { + &self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasModify for Yaml { + type Modify = T; + + fn get_modify(&mut self) -> &mut Self::Modify { + &mut self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::PayloadExtractor for Yaml { + type Payload = T; + + fn get_payload(self) -> Self::Payload { + self.0 + } +} + +#[cfg(feature = "validify")] +impl crate::HasValidify for Yaml { + type Validify = T; + type PayloadExtractor = Yaml; + 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 ValidTest for Yaml { + 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"), + ) + } + } +}