garde_actix_web/web/
header.rs

1use 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/// Drop in replacement for [actix_web::web::Header](https://docs.rs/actix-web/latest/actix_web/web/struct.Header.html)
11#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut, AsRef, Display, From)]
12pub struct Header<T>(pub T);
13
14impl<T> Header<T> {
15  /// Unwrap into the inner `T` value.
16  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}