use crate::error::PayloadError;
pub use crate::headers::ContentType;
use actix_web::body::BoxBody;
use actix_web::{FromRequest, HttpRequest, HttpResponse, Responder};
use actix_web::http::StatusCode;
use std::future::Future;
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use futures_util::StreamExt;
use thiserror::Error;
mod error;
mod headers;
#[cfg(feature = "protobuf")]
pub trait ProtobufSupport: prost::Message {}
#[cfg(not(feature = "protobuf"))]
pub trait ProtobufSupport {}
#[cfg(feature = "protobuf")]
impl<T: prost::Message> ProtobufSupport for T {}
#[cfg(not(feature = "protobuf"))]
impl<T> ProtobufSupport for T {}
#[cfg(any(feature = "json", feature = "xml"))]
pub trait SerdeSupportDeserialize: serde::de::DeserializeOwned {}
#[cfg(not(any(feature = "json", feature = "xml")))]
pub trait SerdeSupportDeserialize {}
#[cfg(any(feature = "json", feature = "xml"))]
impl<T: serde::de::DeserializeOwned> SerdeSupportDeserialize for T {}
#[cfg(not(any(feature = "json", feature = "xml")))]
impl<T> SerdeSupportDeserialize for T {}
#[cfg(any(feature = "json", feature = "xml"))]
pub trait SerdeSupportSerialize: serde::Serialize {}
#[cfg(not(any(feature = "json", feature = "xml")))]
pub trait SerdeSupportSerialize {}
#[cfg(any(feature = "json", feature = "xml"))]
impl<T: serde::Serialize> SerdeSupportSerialize for T {}
#[cfg(not(any(feature = "json", feature = "xml")))]
impl<T> SerdeSupportSerialize for T {}
#[derive(Debug)]
pub struct Payload<T: 'static + Default + Clone>(pub T);
impl<T: 'static + Default + Clone> Deref for Payload<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: 'static + Default + Clone> DerefMut for Payload<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: 'static + SerdeSupportDeserialize + ProtobufSupport + Default + Clone> FromRequest
for Payload<T>
{
type Error = PayloadError;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
let req = req.clone();
#[allow(unused)]
let mut payload = payload.take();
Box::pin(async move {
let mut payload_bytes = Vec::new();
while let Some(Ok(b)) = payload.next().await {
payload_bytes.append(&mut b.to_vec())
}
let content_type = ContentType::from_request_content_type(&req);
if content_type.eq(&ContentType::Other) {
return Err(PayloadError::InvalidContentType)
}
let this = Payload::deserialize(&payload_bytes, content_type)?;
Ok(this)
})
}
}
impl<T: ProtobufSupport + SerdeSupportSerialize + Default + Clone> Responder for Payload<T> {
type Body = BoxBody;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
let content_type = ContentType::from_request_accepts(req);
let content_type = if content_type.eq(&ContentType::Other) {
let content_type_second = ContentType::from_request_content_type(req);
if content_type_second.eq(&ContentType::Other) {
ContentType::default()
} else {
content_type_second
}
} else {
content_type
};
let serialized = match self.serialize(content_type.clone()) {
Ok(x) => x,
Err(e) => {
return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
.body(e.to_string());
}
};
let mut response = HttpResponse::build(StatusCode::OK);
match content_type {
#[cfg(feature = "json")]
ContentType::Json => response.insert_header(("Content-Type", "application/json")),
#[cfg(feature = "protobuf")]
ContentType::Protobuf => response.insert_header(("Content-Type", "application/protobuf")),
#[cfg(feature = "xml")]
ContentType::Xml => response.insert_header(("Content-Type", "application/xml")),
ContentType::Other => panic!("Must have ast least one format feature enabled.")
};
response.body(serialized)
}
}
#[derive(Debug, Error)]
pub enum SerializeError {
#[cfg(feature = "json")]
#[error("Failed to serialize to JSON: {0}")]
SerdeJson(#[from] serde_json::Error),
#[cfg(feature = "json")]
#[error("Failed to encode to protobuf: {0}")]
Prost(String),
#[cfg(feature = "xml")]
#[error("Failed to serialize to XML: {0}")]
QuickXml(#[from] quick_xml::DeError),
#[error("Unable to serialize")]
Unserializable,
}
#[derive(Debug, Error)]
pub enum DeserializeError {
#[cfg(feature = "json")]
#[error("Failed to deserialize from JSON: {0}")]
SerdeJson(#[from] serde_json::Error),
#[cfg(feature = "protobuf")]
#[error("Failed to decode from protobuf: {0}")]
Prost(String),
#[cfg(feature = "xml")]
#[error("Failed to deserialize from XML: {0}")]
Xml(#[from] quick_xml::DeError),
#[error("Unable to deserialize")]
Undeserializable
}
impl<T: ProtobufSupport + SerdeSupportSerialize + Default + Clone> Payload<T> {
pub fn serialize(&self, content_type: ContentType) -> Result<Vec<u8>, SerializeError> {
match content_type {
#[cfg(feature = "json")]
ContentType::Json => {
let json = serde_json::to_string_pretty(&self.0)?;
Ok(json.into_bytes())
},
#[cfg(feature = "protobuf")]
ContentType::Protobuf => {
let mut protobuf = Vec::new();
self.0.encode(&mut protobuf)
.map_err(|e| SerializeError::Prost(e.to_string()))?;
Ok(protobuf)
},
#[cfg(feature = "xml")]
ContentType::Xml => {
let xml = quick_xml::se::to_string(&self.0)?;
Ok(xml.into_bytes())
}
ContentType::Other => Err(SerializeError::Unserializable)
}
}
}
impl<T: ProtobufSupport + SerdeSupportDeserialize + Default + Clone> Payload<T> {
pub fn deserialize(body: &[u8], content_type: ContentType) -> Result<Self, DeserializeError> {
match content_type {
#[cfg(feature = "json")]
ContentType::Json => {
let payload: T = serde_json::from_slice(body)?;
Ok(Self(payload))
},
#[cfg(feature = "protobuf")]
ContentType::Protobuf => {
let payload = T::decode(body)
.map_err(|e| DeserializeError::Prost(e.to_string()))?;
Ok(Self(payload))
},
#[cfg(feature = "xml")]
ContentType::Xml => {
let payload: T = quick_xml::de::from_reader(body)?;
Ok(Self(payload) )
}
ContentType::Other => Err(DeserializeError::Undeserializable)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use prost_derive::Message;
use serde_derive::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Message, Clone)]
struct TestPayload {
#[prost(string, tag = "1")]
foo: String,
#[prost(int64, tag = "2")]
bar: i64,
}
impl TestPayload {
#[allow(unused)]
fn json() -> String {
serde_json::to_string_pretty(&Self::default()).unwrap()
}
#[allow(unused)]
fn protobuf() -> Vec<u8> {
use prost::Message;
Self::default().encode_to_vec()
}
}
#[allow(unused)]
async fn responder(payload: Payload<TestPayload>) -> Payload<TestPayload> {
payload
}
#[allow(unused)]
macro_rules! setup {
() => {
actix_web::test::init_service(
actix_web::App::new().route("/", actix_web::web::get().to(responder)),
)
.await
};
}
#[allow(unused)]
macro_rules! body {
($res:expr) => {
actix_web::body::to_bytes($res.into_body()).await.unwrap()
};
}
#[actix_macros::test]
#[cfg(feature = "json")]
async fn test_json_req_json_res() {
let app = setup!();
let req = actix_web::test::TestRequest::default()
.insert_header(("Content-Type", "application/json"))
.set_payload(TestPayload::json())
.to_request();
let resp = actix_web::test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = body!(resp);
assert_eq!(
TestPayload::json(),
String::from_utf8(body.to_vec()).unwrap()
);
}
#[actix_macros::test]
#[cfg(all(feature = "json", feature = "protobuf"))]
async fn test_json_req_protobuf_response() {
let app = setup!();
let req = actix_web::test::TestRequest::default()
.insert_header(("Content-Type", "application/json"))
.insert_header(("Accept", "application/protobuf"))
.set_payload(TestPayload::json())
.to_request();
let resp = actix_web::test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = body!(resp);
assert_eq!(TestPayload::protobuf(), body.to_vec());
}
#[actix_macros::test]
#[cfg(all(feature = "json", feature = "protobuf"))]
async fn test_protobuf_req_json_response() {
let app = setup!();
let req = actix_web::test::TestRequest::default()
.insert_header(("Accept", "application/json"))
.insert_header(("Content-Type", "application/protobuf"))
.set_payload(TestPayload::protobuf())
.to_request();
let resp = actix_web::test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = body!(resp);
assert_eq!(
TestPayload::json(),
String::from_utf8(body.to_vec()).unwrap()
);
}
#[actix_macros::test]
#[cfg(feature = "protobuf")]
async fn test_protobuf_req_protobuf_response() {
let app = setup!();
let req = actix_web::test::TestRequest::default()
.insert_header(("Accept", "application/protobuf"))
.insert_header(("Content-Type", "application/protobuf"))
.set_payload(TestPayload::protobuf())
.to_request();
let resp = actix_web::test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body = body!(resp);
assert_eq!(TestPayload::protobuf(), body.to_vec());
}
}