garde_actix_web/web/
form.rs

1use actix_http::Payload;
2use actix_web::web::UrlEncoded;
3use actix_web::{Error, FromRequest, HttpRequest, web};
4use serde::{Serialize, de::DeserializeOwned};
5use std::rc::Rc;
6
7use crate::validate_for_request;
8use derive_more::{AsRef, Deref, DerefMut, Display, From};
9use futures::FutureExt;
10use futures::future::LocalBoxFuture;
11use garde::Validate;
12
13/// Drop in replacement for [actix_web::web::Form](https://docs.rs/actix-web/latest/actix_web/web/struct.Form.html)
14#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Deref, DerefMut, AsRef, Display, From)]
15pub struct Form<T>(pub T);
16
17impl<T> Form<T> {
18  pub fn into_inner(self) -> T {
19    self.0
20  }
21}
22
23impl<T> Serialize for Form<T>
24where
25  T: Serialize,
26{
27  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28  where
29    S: serde::Serializer,
30  {
31    self.0.serialize(serializer)
32  }
33}
34
35impl<T> FromRequest for Form<T>
36where
37  T: DeserializeOwned + Validate + 'static,
38  T::Context: Default,
39{
40  type Error = Error;
41  type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
42
43  #[inline]
44  fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
45    let req_copy = req.clone();
46    let req_copy2 = req.clone();
47
48    let FormConfig { limit, err_handler } = FormConfig::from_req(req).clone();
49
50    UrlEncoded::new(req, payload)
51      .limit(limit)
52      .map(move |res: Result<T, _>| match res {
53        Ok(data) => {
54          let req = req_copy;
55          validate_for_request(data, &req)
56        }
57        Err(e) => Err(e.into()),
58      })
59      .map(move |res| match res {
60        Err(err) => {
61          if let Some(err_handler) = err_handler.as_ref() {
62            Err((*err_handler)(err, &req_copy2))
63          } else {
64            Err(err.into())
65          }
66        }
67        Ok(data) => Ok(Form(data)),
68      })
69      .boxed_local()
70  }
71}
72
73type FormErrHandler = Option<Rc<dyn Fn(crate::error::Error, &HttpRequest) -> Error>>;
74
75/// Replacement for [actix_web::web::FormConfig](https://docs.rs/actix-web/latest/actix_web/web/struct.FormConfig.html)
76/// Error handler must map from an `garde_actix_web::error::Error`
77#[derive(Clone)]
78pub struct FormConfig {
79  limit: usize,
80  err_handler: FormErrHandler,
81}
82
83impl FormConfig {
84  pub fn limit(mut self, limit: usize) -> Self {
85    self.limit = limit;
86    self
87  }
88
89  pub fn error_handler<F>(mut self, f: F) -> Self
90  where
91    F: Fn(crate::error::Error, &HttpRequest) -> Error + 'static,
92  {
93    self.err_handler = Some(Rc::new(f));
94    self
95  }
96
97  fn from_req(req: &HttpRequest) -> &Self {
98    req
99      .app_data::<Self>()
100      .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
101      .unwrap_or(&DEFAULT_CONFIG)
102  }
103}
104
105const DEFAULT_CONFIG: FormConfig = FormConfig {
106  limit: 16_384, // 2^14 bytes (~16kB)
107  err_handler: None,
108};
109
110impl Default for FormConfig {
111  fn default() -> Self {
112    DEFAULT_CONFIG
113  }
114}
115
116#[cfg(test)]
117mod test {
118  use crate::web::{Form, FormConfig};
119  use actix_http::StatusCode;
120  use actix_web::error::InternalError;
121  use actix_web::test::{TestRequest, call_service, init_service};
122  use actix_web::web::{post, resource};
123  use actix_web::{App, HttpResponse};
124  use garde::Validate;
125  use serde::{Deserialize, Serialize};
126
127  #[derive(Debug, PartialEq, Validate, Serialize, Deserialize)]
128  struct FormData {
129    #[garde(range(min = 18, max = 28))]
130    age: u8,
131  }
132
133  #[derive(Debug, PartialEq, Validate, Serialize, Deserialize)]
134  #[garde(context(NumberContext))]
135  struct FormDataWithContext {
136    #[garde(custom(is_big_enough))]
137    age: u8,
138  }
139
140  #[derive(Default, Debug)]
141  struct NumberContext {
142    min: u8,
143  }
144
145  fn is_big_enough(value: &u8, context: &NumberContext) -> garde::Result {
146    if value < &context.min {
147      return Err(garde::Error::new("Number is too low"));
148    }
149    Ok(())
150  }
151
152  async fn test_handler(_: Form<FormData>) -> HttpResponse {
153    HttpResponse::Ok().finish()
154  }
155
156  async fn test_handler_with_context(_: Form<FormDataWithContext>) -> HttpResponse {
157    HttpResponse::Ok().finish()
158  }
159
160  #[tokio::test]
161  async fn test_simple_form_validation() {
162    let app = init_service(App::new().service(resource("/").route(post().to(test_handler)))).await;
163
164    let req = TestRequest::post()
165      .uri("/")
166      .set_form(&FormData { age: 24 })
167      .to_request();
168    let resp = call_service(&app, req).await;
169    assert_eq!(resp.status(), StatusCode::OK);
170
171    let req = TestRequest::post()
172      .uri("/")
173      .set_form(&FormData { age: 30 })
174      .to_request();
175    let resp = call_service(&app, req).await;
176    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
177  }
178
179  #[tokio::test]
180  async fn test_form_validation_custom_config() {
181    let app = init_service(
182      App::new()
183        .app_data(
184          FormConfig::default()
185            .error_handler(|err, _req| InternalError::from_response(err, HttpResponse::Conflict().finish()).into()),
186        )
187        .service(resource("/").route(post().to(test_handler))),
188    )
189    .await;
190
191    let req = TestRequest::post()
192      .uri("/")
193      .set_form(&FormData { age: 24 })
194      .to_request();
195    let resp = call_service(&app, req).await;
196    assert_eq!(resp.status(), StatusCode::OK);
197
198    let req = TestRequest::post()
199      .uri("/")
200      .set_form(&FormData { age: 30 })
201      .to_request();
202    let resp = call_service(&app, req).await;
203    assert_eq!(resp.status(), StatusCode::CONFLICT);
204  }
205
206  #[tokio::test]
207  async fn test_form_validation_with_context() {
208    let number_context = NumberContext { min: 25 };
209    let app = init_service(
210      App::new()
211        .app_data(number_context)
212        .service(resource("/").route(post().to(test_handler_with_context))),
213    )
214    .await;
215
216    let req = TestRequest::post()
217      .uri("/")
218      .set_form(&FormData { age: 24 })
219      .to_request();
220    let resp = call_service(&app, req).await;
221    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
222
223    let req = TestRequest::post()
224      .uri("/")
225      .set_form(&FormData { age: 30 })
226      .to_request();
227    let resp = call_service(&app, req).await;
228    assert_eq!(resp.status(), StatusCode::OK);
229  }
230
231  #[tokio::test]
232  async fn test_form_validation_with_missing_context() {
233    let app = init_service(App::new().service(resource("/").route(post().to(test_handler_with_context)))).await;
234
235    let req = TestRequest::post()
236      .uri("/")
237      .set_form(&FormData { age: 24 })
238      .to_request();
239    let resp = call_service(&app, req).await;
240    assert_eq!(resp.status(), StatusCode::OK);
241
242    let req = TestRequest::post()
243      .uri("/")
244      .set_form(&FormData { age: 30 })
245      .to_request();
246    let resp = call_service(&app, req).await;
247    assert_eq!(resp.status(), StatusCode::OK);
248  }
249}