actix_web_validator/form.rs
1use actix_http::Payload;
2use actix_web::{dev::UrlEncoded, FromRequest, HttpRequest};
3use futures::future::LocalBoxFuture;
4use futures::FutureExt;
5use serde::de::DeserializeOwned;
6use std::{ops::Deref, rc::Rc};
7use validator::Validate;
8
9use crate::Error;
10
11/// Form can be used for extracting typed information and validation
12/// from request's form data.
13///
14/// To extract and typed information from request's form data, the type `T` must
15/// implement the `Deserialize` trait from *serde*
16/// and `Validate` trait from *validator* crate.
17///
18/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction
19/// process.
20///
21/// ## Example
22///
23/// ```rust
24/// use actix_web::{web, App};
25/// use actix_web_validator::Form;
26/// use serde::Deserialize;
27/// use validator::Validate;
28///
29/// #[derive(Deserialize, Validate)]
30/// struct Info {
31/// #[validate(length(min = 3))]
32/// username: String,
33/// }
34///
35/// /// deserialize `Info` from request's form data
36/// async fn index(info: Form<Info>) -> String {
37/// format!("Welcome {}!", info.username)
38/// }
39///
40/// fn main() {
41/// let app = App::new().service(
42/// web::resource("/index.html").route(
43/// web::post().to(index))
44/// );
45/// }
46/// ```
47#[derive(Debug)]
48pub struct Form<T>(pub T);
49
50impl<T> Form<T> {
51 /// Deconstruct to an inner value
52 pub fn into_inner(self) -> T {
53 self.0
54 }
55}
56
57impl<T> AsRef<T> for Form<T> {
58 fn as_ref(&self) -> &T {
59 &self.0
60 }
61}
62
63impl<T> Deref for Form<T> {
64 type Target = T;
65
66 fn deref(&self) -> &T {
67 &self.0
68 }
69}
70
71/// Form data helper (`application/x-www-form-urlencoded`). Allow to extract typed information from request's
72/// payload and validate it.
73///
74/// To extract typed information from request's body, the type `T` must
75/// implement the `Deserialize` trait from *serde*.
76///
77/// To validate payload, the type `T` must implement the `Validate` trait
78/// from *validator* crate.
79///
80/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction
81/// process.
82///
83/// ## Example
84///
85/// ```rust
86/// use actix_web::{web, App};
87/// use actix_web_validator::Form;
88/// use serde::Deserialize;
89/// use validator::Validate;
90///
91/// #[derive(Deserialize, Validate)]
92/// struct Info {
93/// #[validate(length(min = 3))]
94/// username: String,
95/// }
96///
97/// /// deserialize `Info` from request's form data
98/// async fn index(info: Form<Info>) -> String {
99/// format!("Welcome {}!", info.username)
100/// }
101/// ```
102impl<T> FromRequest for Form<T>
103where
104 T: DeserializeOwned + Validate + 'static,
105{
106 type Error = actix_web::Error;
107 type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
108
109 #[inline]
110 fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
111 let req2 = req.clone();
112 let (limit, error_handler) = req
113 .app_data::<FormConfig>()
114 .map(|c| (c.limit, c.ehandler.clone()))
115 .unwrap_or((16_384, None));
116
117 UrlEncoded::new(req, payload)
118 .limit(limit)
119 .map(|res: Result<T, _>| match res {
120 Ok(data) => data.validate().map(|_| Form(data)).map_err(Error::from),
121 Err(e) => Err(Error::from(e)),
122 })
123 .map(move |res| match res {
124 Err(e) => {
125 if let Some(err) = error_handler {
126 Err((*err)(e, &req2))
127 } else {
128 Err(e.into())
129 }
130 }
131 Ok(item) => Ok(item),
132 })
133 .boxed_local()
134 }
135}
136
137type ErrHandler = Rc<dyn Fn(Error, &HttpRequest) -> actix_web::Error>;
138
139/// Form extractor configuration
140///
141/// ```rust
142/// use actix_web::{error, web, App, FromRequest, HttpResponse};
143/// use serde::Deserialize;
144/// use actix_web_validator::{Form, FormConfig};
145/// use validator::Validate;
146///
147/// #[derive(Deserialize, Validate)]
148/// struct Info {
149/// #[validate(length(min = 3))]
150/// username: String,
151/// }
152///
153/// /// deserialize `Info` from request's form data, max payload size is 4kb
154/// async fn index(info: Form<Info>) -> String {
155/// format!("Welcome {}!", info.username)
156/// }
157///
158/// fn main() {
159/// let form_config = FormConfig::default()
160/// .limit(4096)
161/// .error_handler(|err, req| { // <- create custom error response
162/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
163/// });
164/// let app = App::new().service(
165/// web::resource("/index.html")
166/// .app_data(form_config)
167/// .route(web::post().to(index))
168/// );
169/// }
170/// ```
171#[derive(Clone)]
172pub struct FormConfig {
173 limit: usize,
174 ehandler: Option<ErrHandler>,
175}
176
177impl FormConfig {
178 /// Change max size of payload. By default max size is 16Kb
179 pub fn limit(mut self, limit: usize) -> Self {
180 self.limit = limit;
181 self
182 }
183
184 /// Set custom error handler
185 pub fn error_handler<F>(mut self, f: F) -> Self
186 where
187 F: Fn(Error, &HttpRequest) -> actix_web::Error + 'static,
188 {
189 self.ehandler = Some(Rc::new(f));
190 self
191 }
192}
193
194impl Default for FormConfig {
195 fn default() -> Self {
196 Self {
197 limit: 16_384,
198 ehandler: None,
199 }
200 }
201}