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