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}