Skip to main content

facet_actix/
json.rs

1use core::fmt;
2use std::{
3    marker::PhantomData,
4    ops,
5    pin::Pin,
6    task::{Context, Poll, ready},
7};
8
9use actix_web::{
10    FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
11    body::EitherBody,
12    http::{StatusCode, header::CONTENT_TYPE},
13    mime::{self, APPLICATION_JSON},
14    web::Bytes,
15};
16use facet::Facet;
17use facet_format::SerializeError;
18use facet_json::{DeserializeError, JsonSerializeError};
19
20#[derive(Debug, facet::Facet)]
21#[facet(transparent)]
22pub struct Json<T>(pub T);
23
24impl<T> Json<T> {
25    /// Unwrap into inner `T` value.
26    pub fn into_inner(self) -> T {
27        self.0
28    }
29}
30
31impl<T> ops::Deref for Json<T> {
32    type Target = T;
33
34    fn deref(&self) -> &T {
35        &self.0
36    }
37}
38
39impl<T> ops::DerefMut for Json<T> {
40    fn deref_mut(&mut self) -> &mut T {
41        &mut self.0
42    }
43}
44
45impl<T: fmt::Display> fmt::Display for Json<T> {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        fmt::Display::fmt(&self.0, f)
48    }
49}
50
51#[derive(Debug)]
52pub enum JsonRejection {
53    /// Failed to read the request body.
54    Body(actix_web::Error),
55    /// Failed to deserialize the JSON data.
56    Deserialize(DeserializeError),
57    /// Missing `Content-Type: application/json` header.
58    MissingContentType,
59    /// Invalid `Content-Type` header (not application/json).
60    InvalidContentType,
61}
62
63impl fmt::Display for JsonRejection {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            JsonRejection::Body(err) => {
67                write!(f, "Failed to read request body: {err}")
68            }
69            JsonRejection::Deserialize(err) => {
70                write!(f, "Failed to deserialize JSON: {err}")
71            }
72            JsonRejection::MissingContentType => {
73                write!(f, "Missing `Content-Type: application/json` header")
74            }
75            JsonRejection::InvalidContentType => {
76                write!(
77                    f,
78                    "Invalid `Content-Type` header: expected `application/json`"
79                )
80            }
81        }
82    }
83}
84
85impl ResponseError for JsonRejection {
86    fn status_code(&self) -> StatusCode {
87        match self {
88            JsonRejection::Body(_error) => StatusCode::BAD_REQUEST,
89            JsonRejection::Deserialize(_deserialize_error) => StatusCode::UNPROCESSABLE_ENTITY,
90            JsonRejection::MissingContentType | JsonRejection::InvalidContentType => {
91                StatusCode::UNSUPPORTED_MEDIA_TYPE
92            }
93        }
94    }
95}
96
97impl<T: Facet<'static>> actix_web::FromRequest for Json<T> {
98    type Error = JsonRejection;
99    type Future = JsonExtractFut<T>;
100
101    fn from_request(
102        req: &actix_web::HttpRequest,
103        payload: &mut actix_web::dev::Payload,
104    ) -> Self::Future {
105        JsonExtractFut {
106            req: Some(req.clone()),
107            bytes: Bytes::from_request(req, payload),
108            marker: PhantomData,
109        }
110    }
111}
112
113pub struct JsonExtractFut<T: Facet<'static>> {
114    req: Option<HttpRequest>,
115    bytes: <Bytes as FromRequest>::Future,
116    marker: PhantomData<T>,
117}
118
119impl<T: Facet<'static>> Unpin for JsonExtractFut<T> {}
120
121impl<T: Facet<'static>> Future for JsonExtractFut<T> {
122    type Output = Result<Json<T>, JsonRejection>;
123
124    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
125        let JsonExtractFut { req, bytes, .. } = self.get_mut();
126
127        if let Some(req) = req.take() {
128            match req.headers().get(CONTENT_TYPE) {
129                Some(ct) if ct != APPLICATION_JSON.as_ref() => {
130                    Err(JsonRejection::InvalidContentType)?
131                }
132                Some(_) => (),
133                None => Err(JsonRejection::MissingContentType)?,
134            }
135        }
136
137        let fut = Pin::new(bytes);
138
139        let res = ready!(fut.poll(cx));
140
141        let res = match res {
142            Err(err) => Err(JsonRejection::Body(err)),
143            Ok(data) => match facet_json::from_slice::<T>(&data) {
144                Ok(data) => Ok(Json(data)),
145                Err(e) => Err(JsonRejection::Deserialize(e))?,
146            },
147        };
148
149        Poll::Ready(res)
150    }
151}
152
153impl<'a, T: Facet<'a>> Responder for Json<T> {
154    type Body = EitherBody<String>;
155
156    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
157        match facet_json::to_string(&self.0) {
158            Ok(body) => match HttpResponse::Ok()
159                .content_type(mime::APPLICATION_JSON)
160                .message_body(body)
161            {
162                Ok(res) => res.map_into_left_body(),
163                Err(err) => HttpResponse::from_error(err).map_into_right_body(),
164            },
165
166            Err(err) => {
167                HttpResponse::from_error(SerializeErrorToActixError(err)).map_into_right_body()
168            }
169        }
170    }
171}
172
173#[derive(Debug)]
174struct SerializeErrorToActixError(pub SerializeError<JsonSerializeError>);
175
176impl fmt::Display for SerializeErrorToActixError {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        self.0.fmt(f)
179    }
180}
181
182impl actix_web::ResponseError for SerializeErrorToActixError {
183    fn status_code(&self) -> StatusCode {
184        StatusCode::INTERNAL_SERVER_ERROR
185    }
186}