feat: add support for Cbor
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -8,6 +8,17 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
## axum-valid 0.18.0 (2024-04-14)
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgrade validator to 0.18.1.
|
||||
* Upgrade validify to 1.4.0.
|
||||
* Upgrade axum-serde to 0.4.1.
|
||||
* Add support for `Cbor<T>`.
|
||||
|
||||
## axum-valid 0.17.0 (2024-03-05)
|
||||
|
||||
### Added
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "axum-valid"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
description = "Provides validation extractors for your Axum application, allowing you to validate data using validator, garde, validify or all of them."
|
||||
authors = ["GengTeng <me@gteng.org>"]
|
||||
license = "MIT"
|
||||
@@ -27,8 +27,8 @@ features = ["full", "aide"]
|
||||
[dependencies]
|
||||
axum = { version = "0.7.3", default-features = false }
|
||||
garde = { version = "0.18.0", optional = true }
|
||||
validator = { version = "0.18.0", optional = true }
|
||||
validify = { version = "1.3.0", optional = true }
|
||||
validator = { version = "0.18.1", optional = true }
|
||||
validify = { version = "1.4.0", optional = true }
|
||||
|
||||
[dependencies.axum-extra]
|
||||
version = "0.9.0"
|
||||
@@ -36,7 +36,7 @@ default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.axum-serde]
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.axum_typed_multipart]
|
||||
@@ -55,13 +55,14 @@ optional = true
|
||||
anyhow = "1.0.75"
|
||||
axum = { version = "0.7.1", features = ["macros"] }
|
||||
tokio = { version = "1.34.0", features = ["full"] }
|
||||
reqwest = { version = "0.11.23", features = ["json", "multipart"] }
|
||||
reqwest = { version = "0.12.3", features = ["json", "multipart"] }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
validator = { version = "0.18.0", features = ["derive"] }
|
||||
garde = { version = "0.18.0", features = ["serde", "derive"] }
|
||||
serde_json = "1.0.108"
|
||||
serde_yaml = "0.9.27"
|
||||
quick-xml = { version = "0.31.0", features = ["serialize"] }
|
||||
ciborium = { version = "0.2.2" }
|
||||
toml = "0.8.8"
|
||||
mime = "0.3.17"
|
||||
prost = "0.12.3"
|
||||
@@ -83,6 +84,7 @@ yaml = ["dep:axum-serde", "axum-serde/yaml"]
|
||||
xml = ["dep:axum-serde", "axum-serde/xml"]
|
||||
toml = ["dep:axum-serde", "axum-serde/toml"]
|
||||
sonic = ["dep:axum-serde", "axum-serde/sonic"]
|
||||
cbor = ["dep:axum-serde", "axum-serde/cbor"]
|
||||
typed_multipart = ["dep:axum_typed_multipart"]
|
||||
into_json = ["json", "dep:serde", "garde?/serde"]
|
||||
422 = []
|
||||
@@ -92,7 +94,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", "msgpack", "yaml", "xml", "toml", "sonic", "all_extra_types", "typed_multipart"]
|
||||
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "toml", "sonic", "cbor", "all_extra_types", "typed_multipart"]
|
||||
full_validator = ["validator", "all_types", "422", "into_json"]
|
||||
full_garde = ["garde", "all_types", "422", "into_json"]
|
||||
full_validify = ["validify", "all_types", "422", "into_json"]
|
||||
|
||||
179
src/cbor.rs
Normal file
179
src/cbor.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
//! # Support for `Cbor<T>`
|
||||
//!
|
||||
//! ## Feature
|
||||
//!
|
||||
//! Enable the `cbor` feature to use `Valid<Cbor<T>>`.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
|
||||
//! 2. In your handler function, use `Valid<Cbor<T>>` as some parameter's type.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! #[cfg(feature = "validator")]
|
||||
//! mod validator_example {
|
||||
//! use axum::routing::post;
|
||||
//! use axum_serde::Cbor;
|
||||
//! use axum::Router;
|
||||
//! use axum_valid::Valid;
|
||||
//! use serde::Deserialize;
|
||||
//! use validator::Validate;
|
||||
//!
|
||||
//! pub fn router() -> Router {
|
||||
//! Router::new().route("/cbor", post(handler))
|
||||
//! }
|
||||
//!
|
||||
//! async fn handler(Valid(Cbor(parameter)): Valid<Cbor<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::Cbor;
|
||||
//! use axum::Router;
|
||||
//! use axum_valid::Garde;
|
||||
//! use serde::Deserialize;
|
||||
//! use garde::Validate;
|
||||
//!
|
||||
//! pub fn router() -> Router {
|
||||
//! Router::new().route("/cbor", post(handler))
|
||||
//! }
|
||||
//!
|
||||
//! async fn handler(Garde(Cbor(parameter)): Garde<Cbor<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::Cbor;
|
||||
#[cfg(feature = "validator")]
|
||||
use validator::ValidateArgs;
|
||||
|
||||
impl<T> HasValidate for Cbor<T> {
|
||||
type Validate = T;
|
||||
fn get_validate(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validator")]
|
||||
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Cbor<T> {
|
||||
type ValidateArgs = T;
|
||||
fn get_validate_args(&self) -> &Self::ValidateArgs {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Modify> crate::HasModify for Cbor<T> {
|
||||
type Modify = T;
|
||||
|
||||
fn get_modify(&mut self) -> &mut Self::Modify {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T> crate::PayloadExtractor for Cbor<T> {
|
||||
type Payload = T;
|
||||
|
||||
fn get_payload(self) -> Self::Payload {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "validify")]
|
||||
impl<T: validify::Validify + validify::ValidifyPayload> crate::HasValidify for Cbor<T> {
|
||||
type Validify = T;
|
||||
type PayloadExtractor = Cbor<T::Payload>;
|
||||
fn from_validify(v: Self::Validify) -> Self {
|
||||
Cbor(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{ValidTest, ValidTestParameter};
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Cbor;
|
||||
use reqwest::RequestBuilder;
|
||||
use serde::Serialize;
|
||||
|
||||
impl<T: ValidTestParameter + Serialize> ValidTest for Cbor<T> {
|
||||
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
|
||||
|
||||
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
let mut vec = Vec::new();
|
||||
ciborium::ser::into_writer(&T::valid(), &mut vec)
|
||||
.expect("Failed to serialize parameters to cbor");
|
||||
builder
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
|
||||
.body(vec)
|
||||
}
|
||||
|
||||
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
#[derive(Serialize, Default)]
|
||||
struct ErrorData {
|
||||
error_field: i32,
|
||||
}
|
||||
let mut vec = Vec::new();
|
||||
ciborium::ser::into_writer(&ErrorData::default(), &mut vec)
|
||||
.expect("Failed to serialize parameters to cbor");
|
||||
builder
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
|
||||
.body(vec)
|
||||
}
|
||||
|
||||
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
|
||||
let mut vec = Vec::new();
|
||||
ciborium::ser::into_writer(&T::invalid(), &mut vec)
|
||||
.expect("Failed to serialize parameters to cbor");
|
||||
builder
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
|
||||
.body(vec)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,9 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
#[cfg(feature = "sonic")]
|
||||
let router = router.route(sonic::route::SONIC, post(sonic::extract_sonic));
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
let router = router.route(cbor::route::CBOR, post(cbor::extract_cbor));
|
||||
|
||||
let router = router.with_state(MyState::default());
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
@@ -420,6 +423,14 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
use axum_serde::Cbor;
|
||||
test_executor
|
||||
.execute::<Cbor<ParametersGarde>>(Method::POST, cbor::route::CBOR)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -970,3 +981,19 @@ mod sonic {
|
||||
validate_again(parameters, ())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
mod cbor {
|
||||
use super::{validate_again, ParametersGarde};
|
||||
use crate::Garde;
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Cbor;
|
||||
|
||||
pub mod route {
|
||||
pub const CBOR: &str = "/cbor";
|
||||
}
|
||||
|
||||
pub async fn extract_cbor(Garde(Cbor(parameters)): Garde<Cbor<ParametersGarde>>) -> StatusCode {
|
||||
validate_again(parameters, ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ pub mod validify;
|
||||
#[cfg(feature = "yaml")]
|
||||
pub mod yaml;
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
pub mod cbor;
|
||||
#[cfg(feature = "sonic")]
|
||||
pub mod sonic;
|
||||
#[cfg(feature = "toml")]
|
||||
|
||||
@@ -289,6 +289,11 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.route(sonic::route::SONIC, post(sonic::extract_sonic))
|
||||
.route(sonic::route::SONIC_EX, post(sonic::extract_sonic_ex));
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
let router = router
|
||||
.route(cbor::route::CBOR, post(cbor::extract_cbor))
|
||||
.route(cbor::route::CBOR_EX, post(cbor::extract_cbor_ex));
|
||||
|
||||
let router = router.with_state(state);
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
@@ -644,6 +649,17 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
use axum_serde::Cbor;
|
||||
test_executor
|
||||
.execute::<Cbor<Parameters>>(Method::POST, cbor::route::CBOR)
|
||||
.await?;
|
||||
test_executor
|
||||
.execute::<Cbor<Parameters>>(Method::POST, cbor::route::CBOR_EX)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1514,3 +1530,31 @@ mod sonic {
|
||||
validate_again_ex(parameters, &arguments)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
mod cbor {
|
||||
use super::{
|
||||
validate_again, validate_again_ex, Parameters, ParametersEx,
|
||||
ParametersExValidationArguments,
|
||||
};
|
||||
use crate::{Valid, ValidEx};
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Cbor;
|
||||
|
||||
pub mod route {
|
||||
pub const CBOR: &str = "/cbor";
|
||||
pub const CBOR_EX: &str = "/cbor_ex";
|
||||
}
|
||||
|
||||
pub async fn extract_cbor(Valid(Cbor(parameters)): Valid<Cbor<Parameters>>) -> StatusCode {
|
||||
validate_again(parameters)
|
||||
}
|
||||
|
||||
pub async fn extract_cbor_ex(
|
||||
State(arguments): State<ParametersExValidationArguments>,
|
||||
ValidEx(Cbor(parameters)): ValidEx<Cbor<ParametersEx>>,
|
||||
) -> StatusCode {
|
||||
validate_again_ex(parameters, &arguments)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +383,22 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
post(sonic::extract_sonic_validified_by_ref),
|
||||
);
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
let router = router
|
||||
.route(cbor::route::CBOR, post(cbor::extract_cbor))
|
||||
.route(
|
||||
cbor::route::CBOR_MODIFIED,
|
||||
post(cbor::extract_cbor_modified),
|
||||
)
|
||||
.route(
|
||||
cbor::route::CBOR_VALIDIFIED,
|
||||
post(cbor::extract_cbor_validified),
|
||||
)
|
||||
.route(
|
||||
cbor::route::CBOR_VALIDIFIED_BY_REF,
|
||||
post(cbor::extract_cbor_validified_by_ref),
|
||||
);
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
|
||||
let server_addr = listener.local_addr()?;
|
||||
let server = axum::serve(listener, router.into_make_service());
|
||||
@@ -1066,6 +1082,31 @@ async fn test_main() -> anyhow::Result<()> {
|
||||
.await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
use axum_serde::Cbor;
|
||||
|
||||
// Validated
|
||||
test_executor
|
||||
.execute::<Cbor<ParametersValidify>>(Method::POST, cbor::route::CBOR)
|
||||
.await?;
|
||||
// Modified
|
||||
test_executor
|
||||
.execute_modified::<Cbor<ParametersValidify>>(Method::POST, cbor::route::CBOR_MODIFIED)
|
||||
.await?;
|
||||
// Validified
|
||||
test_executor
|
||||
.execute_validified::<Cbor<ParametersValidify>>(
|
||||
Method::POST,
|
||||
cbor::route::CBOR_VALIDIFIED,
|
||||
)
|
||||
.await?;
|
||||
// ValidifiedByRef
|
||||
test_executor
|
||||
.execute::<Cbor<ParametersValidify>>(Method::POST, cbor::route::CBOR_VALIDIFIED_BY_REF)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2157,3 +2198,42 @@ mod sonic {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cbor")]
|
||||
mod cbor {
|
||||
use super::{check_modified, check_validated, check_validified, ParametersValidify};
|
||||
use crate::{Modified, Validated, Validified, ValidifiedByRef};
|
||||
use axum::http::StatusCode;
|
||||
use axum_serde::Cbor;
|
||||
|
||||
pub mod route {
|
||||
pub const CBOR: &str = "/cbor";
|
||||
pub const CBOR_MODIFIED: &str = "/cbor_modified";
|
||||
pub const CBOR_VALIDIFIED: &str = "/cbor_validified";
|
||||
pub const CBOR_VALIDIFIED_BY_REF: &str = "/cbor_validified_by_ref";
|
||||
}
|
||||
|
||||
pub async fn extract_cbor(
|
||||
Validated(Cbor(parameters)): Validated<Cbor<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validated(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_cbor_modified(
|
||||
Modified(Cbor(parameters)): Modified<Cbor<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_modified(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_cbor_validified(
|
||||
Validified(Cbor(parameters)): Validified<Cbor<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
|
||||
pub async fn extract_cbor_validified_by_ref(
|
||||
ValidifiedByRef(Cbor(parameters)): ValidifiedByRef<Cbor<ParametersValidify>>,
|
||||
) -> StatusCode {
|
||||
check_validified(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user