axum_valid/typed_header.rs
1//! # Support for `TypedHeader<T>`
2//!
3//! ## Feature
4//!
5//! Enable the `typed_header` feature to use `Valid<TypedHeader<T>>`.
6//!
7//! ## Usage
8//!
9//! 1. Implement `Header` and `Validate` for your data type `T`.
10//! 2. In your handler function, use `Valid<TypedHeader<T>>` as some parameter's type.
11//!
12//! ## Example
13//!
14//! ```no_run
15//! #[cfg(feature = "validator")]
16//! mod validator_example {
17//! use axum_extra::headers::{Error, Header, HeaderValue};
18//! use axum_extra::typed_header::TypedHeader;
19//! use axum::http::HeaderName;
20//! use axum::routing::post;
21//! use axum::Router;
22//! use axum_valid::Valid;
23//! use validator::Validate;
24//!
25//! pub fn router() -> Router {
26//! Router::new().route("/typed_header", post(handler))
27//! }
28//!
29//! async fn handler(Valid(TypedHeader(parameter)): Valid<TypedHeader<Parameter>>) {
30//! assert!(parameter.validate().is_ok());
31//! }
32//!
33//! #[derive(Validate)]
34//! pub struct Parameter {
35//! #[validate(range(min = 5, max = 10))]
36//! pub v0: i32,
37//! #[validate(length(min = 1, max = 10))]
38//! pub v1: String,
39//! }
40//!
41//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header");
42//!
43//! impl Header for Parameter {
44//! fn name() -> &'static HeaderName {
45//! &HEADER_NAME
46//! }
47//!
48//! fn decode<'i, I>(_values: &mut I) -> Result<Self, Error>
49//! where
50//! Self: Sized,
51//! I: Iterator<Item = &'i HeaderValue>,
52//! {
53//! todo!()
54//! }
55//!
56//! fn encode<E: Extend<HeaderValue>>(&self, _values: &mut E) {
57//! todo!()
58//! }
59//! }
60//! }
61//!
62//! #[cfg(feature = "garde")]
63//! mod garde_example {
64//! use axum_extra::headers::{Error, Header, HeaderValue};
65//! use axum_extra::typed_header::TypedHeader;
66//! use axum::http::HeaderName;
67//! use axum::routing::post;
68//! use axum::Router;
69//! use axum_valid::Garde;
70//! use garde::Validate;
71//!
72//! pub fn router() -> Router {
73//! Router::new().route("/typed_header", post(handler))
74//! }
75//!
76//! async fn handler(Garde(TypedHeader(parameter)): Garde<TypedHeader<Parameter>>) {
77//! assert!(parameter.validate_with(&()).is_ok());
78//! }
79//!
80//! #[derive(Validate)]
81//! pub struct Parameter {
82//! #[garde(range(min = 5, max = 10))]
83//! pub v0: i32,
84//! #[garde(length(min = 1, max = 10))]
85//! pub v1: String,
86//! }
87//!
88//! static HEADER_NAME: HeaderName = HeaderName::from_static("my-header");
89//!
90//! impl Header for Parameter {
91//! fn name() -> &'static HeaderName {
92//! &HEADER_NAME
93//! }
94//!
95//! fn decode<'i, I>(_values: &mut I) -> Result<Self, Error>
96//! where
97//! Self: Sized,
98//! I: Iterator<Item = &'i HeaderValue>,
99//! {
100//! todo!()
101//! }
102//!
103//! fn encode<E: Extend<HeaderValue>>(&self, _values: &mut E) {
104//! todo!()
105//! }
106//! }
107//! }
108//!
109//! # #[tokio::main]
110//! # async fn main() -> anyhow::Result<()> {
111//! # use std::net::SocketAddr;
112//! # use axum::Router;
113//! # use tokio::net::TcpListener;
114//! # let router = Router::new();
115//! # #[cfg(feature = "validator")]
116//! # let router = router.nest("/validator", validator_example::router());
117//! # #[cfg(feature = "garde")]
118//! # let router = router.nest("/garde", garde_example::router());
119//! # let listener = TcpListener::bind(&SocketAddr::from(([0u8, 0, 0, 0], 0u16))).await?;
120//! # axum::serve(listener, router.into_make_service())
121//! # .await?;
122//! # Ok(())
123//! # }
124//! ```
125
126use crate::HasValidate;
127#[cfg(feature = "validator")]
128use crate::HasValidateArgs;
129use axum_extra::typed_header::TypedHeader;
130#[cfg(feature = "validator")]
131use validator::ValidateArgs;
132
133impl<T> HasValidate for TypedHeader<T> {
134 type Validate = T;
135 fn get_validate(&self) -> &T {
136 &self.0
137 }
138}
139
140#[cfg(feature = "validator")]
141impl<'v, T: ValidateArgs<'v>> HasValidateArgs<'v> for TypedHeader<T> {
142 type ValidateArgs = T;
143 fn get_validate_args(&self) -> &Self::ValidateArgs {
144 &self.0
145 }
146}
147
148#[cfg(feature = "validify")]
149impl<T: validify::Modify> crate::HasModify for TypedHeader<T> {
150 type Modify = T;
151
152 fn get_modify(&mut self) -> &mut Self::Modify {
153 &mut self.0
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use crate::tests::{ValidTest, ValidTestParameter};
160 use axum::http::StatusCode;
161 use axum_extra::headers::Header;
162 use axum_extra::typed_header::TypedHeader;
163 use reqwest::header::{HeaderMap, HeaderValue};
164 use reqwest::RequestBuilder;
165
166 impl<T: ValidTestParameter + Header + Clone> ValidTest for TypedHeader<T> {
167 const ERROR_STATUS_CODE: StatusCode = StatusCode::BAD_REQUEST;
168
169 fn set_valid_request(builder: RequestBuilder) -> RequestBuilder {
170 let mut vec = Vec::new();
171 T::valid().encode(&mut vec);
172 let hv = vec.pop().expect("get header value");
173 let mut headers = HeaderMap::default();
174 headers.insert(
175 T::name().as_str(),
176 HeaderValue::from_bytes(hv.as_bytes()).expect("build header value"),
177 );
178 builder.headers(headers)
179 }
180
181 fn set_error_request(builder: RequestBuilder) -> RequestBuilder {
182 builder
183 }
184
185 fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder {
186 let mut vec = Vec::new();
187 T::invalid().encode(&mut vec);
188 let hv = vec.pop().expect("get header value");
189 let mut headers = HeaderMap::default();
190 headers.insert(
191 T::name().as_str(),
192 HeaderValue::from_bytes(hv.as_bytes()).expect("build header value"),
193 );
194 builder.headers(headers)
195 }
196 }
197}