garde_actix_web/web/
form.rs1use 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#[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#[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, 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}