garde_actix_web/web/
json.rs1use crate::validate_for_request;
2use actix_web::dev::{JsonBody, Payload};
3use actix_web::{Error, FromRequest, HttpRequest, web};
4use futures::FutureExt;
5use futures::future::LocalBoxFuture;
6use garde::Validate;
7use serde::de::DeserializeOwned;
8use std::sync::Arc;
9use std::{fmt, ops};
10
11#[derive(Debug)]
13pub struct Json<T>(pub T);
14
15impl<T> Json<T> {
16 pub fn into_inner(self) -> T {
17 self.0
18 }
19}
20
21impl<T> ops::Deref for Json<T> {
22 type Target = T;
23
24 fn deref(&self) -> &T {
25 &self.0
26 }
27}
28
29impl<T> ops::DerefMut for Json<T> {
30 fn deref_mut(&mut self) -> &mut T {
31 &mut self.0
32 }
33}
34
35impl<T: fmt::Display> fmt::Display for Json<T> {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 fmt::Display::fmt(&self.0, f)
38 }
39}
40
41impl<T> FromRequest for Json<T>
42where
43 T: DeserializeOwned + Validate + 'static,
44 T::Context: Default,
45{
46 type Error = Error;
47 type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
48
49 #[inline]
50 fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
51 let req_copy = req.clone();
52 let req_copy2 = req.clone();
53
54 let config = JsonConfig::from_req(req);
55
56 let limit = config.limit;
57 let ctype_required = config.content_type_required;
58 let ctype_fn = config.content_type.as_deref();
59 let err_handler = config.err_handler.clone();
60
61 JsonBody::new(req, payload, ctype_fn, ctype_required)
62 .limit(limit)
63 .map(move |res: Result<T, _>| match res {
64 Ok(data) => {
65 let req = req_copy;
66 validate_for_request(data, &req)
67 }
68 Err(e) => Err(e.into()),
69 })
70 .map(move |res| match res {
71 Err(err) => {
72 log::debug!(
73 "Failed to deserialize Json from payload. \
74 Request path: {}",
75 req_copy2.path()
76 );
77
78 if let Some(err_handler) = err_handler.as_ref() {
79 Err((*err_handler)(err, &req_copy2))
80 } else {
81 Err(err.into())
82 }
83 }
84 Ok(data) => Ok(Json(data)),
85 })
86 .boxed_local()
87 }
88}
89
90type JsonErrorHandler = Option<Arc<dyn Fn(crate::error::Error, &HttpRequest) -> Error + Send + Sync>>;
91
92#[derive(Clone)]
95pub struct JsonConfig {
96 limit: usize,
97 err_handler: JsonErrorHandler,
98 content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
99 content_type_required: bool,
100}
101
102impl JsonConfig {
103 pub fn limit(mut self, limit: usize) -> Self {
104 self.limit = limit;
105 self
106 }
107
108 pub fn error_handler<F>(mut self, f: F) -> Self
109 where
110 F: Fn(crate::error::Error, &HttpRequest) -> Error + Send + Sync + 'static,
111 {
112 self.err_handler = Some(Arc::new(f));
113 self
114 }
115
116 pub fn content_type<F>(mut self, predicate: F) -> Self
117 where
118 F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
119 {
120 self.content_type = Some(Arc::new(predicate));
121 self
122 }
123
124 pub fn content_type_required(mut self, content_type_required: bool) -> Self {
125 self.content_type_required = content_type_required;
126 self
127 }
128
129 pub fn from_req(req: &HttpRequest) -> &Self {
130 req
131 .app_data::<Self>()
132 .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
133 .unwrap_or(&DEFAULT_CONFIG)
134 }
135}
136
137const DEFAULT_LIMIT: usize = 2_097_152; const DEFAULT_CONFIG: JsonConfig = JsonConfig {
140 limit: DEFAULT_LIMIT,
141 err_handler: None,
142 content_type: None,
143 content_type_required: true,
144};
145
146impl Default for JsonConfig {
147 fn default() -> Self {
148 DEFAULT_CONFIG
149 }
150}
151
152#[cfg(test)]
153mod test {
154 use crate::web::{Json, JsonConfig};
155 use actix_http::StatusCode;
156 use actix_web::error::InternalError;
157 use actix_web::test::{TestRequest, call_service, init_service};
158 use actix_web::web::{post, resource};
159 use actix_web::{App, HttpResponse};
160 use garde::Validate;
161 use serde::{Deserialize, Serialize};
162
163 #[derive(Debug, PartialEq, Validate, Serialize, Deserialize)]
164 struct JsonData {
165 #[garde(range(min = 18, max = 28))]
166 age: u8,
167 }
168
169 #[derive(Debug, PartialEq, Validate, Serialize, Deserialize)]
170 #[garde(context(NumberContext))]
171 struct JsonDataWithContext {
172 #[garde(custom(is_big_enough))]
173 age: u8,
174 }
175
176 #[derive(Default, Debug)]
177 struct NumberContext {
178 min: u8,
179 }
180
181 fn is_big_enough(value: &u8, context: &NumberContext) -> garde::Result {
182 if value < &context.min {
183 return Err(garde::Error::new("Number is too low"));
184 }
185 Ok(())
186 }
187
188 async fn test_handler(_: Json<JsonData>) -> HttpResponse {
189 HttpResponse::Ok().finish()
190 }
191
192 async fn test_handler_with_context(_: Json<JsonDataWithContext>) -> HttpResponse {
193 HttpResponse::Ok().finish()
194 }
195
196 #[tokio::test]
197 async fn test_simple_json_validation() {
198 let app = init_service(App::new().service(resource("/").route(post().to(test_handler)))).await;
199
200 let req = TestRequest::post()
201 .uri("/")
202 .set_json(&JsonData { age: 24 })
203 .to_request();
204 let resp = call_service(&app, req).await;
205 assert_eq!(resp.status(), StatusCode::OK);
206
207 let req = TestRequest::post()
208 .uri("/")
209 .set_json(&JsonData { age: 30 })
210 .to_request();
211 let resp = call_service(&app, req).await;
212 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
213 }
214
215 #[tokio::test]
216 async fn test_json_validation_custom_config() {
217 let app = init_service(
218 App::new()
219 .app_data(
220 JsonConfig::default()
221 .error_handler(|err, _req| InternalError::from_response(err, HttpResponse::Conflict().finish()).into()),
222 )
223 .service(resource("/").route(post().to(test_handler))),
224 )
225 .await;
226
227 let req = TestRequest::post()
228 .uri("/")
229 .set_json(&JsonData { age: 24 })
230 .to_request();
231 let resp = call_service(&app, req).await;
232 assert_eq!(resp.status(), StatusCode::OK);
233
234 let req = TestRequest::post()
235 .uri("/")
236 .set_json(&JsonData { age: 30 })
237 .to_request();
238 let resp = call_service(&app, req).await;
239 assert_eq!(resp.status(), StatusCode::CONFLICT);
240 }
241
242 #[tokio::test]
243 async fn test_json_validation_with_context() {
244 let number_context = NumberContext { min: 25 };
245 let app = init_service(
246 App::new()
247 .app_data(number_context)
248 .service(resource("/").route(post().to(test_handler_with_context))),
249 )
250 .await;
251
252 let req = TestRequest::post()
253 .uri("/")
254 .set_json(&JsonData { age: 24 })
255 .to_request();
256 let resp = call_service(&app, req).await;
257 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
258
259 let req = TestRequest::post()
260 .uri("/")
261 .set_json(&JsonData { age: 30 })
262 .to_request();
263 let resp = call_service(&app, req).await;
264 assert_eq!(resp.status(), StatusCode::OK);
265 }
266
267 #[tokio::test]
268 async fn test_json_validation_with_missing_context() {
269 let app = init_service(App::new().service(resource("/").route(post().to(test_handler_with_context)))).await;
270
271 let req = TestRequest::post()
272 .uri("/")
273 .set_json(&JsonData { age: 24 })
274 .to_request();
275 let resp = call_service(&app, req).await;
276 assert_eq!(resp.status(), StatusCode::OK);
277
278 let req = TestRequest::post()
279 .uri("/")
280 .set_json(&JsonData { age: 30 })
281 .to_request();
282 let resp = call_service(&app, req).await;
283 assert_eq!(resp.status(), StatusCode::OK);
284 }
285}