Skip to content

Commit af02a87

Browse files
committed
feat: add support for Cbor
1 parent 6888a7b commit af02a87

File tree

7 files changed

+351
-6
lines changed

7 files changed

+351
-6
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88

99
### Fixed
1010

11+
## axum-valid 0.18.0 (2024-04-14)
12+
13+
### Added
14+
15+
### Changed
16+
17+
* Upgrade validator to 0.18.1.
18+
* Upgrade validify to 1.4.0.
19+
* Upgrade axum-serde to 0.4.1.
20+
* Add support for `Cbor<T>`.
21+
1122
## axum-valid 0.17.0 (2024-03-05)
1223

1324
### Added

Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "axum-valid"
3-
version = "0.17.0"
3+
version = "0.18.0"
44
description = "Provides validation extractors for your Axum application, allowing you to validate data using validator, garde, validify or all of them."
55
authors = ["GengTeng <me@gteng.org>"]
66
license = "MIT"
@@ -27,16 +27,16 @@ features = ["full", "aide"]
2727
[dependencies]
2828
axum = { version = "0.7.3", default-features = false }
2929
garde = { version = "0.18.0", optional = true }
30-
validator = { version = "0.18.0", optional = true }
31-
validify = { version = "1.3.0", optional = true }
30+
validator = { version = "0.18.1", optional = true }
31+
validify = { version = "1.4.0", optional = true }
3232

3333
[dependencies.axum-extra]
3434
version = "0.9.0"
3535
default-features = false
3636
optional = true
3737

3838
[dependencies.axum-serde]
39-
version = "0.3.0"
39+
version = "0.4.1"
4040
optional = true
4141

4242
[dependencies.axum_typed_multipart]
@@ -55,13 +55,14 @@ optional = true
5555
anyhow = "1.0.75"
5656
axum = { version = "0.7.1", features = ["macros"] }
5757
tokio = { version = "1.34.0", features = ["full"] }
58-
reqwest = { version = "0.11.23", features = ["json", "multipart"] }
58+
reqwest = { version = "0.12.3", features = ["json", "multipart"] }
5959
serde = { version = "1.0.195", features = ["derive"] }
6060
validator = { version = "0.18.0", features = ["derive"] }
6161
garde = { version = "0.18.0", features = ["serde", "derive"] }
6262
serde_json = "1.0.108"
6363
serde_yaml = "0.9.27"
6464
quick-xml = { version = "0.31.0", features = ["serialize"] }
65+
ciborium = { version = "0.2.2" }
6566
toml = "0.8.8"
6667
mime = "0.3.17"
6768
prost = "0.12.3"
@@ -83,6 +84,7 @@ yaml = ["dep:axum-serde", "axum-serde/yaml"]
8384
xml = ["dep:axum-serde", "axum-serde/xml"]
8485
toml = ["dep:axum-serde", "axum-serde/toml"]
8586
sonic = ["dep:axum-serde", "axum-serde/sonic"]
87+
cbor = ["dep:axum-serde", "axum-serde/cbor"]
8688
typed_multipart = ["dep:axum_typed_multipart"]
8789
into_json = ["json", "dep:serde", "garde?/serde"]
8890
422 = []
@@ -92,7 +94,7 @@ extra_query = ["extra", "axum-extra/query"]
9294
extra_form = ["extra", "axum-extra/form"]
9395
extra_protobuf = ["extra", "axum-extra/protobuf"]
9496
all_extra_types = ["extra", "typed_header", "extra_typed_path", "extra_query", "extra_form", "extra_protobuf"]
95-
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "toml", "sonic", "all_extra_types", "typed_multipart"]
97+
all_types = ["json", "form", "query", "msgpack", "yaml", "xml", "toml", "sonic", "cbor", "all_extra_types", "typed_multipart"]
9698
full_validator = ["validator", "all_types", "422", "into_json"]
9799
full_garde = ["garde", "all_types", "422", "into_json"]
98100
full_validify = ["validify", "all_types", "422", "into_json"]

src/cbor.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//! # Support for `Cbor<T>`
2+
//!
3+
//! ## Feature
4+
//!
5+
//! Enable the `cbor` feature to use `Valid<Cbor<T>>`.
6+
//!
7+
//! ## Usage
8+
//!
9+
//! 1. Implement `Deserialize` and `Validate` for your data type `T`.
10+
//! 2. In your handler function, use `Valid<Cbor<T>>` as some parameter's type.
11+
//!
12+
//! ## Example
13+
//!
14+
//! ```no_run
15+
//! #[cfg(feature = "validator")]
16+
//! mod validator_example {
17+
//! use axum::routing::post;
18+
//! use axum_serde::Cbor;
19+
//! use axum::Router;
20+
//! use axum_valid::Valid;
21+
//! use serde::Deserialize;
22+
//! use validator::Validate;
23+
//!
24+
//! pub fn router() -> Router {
25+
//! Router::new().route("/cbor", post(handler))
26+
//! }
27+
//!
28+
//! async fn handler(Valid(Cbor(parameter)): Valid<Cbor<Parameter>>) {
29+
//! assert!(parameter.validate().is_ok());
30+
//! // Support automatic dereferencing
31+
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
32+
//! }
33+
//!
34+
//! #[derive(Validate, Deserialize)]
35+
//! pub struct Parameter {
36+
//! #[validate(range(min = 5, max = 10))]
37+
//! pub v0: i32,
38+
//! #[validate(length(min = 1, max = 10))]
39+
//! pub v1: String,
40+
//! }
41+
//! }
42+
//!
43+
//! #[cfg(feature = "garde")]
44+
//! mod garde_example {
45+
//! use axum::routing::post;
46+
//! use axum_serde::Cbor;
47+
//! use axum::Router;
48+
//! use axum_valid::Garde;
49+
//! use serde::Deserialize;
50+
//! use garde::Validate;
51+
//!
52+
//! pub fn router() -> Router {
53+
//! Router::new().route("/cbor", post(handler))
54+
//! }
55+
//!
56+
//! async fn handler(Garde(Cbor(parameter)): Garde<Cbor<Parameter>>) {
57+
//! assert!(parameter.validate(&()).is_ok());
58+
//! // Support automatic dereferencing
59+
//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
60+
//! }
61+
//!
62+
//! #[derive(Validate, Deserialize)]
63+
//! pub struct Parameter {
64+
//! #[garde(range(min = 5, max = 10))]
65+
//! pub v0: i32,
66+
//! #[garde(length(min = 1, max = 10))]
67+
//! pub v1: String,
68+
//! }
69+
//! }
70+
//!
71+
//! # #[tokio::main]
72+
//! # async fn main() -> anyhow::Result<()> {
73+
//! # use std::net::SocketAddr;
74+
//! # use axum::Router;
75+
//! # use tokio::net::TcpListener;
76+
//! # let router = Router::new();
77+
//! # #[cfg(feature = "validator")]
78+
//! # let router = router.nest("/validator", validator_example::router());
79+
//! # #[cfg(feature = "garde")]
80+
//! # let router = router.nest("/garde", garde_example::router());
81+
//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
82+
//! # axum::serve(listener, router.into_make_service())
83+
//! # .await?;
84+
//! # Ok(())
85+
//! # }
86+
//! ```
87+
88+
use crate::HasValidate;
89+
#[cfg(feature = "validator")]
90+
use crate::HasValidateArgs;
91+
use axum_serde::Cbor;
92+
#[cfg(feature = "validator")]
93+
use validator::ValidateArgs;
94+
95+
impl<T> HasValidate for Cbor<T> {
96+
type Validate = T;
97+
fn get_validate(&self) -> &T {
98+
&self.0
99+
}
100+
}
101+
102+
#[cfg(feature = "validator")]
103+
impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Cbor<T> {
104+
type ValidateArgs = T;
105+
fn get_validate_args(&self) -> &Self::ValidateArgs {
106+
&self.0
107+
}
108+
}
109+
110+
#[cfg(feature = "validify")]
111+
impl<T: validify::Modify> crate::HasModify for Cbor<T> {
112+
type Modify = T;
113+
114+
fn get_modify(&mut self) -> &mut Self::Modify {
115+
&mut self.0
116+
}
117+
}
118+
119+
#[cfg(feature = "validify")]
120+
impl<T> crate::PayloadExtractor for Cbor<T> {
121+
type Payload = T;
122+
123+
fn get_payload(self) -> Self::Payload {
124+
self.0
125+
}
126+
}
127+
128+
#[cfg(feature = "validify")]
129+
impl<T: validify::Validify + validify::ValidifyPayload> crate::HasValidify for Cbor<T> {
130+
type Validify = T;
131+
type PayloadExtractor = Cbor<T::Payload>;
132+
fn from_validify(v: Self::Validify) -> Self {
133+
Cbor(v)
134+
}
135+
}
136+
137+
#[cfg(test)]
138+
mod tests {
139+
use crate::tests::{ValidTest, ValidTestParameter};
140+
use axum::http::StatusCode;
141+
use axum_serde::Cbor;
142+
use reqwest::RequestBuilder;
143+
use serde::Serialize;
144+
145+
impl<T: ValidTestParameter + Serialize> ValidTest for Cbor<T> {
146+
const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
147+
148+
fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
149+
let mut vec = Vec::new();
150+
ciborium::ser::into_writer(&T::valid(), &mut vec)
151+
.expect("Failed to serialize parameters to cbor");
152+
builder
153+
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
154+
.body(vec)
155+
}
156+
157+
fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
158+
#[derive(Serialize, Default)]
159+
struct ErrorData {
160+
error_field: i32,
161+
}
162+
let mut vec = Vec::new();
163+
ciborium::ser::into_writer(&ErrorData::default(), &mut vec)
164+
.expect("Failed to serialize parameters to cbor");
165+
builder
166+
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
167+
.body(vec)
168+
}
169+
170+
fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
171+
let mut vec = Vec::new();
172+
ciborium::ser::into_writer(&T::invalid(), &mut vec)
173+
.expect("Failed to serialize parameters to cbor");
174+
builder
175+
.header(reqwest::header::CONTENT_TYPE, "application/cbor")
176+
.body(vec)
177+
}
178+
}
179+
}

src/garde/test.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ async fn test_main() -> anyhow::Result<()> {
148148
#[cfg(feature = "sonic")]
149149
let router = router.route(sonic::route::SONIC, post(sonic::extract_sonic));
150150

151+
#[cfg(feature = "cbor")]
152+
let router = router.route(cbor::route::CBOR, post(cbor::extract_cbor));
153+
151154
let router = router.with_state(MyState::default());
152155

153156
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
@@ -420,6 +423,14 @@ async fn test_main() -> anyhow::Result<()> {
420423
.await?;
421424
}
422425

426+
#[cfg(feature = "cbor")]
427+
{
428+
use axum_serde::Cbor;
429+
test_executor
430+
.execute::<Cbor<ParametersGarde>>(Method::POST, cbor::route::CBOR)
431+
.await?;
432+
}
433+
423434
Ok(())
424435
}
425436

@@ -970,3 +981,19 @@ mod sonic {
970981
validate_again(parameters, ())
971982
}
972983
}
984+
985+
#[cfg(feature = "cbor")]
986+
mod cbor {
987+
use super::{validate_again, ParametersGarde};
988+
use crate::Garde;
989+
use axum::http::StatusCode;
990+
use axum_serde::Cbor;
991+
992+
pub mod route {
993+
pub const CBOR: &str = "/cbor";
994+
}
995+
996+
pub async fn extract_cbor(Garde(Cbor(parameters)): Garde<Cbor<ParametersGarde>>) -> StatusCode {
997+
validate_again(parameters, ())
998+
}
999+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub mod validify;
2323
#[cfg(feature = "yaml")]
2424
pub mod yaml;
2525

26+
#[cfg(feature = "cbor")]
27+
pub mod cbor;
2628
#[cfg(feature = "sonic")]
2729
pub mod sonic;
2830
#[cfg(feature = "toml")]

src/validator/test.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ async fn test_main() -> anyhow::Result<()> {
289289
.route(sonic::route::SONIC, post(sonic::extract_sonic))
290290
.route(sonic::route::SONIC_EX, post(sonic::extract_sonic_ex));
291291

292+
#[cfg(feature = "cbor")]
293+
let router = router
294+
.route(cbor::route::CBOR, post(cbor::extract_cbor))
295+
.route(cbor::route::CBOR_EX, post(cbor::extract_cbor_ex));
296+
292297
let router = router.with_state(state);
293298

294299
let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
@@ -644,6 +649,17 @@ async fn test_main() -> anyhow::Result<()> {
644649
.await?;
645650
}
646651

652+
#[cfg(feature = "cbor")]
653+
{
654+
use axum_serde::Cbor;
655+
test_executor
656+
.execute::<Cbor<Parameters>>(Method::POST, cbor::route::CBOR)
657+
.await?;
658+
test_executor
659+
.execute::<Cbor<Parameters>>(Method::POST, cbor::route::CBOR_EX)
660+
.await?;
661+
}
662+
647663
Ok(())
648664
}
649665

@@ -1514,3 +1530,31 @@ mod sonic {
15141530
validate_again_ex(parameters, &arguments)
15151531
}
15161532
}
1533+
1534+
#[cfg(feature = "cbor")]
1535+
mod cbor {
1536+
use super::{
1537+
validate_again, validate_again_ex, Parameters, ParametersEx,
1538+
ParametersExValidationArguments,
1539+
};
1540+
use crate::{Valid, ValidEx};
1541+
use axum::extract::State;
1542+
use axum::http::StatusCode;
1543+
use axum_serde::Cbor;
1544+
1545+
pub mod route {
1546+
pub const CBOR: &str = "/cbor";
1547+
pub const CBOR_EX: &str = "/cbor_ex";
1548+
}
1549+
1550+
pub async fn extract_cbor(Valid(Cbor(parameters)): Valid<Cbor<Parameters>>) -> StatusCode {
1551+
validate_again(parameters)
1552+
}
1553+
1554+
pub async fn extract_cbor_ex(
1555+
State(arguments): State<ParametersExValidationArguments>,
1556+
ValidEx(Cbor(parameters)): ValidEx<Cbor<ParametersEx>>,
1557+
) -> StatusCode {
1558+
validate_again_ex(parameters, &arguments)
1559+
}
1560+
}

0 commit comments

Comments
 (0)