Skip to content

Commit

Permalink
feat: add support for Cbor
Browse files Browse the repository at this point in the history
  • Loading branch information
gengteng committed Apr 14, 2024
1 parent 6888a7b commit af02a87
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 6 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -27,16 +27,16 @@ 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"
default-features = false
optional = true

[dependencies.axum-serde]
version = "0.3.0"
version = "0.4.1"
optional = true

[dependencies.axum_typed_multipart]
Expand All @@ -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"
Expand All @@ -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 = []
Expand All @@ -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"]
Expand Down
179 changes: 179 additions & 0 deletions src/cbor.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
27 changes: 27 additions & 0 deletions src/garde/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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, ())
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
44 changes: 44 additions & 0 deletions src/validator/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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)
}
}
Loading

0 comments on commit af02a87

Please sign in to comment.