garde_actix_web/web/
header.rs1use crate::validate_for_request;
2use actix_http::header::Header as ParseHeader;
3use actix_web::dev::Payload;
4use actix_web::error::Error;
5use actix_web::{FromRequest, HttpRequest};
6use derive_more::{AsRef, Deref, DerefMut, Display, From};
7use futures::future::{Ready, err, ok};
8use garde::Validate;
9
10#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut, AsRef, Display, From)]
12pub struct Header<T>(pub T);
13
14impl<T> Header<T> {
15 pub fn into_inner(self) -> T {
17 self.0
18 }
19}
20
21impl<T> FromRequest for Header<T>
22where
23 T: ParseHeader + Validate + 'static,
24 T::Context: Default,
25{
26 type Error = Error;
27 type Future = Ready<Result<Self, Self::Error>>;
28
29 #[inline]
30 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
31 match ParseHeader::parse(req) {
32 Ok(header) => match validate_for_request(header, &req.clone()) {
33 Ok(header) => ok(Header(header)),
34 Err(e) => err(e.into()),
35 },
36 Err(e) => err(e.into()),
37 }
38 }
39}
40
41#[cfg(test)]
42mod test {
43 use crate::web::Header;
44 use actix_http::error::ParseError;
45 use actix_http::header::Header as ParseHeader;
46 use actix_http::header::{HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue};
47 use actix_http::{HttpMessage, StatusCode};
48 use actix_test::TestRequest;
49 use actix_web::FromRequest;
50 use garde::Validate;
51 use serde::{Deserialize, Serialize};
52
53 #[derive(Debug, PartialEq, Validate, Serialize, Deserialize)]
54 struct HeaderData {
55 #[garde(range(min = 18, max = 28))]
56 age: u8,
57 }
58
59 impl TryIntoHeaderValue for HeaderData {
60 type Error = InvalidHeaderValue;
61
62 fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
63 HeaderValue::try_from(self.age.to_string())
64 }
65 }
66
67 impl ParseHeader for HeaderData {
68 fn name() -> HeaderName {
69 HeaderName::from_static("header-data")
70 }
71
72 fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError> {
73 msg
74 .headers()
75 .get(Self::name())
76 .ok_or(ParseError::Header)
77 .and_then(|v| v.to_str().map_err(|_| ParseError::Header))
78 .and_then(|v| v.parse::<u8>().map_err(|_| ParseError::Header))
79 .map(|v| HeaderData { age: v })
80 }
81 }
82
83 #[tokio::test]
84 async fn test_simple_header_validation() {
85 let (req, mut pl) = TestRequest::default()
86 .insert_header(("header-data", HeaderData { age: 10 }))
87 .to_http_parts();
88
89 #[allow(clippy::unwrap_used)]
90 let res = Header::<HeaderData>::from_request(&req, &mut pl).await.unwrap_err();
91 assert_eq!(res.as_response_error().status_code(), StatusCode::BAD_REQUEST);
92
93 let (req, mut pl) = TestRequest::default()
94 .insert_header(("header-data", HeaderData { age: 25 }))
95 .to_http_parts();
96
97 let res = Header::<HeaderData>::from_request(&req, &mut pl).await;
98 assert!(res.is_ok());
99 }
100}