actix_multiresponse/
lib.rs1use crate::error::PayloadError;
30pub use crate::headers::ContentType;
31
32use actix_web::body::BoxBody;
33use actix_web::{FromRequest, HttpRequest, HttpResponse, Responder};
34use actix_web::http::StatusCode;
35
36use std::future::Future;
37use std::ops::{Deref, DerefMut};
38use std::pin::Pin;
39
40use futures_util::StreamExt;
41use thiserror::Error;
42
43mod error;
44mod headers;
45
46#[cfg(feature = "protobuf")]
47pub trait ProtobufSupport: prost::Message {}
48#[cfg(not(feature = "protobuf"))]
49pub trait ProtobufSupport {}
50
51#[cfg(feature = "protobuf")]
52impl<T: prost::Message> ProtobufSupport for T {}
53#[cfg(not(feature = "protobuf"))]
54impl<T> ProtobufSupport for T {}
55
56#[cfg(any(feature = "json", feature = "xml"))]
57pub trait SerdeSupportDeserialize: serde::de::DeserializeOwned {}
58#[cfg(not(any(feature = "json", feature = "xml")))]
59pub trait SerdeSupportDeserialize {}
60
61#[cfg(any(feature = "json", feature = "xml"))]
62impl<T: serde::de::DeserializeOwned> SerdeSupportDeserialize for T {}
63#[cfg(not(any(feature = "json", feature = "xml")))]
64impl<T> SerdeSupportDeserialize for T {}
65
66#[cfg(any(feature = "json", feature = "xml"))]
67pub trait SerdeSupportSerialize: serde::Serialize {}
68#[cfg(not(any(feature = "json", feature = "xml")))]
69pub trait SerdeSupportSerialize {}
70
71#[cfg(any(feature = "json", feature = "xml"))]
72impl<T: serde::Serialize> SerdeSupportSerialize for T {}
73#[cfg(not(any(feature = "json", feature = "xml")))]
74impl<T> SerdeSupportSerialize for T {}
75
76#[derive(Debug)]
95pub struct Payload<T: 'static + Default + Clone>(pub T);
96
97impl<T: 'static + Default + Clone> Deref for Payload<T> {
98 type Target = T;
99
100 fn deref(&self) -> &Self::Target {
101 &self.0
102 }
103}
104
105impl<T: 'static + Default + Clone> DerefMut for Payload<T> {
106 fn deref_mut(&mut self) -> &mut Self::Target {
107 &mut self.0
108 }
109}
110
111impl<T: 'static + SerdeSupportDeserialize + ProtobufSupport + Default + Clone> FromRequest
112 for Payload<T>
113{
114 type Error = PayloadError;
115 type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
116
117 fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
118 let req = req.clone();
119 #[allow(unused)]
120 let mut payload = payload.take();
121
122 Box::pin(async move {
123 let mut payload_bytes = Vec::new();
124 while let Some(Ok(b)) = payload.next().await {
125 payload_bytes.append(&mut b.to_vec())
126 }
127
128 let content_type = ContentType::from_request_content_type(&req);
129 if content_type.eq(&ContentType::Other) {
130 return Err(PayloadError::InvalidContentType)
131 }
132
133 let this = Payload::deserialize(&payload_bytes, content_type)?;
134
135 Ok(this)
136 })
137 }
138}
139
140impl<T: ProtobufSupport + SerdeSupportSerialize + Default + Clone> Responder for Payload<T> {
141 type Body = BoxBody;
142
143 fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
144 let content_type = ContentType::from_request_accepts(req);
149 let content_type = if content_type.eq(&ContentType::Other) {
150 let content_type_second = ContentType::from_request_content_type(req);
151 if content_type_second.eq(&ContentType::Other) {
152 ContentType::default()
153 } else {
154 content_type_second
155 }
156 } else {
157 content_type
158 };
159
160 let serialized = match self.serialize(content_type.clone()) {
161 Ok(x) => x,
162 Err(e) => {
163 return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
164 .body(e.to_string());
165 }
166 };
167
168 let mut response = HttpResponse::build(StatusCode::OK);
169 match content_type {
170 #[cfg(feature = "json")]
171 ContentType::Json => response.insert_header(("Content-Type", "application/json")),
172 #[cfg(feature = "protobuf")]
173 ContentType::Protobuf => response.insert_header(("Content-Type", "application/protobuf")),
174 #[cfg(feature = "xml")]
175 ContentType::Xml => response.insert_header(("Content-Type", "application/xml")),
176 ContentType::Other => panic!("Must have ast least one format feature enabled.")
177 };
178
179 response.body(serialized)
180 }
181}
182
183#[derive(Debug, Error)]
184pub enum SerializeError {
185 #[cfg(feature = "json")]
186 #[error("Failed to serialize to JSON: {0}")]
187 SerdeJson(#[from] serde_json::Error),
188 #[cfg(feature = "json")]
189 #[error("Failed to encode to protobuf: {0}")]
190 Prost(String),
191 #[cfg(feature = "xml")]
192 #[error("Failed to serialize to XML: {0}")]
193 QuickXml(#[from] quick_xml::DeError),
194 #[error("Unable to serialize")]
195 Unserializable,
196}
197
198#[derive(Debug, Error)]
199pub enum DeserializeError {
200 #[cfg(feature = "json")]
201 #[error("Failed to deserialize from JSON: {0}")]
202 SerdeJson(#[from] serde_json::Error),
203 #[cfg(feature = "protobuf")]
204 #[error("Failed to decode from protobuf: {0}")]
205 Prost(String),
206 #[cfg(feature = "xml")]
207 #[error("Failed to deserialize from XML: {0}")]
208 Xml(#[from] quick_xml::DeError),
209 #[error("Unable to deserialize")]
210 Undeserializable
211}
212
213impl<T: ProtobufSupport + SerdeSupportSerialize + Default + Clone> Payload<T> {
214 pub fn serialize(&self, content_type: ContentType) -> Result<Vec<u8>, SerializeError> {
215 match content_type {
216 #[cfg(feature = "json")]
217 ContentType::Json => {
218 let json = serde_json::to_string_pretty(&self.0)?;
219 Ok(json.into_bytes())
220 },
221 #[cfg(feature = "protobuf")]
222 ContentType::Protobuf => {
223 let mut protobuf = Vec::new();
224 self.0.encode(&mut protobuf)
225 .map_err(|e| SerializeError::Prost(e.to_string()))?;
226 Ok(protobuf)
227 },
228 #[cfg(feature = "xml")]
229 ContentType::Xml => {
230 let xml = quick_xml::se::to_string(&self.0)?;
231 Ok(xml.into_bytes())
232 }
233 ContentType::Other => Err(SerializeError::Unserializable)
234 }
235 }
236}
237
238impl<T: ProtobufSupport + SerdeSupportDeserialize + Default + Clone> Payload<T> {
239 pub fn deserialize(body: &[u8], content_type: ContentType) -> Result<Self, DeserializeError> {
240 match content_type {
241 #[cfg(feature = "json")]
242 ContentType::Json => {
243 let payload: T = serde_json::from_slice(body)?;
244 Ok(Self(payload))
245 },
246 #[cfg(feature = "protobuf")]
247 ContentType::Protobuf => {
248 let payload = T::decode(body)
249 .map_err(|e| DeserializeError::Prost(e.to_string()))?;
250 Ok(Self(payload))
251 },
252 #[cfg(feature = "xml")]
253 ContentType::Xml => {
254 let payload: T = quick_xml::de::from_reader(body)?;
255 Ok(Self(payload) )
256 }
257 ContentType::Other => Err(DeserializeError::Undeserializable)
258 }
259 }
260
261}
262
263#[cfg(test)]
264mod test {
265 use super::*;
266 use prost_derive::Message;
267 use serde_derive::{Deserialize, Serialize};
268
269 #[derive(Deserialize, Serialize, Message, Clone)]
270 struct TestPayload {
271 #[prost(string, tag = "1")]
272 foo: String,
273 #[prost(int64, tag = "2")]
274 bar: i64,
275 }
276
277 impl TestPayload {
278 #[allow(unused)]
279 fn json() -> String {
280 serde_json::to_string_pretty(&Self::default()).unwrap()
281 }
282
283 #[allow(unused)]
284 fn protobuf() -> Vec<u8> {
285 use prost::Message;
286 Self::default().encode_to_vec()
287 }
288 }
289
290 #[allow(unused)]
291 async fn responder(payload: Payload<TestPayload>) -> Payload<TestPayload> {
292 payload
293 }
294
295 #[allow(unused)]
296 macro_rules! setup {
297 () => {
298 actix_web::test::init_service(
299 actix_web::App::new().route("/", actix_web::web::get().to(responder)),
300 )
301 .await
302 };
303 }
304
305 #[allow(unused)]
306 macro_rules! body {
307 ($res:expr) => {
308 actix_web::body::to_bytes($res.into_body()).await.unwrap()
309 };
310 }
311
312 #[actix_macros::test]
313 #[cfg(feature = "json")]
314 async fn test_json_req_json_res() {
315 let app = setup!();
316 let req = actix_web::test::TestRequest::default()
317 .insert_header(("Content-Type", "application/json"))
318 .set_payload(TestPayload::json())
319 .to_request();
320 let resp = actix_web::test::call_service(&app, req).await;
321
322 assert!(resp.status().is_success());
323
324 let body = body!(resp);
325 assert_eq!(
326 TestPayload::json(),
327 String::from_utf8(body.to_vec()).unwrap()
328 );
329 }
330
331 #[actix_macros::test]
332 #[cfg(all(feature = "json", feature = "protobuf"))]
333 async fn test_json_req_protobuf_response() {
334 let app = setup!();
335 let req = actix_web::test::TestRequest::default()
336 .insert_header(("Content-Type", "application/json"))
337 .insert_header(("Accept", "application/protobuf"))
338 .set_payload(TestPayload::json())
339 .to_request();
340 let resp = actix_web::test::call_service(&app, req).await;
341
342 assert!(resp.status().is_success());
343
344 let body = body!(resp);
345 assert_eq!(TestPayload::protobuf(), body.to_vec());
346 }
347
348 #[actix_macros::test]
349 #[cfg(all(feature = "json", feature = "protobuf"))]
350 async fn test_protobuf_req_json_response() {
351 let app = setup!();
352 let req = actix_web::test::TestRequest::default()
353 .insert_header(("Accept", "application/json"))
354 .insert_header(("Content-Type", "application/protobuf"))
355 .set_payload(TestPayload::protobuf())
356 .to_request();
357 let resp = actix_web::test::call_service(&app, req).await;
358
359 assert!(resp.status().is_success());
360
361 let body = body!(resp);
362 assert_eq!(
363 TestPayload::json(),
364 String::from_utf8(body.to_vec()).unwrap()
365 );
366 }
367
368 #[actix_macros::test]
369 #[cfg(feature = "protobuf")]
370 async fn test_protobuf_req_protobuf_response() {
371 let app = setup!();
372 let req = actix_web::test::TestRequest::default()
373 .insert_header(("Accept", "application/protobuf"))
374 .insert_header(("Content-Type", "application/protobuf"))
375 .set_payload(TestPayload::protobuf())
376 .to_request();
377 let resp = actix_web::test::call_service(&app, req).await;
378
379 assert!(resp.status().is_success());
380
381 let body = body!(resp);
382 assert_eq!(TestPayload::protobuf(), body.to_vec());
383 }
384}