axum_valid/extra/protobuf.rs
1//! # Support for `Protobuf<T>` from `axum-extra`
2//!
3//! ## Feature
4//!
5//! Enable the `extra_protobuf` feature to use `Valid<Protobuf<T>>`.
6//!
7//! ## Usage
8//!
9//! 1. Implement `prost::Message` and `Validate` for your data type `T`.
10//! 2. In your handler function, use `Valid<Protobuf<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_extra::protobuf::Protobuf;
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("/protobuf", post(handler))
26//! }
27//!
28//! async fn handler(Valid(Protobuf(parameter)): Valid<Protobuf<Parameter>>) {
29//! assert!(parameter.validate().is_ok());
30//! // Support automatic dereferencing
31//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
32//! }
33//!
34//! #[derive(Validate, prost::Message)]
35//! pub struct Parameter {
36//! #[validate(range(min = 5, max = 10))]
37//! #[prost(int32, tag = "1")]
38//! pub v0: i32,
39//! #[validate(length(min = 1, max = 10))]
40//! #[prost(string, tag = "2")]
41//! pub v1: String,
42//! }
43//! }
44//!
45//! #[cfg(feature = "garde")]
46//! mod garde_example {
47//! use axum::routing::post;
48//! use axum::Router;
49//! use axum_extra::protobuf::Protobuf;
50//! use axum_valid::Garde;
51//! use serde::Deserialize;
52//! use garde::Validate;
53//!
54//! pub fn router() -> Router {
55//! Router::new().route("/protobuf", post(handler))
56//! }
57//!
58//! async fn handler(Garde(Protobuf(parameter)): Garde<Protobuf<Parameter>>) {
59//! assert!(parameter.validate_with(&()).is_ok());
60//! // Support automatic dereferencing
61//! println!("v0 = {}, v1 = {}", parameter.v0, parameter.v1);
62//! }
63//!
64//! #[derive(Validate, prost::Message)]
65//! pub struct Parameter {
66//! #[garde(range(min = 5, max = 10))]
67//! #[prost(int32, tag = "1")]
68//! pub v0: i32,
69//! #[garde(length(min = 1, max = 10))]
70//! #[prost(string, tag = "2")]
71//! pub v1: String,
72//! }
73//! }
74//!
75//! # #[tokio::main]
76//! # async fn main() -> anyhow::Result<()> {
77//! # use std::net::SocketAddr;
78//! # use axum::Router;
79//! # use tokio::net::TcpListener;
80//! # let router = Router::new();
81//! # #[cfg(feature = "validator")]
82//! # let router = router.nest("/validator", validator_example::router());
83//! # #[cfg(feature = "garde")]
84//! # let router = router.nest("/garde", garde_example::router());
85//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
86//! # axum::serve(listener, router.into_make_service())
87//! # .await?;
88//! # Ok(())
89//! # }
90//! ```
91
92use crate::HasValidate;
93#[cfg(feature = "validator")]
94use crate::HasValidateArgs;
95use axum_extra::protobuf::Protobuf;
96#[cfg(feature = "validator")]
97use validator::ValidateArgs;
98
99impl<T> HasValidate for Protobuf<T> {
100 type Validate = T;
101 fn get_validate(&self) -> &T {
102 &self.0
103 }
104}
105
106#[cfg(feature = "validator")]
107impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for Protobuf<T> {
108 type ValidateArgs = T;
109 fn get_validate_args(&self) -> &Self::ValidateArgs {
110 &self.0
111 }
112}
113
114#[cfg(feature = "validify")]
115impl<T: validify::Modify> crate::HasModify for Protobuf<T> {
116 type Modify = T;
117
118 fn get_modify(&mut self) -> &mut Self::Modify {
119 &mut self.0
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{ValidTest, ValidTestParameter};
126 use axum::http::StatusCode;
127 use axum_extra::protobuf::Protobuf;
128 use reqwest::RequestBuilder;
129
130 impl<T: ValidTestParameter + prost::Message> ValidTest for Protobuf<T> {
131 const ERROR_STATUS_CODE: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
132
133 fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
134 builder.body(T::valid().encode_to_vec())
135 }
136
137 fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
138 builder.body("invalid protobuf")
139 }
140
141 fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
142 builder.body(T::invalid().encode_to_vec())
143 }
144 }
145}