feat: add support for Cbor

This commit is contained in:
gengteng
2024-04-14 21:41:16 +08:00
parent 6888a7bbdf
commit af02a87ef1
7 changed files with 351 additions and 6 deletions

179
src/cbor.rs Normal file
View 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)
}
}
}

View File

@@ -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, ())
}
}

View File

@@ -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")]

View File

@@ -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)
}
}

View File

@@ -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(&parameters)
}
}
#[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(&parameters)
}
pub async fn extract_cbor_modified(
Modified(Cbor(parameters)): Modified<Cbor<ParametersValidify>>,
) -> StatusCode {
check_modified(&parameters)
}
pub async fn extract_cbor_validified(
Validified(Cbor(parameters)): Validified<Cbor<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
pub async fn extract_cbor_validified_by_ref(
ValidifiedByRef(Cbor(parameters)): ValidifiedByRef<Cbor<ParametersValidify>>,
) -> StatusCode {
check_validified(&parameters)
}
}