#[macro_use]
extern crate derive_deref;
#[cfg(feature = "chrono")]
pub mod chrono_types;
pub mod images;
#[cfg(feature = "test")]
pub mod test;
#[cfg(feature = "uuid")]
pub mod uuid_field;
mod basic;
pub use basic::*;
use actix_web::{dev::Payload, http::StatusCode, FromRequest, HttpRequest};
use displaydoc::Display;
use futures::future::{FutureExt, LocalBoxFuture};
use futures::StreamExt;
use mime::Mime;
use serde::de::DeserializeOwned;
use thiserror::Error;
use std::collections::HashMap;
use std::convert::Infallible;
use std::future::Future;
use std::io::Cursor;
use std::marker::PhantomData;
use std::path::PathBuf;
#[derive(Debug, Error, Display)]
pub enum Error {
SerializationError(#[from] serde_json::error::Error),
ImageDecodeError(#[from] image::error::ImageError),
MozjpgDecodeError,
NoFieldError(String),
NoFilenameError,
FilenameUTF8Error,
StringDecodeError(#[from] std::string::FromUtf8Error),
ActixWebError(#[from] actix_web::error::Error),
FieldError(&'static str),
UnknownError,
#[cfg(feature = "uuid")]
UUIDParseError(#[from] uuid::Error),
}
impl actix_web::error::ResponseError for Error {
fn status_code(&self) -> StatusCode {
match self {
Error::ActixWebError(e) => e.as_response_error().status_code(),
_ => StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
actix_web::HttpResponseBuilder::new(self.status_code())
.insert_header((
actix_web::http::header::CONTENT_TYPE,
"text/html; charset=utf-8",
))
.body(self.to_string())
}
}
impl std::convert::From<Infallible> for Error {
fn from(err: Infallible) -> Self {
match err {}
}
}
pub struct Multipart<T> {
pub mp: actix_multipart::Multipart,
pub _marker: PhantomData<T>,
}
pub trait FromField: Sized {
type Error: Into<actix_web::Error>;
type Future: Future<Output = Result<Self, Self::Error>> + 'static;
fn from_field(field: actix_multipart::Field) -> Self::Future;
}
pub trait FromMultipart<'a>: Sized {
type Error: Into<actix_web::Error>;
type Future: Future<Output = Result<Self, Self::Error>> + 'a;
fn from_multipart(mp: actix_multipart::Multipart) -> Self::Future;
}
impl<'a, T: FromMultipart<'a>> Multipart<T> {
#[inline]
pub async fn into_inner(self) -> Result<T, Error> {
Ok(T::from_multipart(self.mp).await.map_err(|e| e.into())?)
}
}
impl<'a, T: Sized> FromRequest for Multipart<T> {
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let mp = actix_multipart::Multipart::from_request(req, payload);
async {
Ok(Self {
mp: mp.await?,
_marker: Default::default(),
})
}
.boxed_local()
}
}
#[derive(Deref, DerefMut, Debug, Clone, Copy, Display)]
pub struct FormOrMultipart<T>(pub T);
pub enum FormOrMultipartFuture<T> {
Form(actix_web::web::Form<T>),
Multipart(Multipart<T>),
}
impl<'a, T: FromMultipart<'a>> FormOrMultipartFuture<T> {
pub async fn into_inner(self) -> Result<T, Error> {
Ok(match self {
Self::Form(f) => f.into_inner(),
Self::Multipart(m) => m.into_inner().await?,
})
}
}
impl<T: DeserializeOwned + 'static> FromRequest for FormOrMultipartFuture<T> {
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
#[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
use actix_web::HttpMessage;
let cont_type: &str = &req.content_type().to_lowercase();
if cont_type == "application/x-www-form-urlencoded" {
actix_web::web::Form::from_request(req, payload)
.map(move |res| Ok(Self::Form(res?)))
.boxed_local()
} else {
Multipart::from_request(req, payload)
.map(move |res| Ok(Self::Multipart(res?)))
.boxed_local()
}
}
}
pub fn get_content_disposition(field: &actix_multipart::Field) -> HashMap<Box<str>, Box<str>> {
let mut out = HashMap::new();
let disp = field
.headers()
.get("content-disposition")
.expect("Multipart always should have content-disposition")
.to_str()
.expect("TODO: for now assume `content-disposition' is UTF8");
let mut splt = disp.split(';').map(|f| f.trim());
assert_eq!(splt.next().unwrap(), "form-data");
for f in splt {
let vec = f.splitn(2, '=').collect::<Vec<_>>();
let k = vec[0];
let v = vec[1]
.strip_prefix('\"')
.unwrap()
.strip_suffix('\"')
.unwrap();
out.insert(
k.to_string().into_boxed_str(),
v.to_string().into_boxed_str(),
);
}
out
}