actix_easy_multipart/
json.rs1use crate::bytes::Bytes;
3use crate::{field_mime, FieldReader, Limits};
4use actix_multipart::Field;
5use actix_web::http::StatusCode;
6use actix_web::{web, HttpRequest, ResponseError};
7use derive_more::{Deref, DerefMut, Display, Error};
8use futures_core::future::LocalBoxFuture;
9use futures_util::FutureExt;
10use serde::de::DeserializeOwned;
11use std::sync::Arc;
12
13#[derive(Debug, Deref, DerefMut)]
15pub struct Json<T: DeserializeOwned>(pub T);
16
17impl<T: DeserializeOwned> Json<T> {
18    pub fn into_inner(self) -> T {
19        self.0
20    }
21}
22
23impl<'t, T: DeserializeOwned + 'static> FieldReader<'t> for Json<T> {
24    type Future = LocalBoxFuture<'t, Result<Self, crate::Error>>;
25
26    fn read_field(req: &'t HttpRequest, field: Field, limits: &'t mut Limits) -> Self::Future {
27        async move {
28            let config = JsonConfig::from_req(req);
29            let field_name = field.name().to_owned();
30
31            if config.validate_content_type {
32                let valid = if let Some(mime) = field_mime(&field) {
33                    mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
34                } else {
35                    false
36                };
37                if !valid {
38                    return Err(crate::Error::Field {
39                        field_name,
40                        source: config.map_error(req, JsonFieldError::ContentType),
41                    });
42                }
43            }
44
45            let bytes = Bytes::read_field(req, field, limits).await?;
46
47            Ok(Json(serde_json::from_slice(bytes.data.as_ref()).map_err(
48                |e| crate::Error::Field {
49                    field_name,
50                    source: config.map_error(req, JsonFieldError::Deserialize(e)),
51                },
52            )?))
53        }
54        .boxed_local()
55    }
56}
57
58#[derive(Debug, Display, Error)]
59#[non_exhaustive]
60pub enum JsonFieldError {
61    #[display(fmt = "Json deserialize error: {}", _0)]
63    Deserialize(serde_json::Error),
64
65    #[display(fmt = "Content type error")]
67    ContentType,
68}
69
70impl ResponseError for JsonFieldError {
71    fn status_code(&self) -> StatusCode {
72        StatusCode::BAD_REQUEST
73    }
74}
75
76#[derive(Clone)]
78pub struct JsonConfig {
79    err_handler:
80        Option<Arc<dyn Fn(JsonFieldError, &HttpRequest) -> actix_web::Error + Send + Sync>>,
81    validate_content_type: bool,
82}
83
84const DEFAULT_CONFIG: JsonConfig = JsonConfig {
85    err_handler: None,
86    validate_content_type: true,
87};
88
89impl JsonConfig {
90    pub fn error_handler<F>(mut self, f: F) -> Self
91    where
92        F: Fn(JsonFieldError, &HttpRequest) -> actix_web::Error + Send + Sync + 'static,
93    {
94        self.err_handler = Some(Arc::new(f));
95        self
96    }
97
98    fn from_req(req: &HttpRequest) -> &Self {
101        req.app_data::<Self>()
102            .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
103            .unwrap_or(&DEFAULT_CONFIG)
104    }
105
106    fn map_error(&self, req: &HttpRequest, err: JsonFieldError) -> actix_web::Error {
107        if let Some(err_handler) = self.err_handler.as_ref() {
108            (*err_handler)(err, req)
109        } else {
110            err.into()
111        }
112    }
113
114    pub fn validate_content_type(mut self, validate_content_type: bool) -> Self {
116        self.validate_content_type = validate_content_type;
117        self
118    }
119}
120
121impl Default for JsonConfig {
122    fn default() -> Self {
123        DEFAULT_CONFIG
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use crate::json::{Json, JsonConfig};
130    use crate::tests::send_form;
131    use crate::MultipartForm;
132    use actix_multipart_rfc7578::client::multipart;
133    use actix_web::http::StatusCode;
134    use actix_web::{web, App, HttpResponse, Responder};
135    use std::collections::HashMap;
136    use std::io::Cursor;
137
138    #[derive(MultipartForm)]
139    struct JsonForm {
140        json: Json<HashMap<String, String>>,
141    }
142
143    async fn test_json_route(form: MultipartForm<JsonForm>) -> impl Responder {
144        let mut expected = HashMap::new();
145        expected.insert("key1".to_owned(), "value1".to_owned());
146        expected.insert("key2".to_owned(), "value2".to_owned());
147        assert_eq!(&*form.json, &expected);
148        HttpResponse::Ok().finish()
149    }
150
151    #[actix_rt::test]
152    async fn test_json_without_content_type() {
153        let srv = actix_test::start(|| {
154            App::new()
155                .route("/", web::post().to(test_json_route))
156                .app_data(JsonConfig::default().validate_content_type(false))
157        });
158
159        let mut form = multipart::Form::default();
160        form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}");
161        let response = send_form(&srv, form, "/").await;
162        assert_eq!(response.status(), StatusCode::OK);
163    }
164
165    #[actix_rt::test]
166    async fn test_content_type_validation() {
167        let srv = actix_test::start(|| {
168            App::new()
169                .route("/", web::post().to(test_json_route))
170                .app_data(JsonConfig::default().validate_content_type(true))
171        });
172
173        let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
175        let mut form = multipart::Form::default();
176        form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM);
177        let response = send_form(&srv, form, "/").await;
178        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
179
180        let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
182        let mut form = multipart::Form::default();
183        form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON);
184        let response = send_form(&srv, form, "/").await;
185        assert_eq!(response.status(), StatusCode::OK);
186    }
187}