Compare commits

...

3 commits

Author SHA1 Message Date
asonix a2e1ffa091 Remove Send + Sync bounds from callbacks 2022-09-24 16:22:00 -05:00
asonix 3525bcd09c Remove config from FormData trait 2022-09-10 10:55:52 -05:00
asonix c5265d286e Move to extractor pattern 2022-09-07 18:38:10 -05:00
9 changed files with 238 additions and 336 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "actix-form-data" name = "actix-form-data"
description = "Multipart Form Data for Actix Web" description = "Multipart Form Data for Actix Web"
version = "0.6.2" version = "0.7.0-beta.0"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/asonix/actix-form-data.git" repository = "https://git.asonix.dog/asonix/actix-form-data.git"

View file

@ -1,46 +1,56 @@
use actix_form_data::{Error, Field, Form, Value}; use actix_form_data::{Error, Field, Form, FormData, Multipart, Value};
use actix_web::{ use actix_web::{
web::{post, resource}, web::{post, resource},
App, HttpResponse, HttpServer, App, HttpRequest, HttpResponse, HttpServer,
}; };
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
async fn upload(uploaded_content: Value<()>) -> HttpResponse { struct UploadedContent(Value<()>);
println!("Uploaded Content: {:#?}", uploaded_content);
impl FormData for UploadedContent {
type Item = ();
type Error = Error;
fn form(_: &HttpRequest) -> Form<Self::Item, Self::Error> {
Form::new()
.field("Hey", Field::text())
.field(
"Hi",
Field::map()
.field("One", Field::int())
.field("Two", Field::float())
.finalize(),
)
.field(
"files",
Field::array(Field::file(|_, _, mut stream| async move {
while let Some(res) = stream.next().await {
res?;
}
Ok(()) as Result<(), Error>
})),
)
}
fn extract(value: Value<Self::Item>) -> Result<Self, Self::Error>
where
Self: Sized,
{
Ok(UploadedContent(value))
}
}
async fn upload(Multipart(UploadedContent(value)): Multipart<UploadedContent>) -> HttpResponse {
println!("Uploaded Content: {:#?}", value);
HttpResponse::Created().finish() HttpResponse::Created().finish()
} }
#[actix_rt::main] #[actix_rt::main]
async fn main() -> Result<(), anyhow::Error> { async fn main() -> Result<(), anyhow::Error> {
let form = Form::new() HttpServer::new(move || App::new().service(resource("/upload").route(post().to(upload))))
.field("Hey", Field::text()) .bind("127.0.0.1:8080")?
.field( .run()
"Hi", .await?;
Field::map()
.field("One", Field::int())
.field("Two", Field::float())
.finalize(),
)
.field(
"files",
Field::array(Field::file(|_, _, mut stream| async move {
while let Some(res) = stream.next().await {
res?;
}
Ok(()) as Result<(), Error>
})),
);
println!("{:?}", form);
HttpServer::new(move || {
App::new()
.wrap(form.clone())
.service(resource("/upload").route(post().to(upload)))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
Ok(()) Ok(())
} }

View file

@ -1,9 +1,9 @@
use actix_form_data::{Error, Field, Form, Value}; use actix_form_data::{Error, Field, Form, FormData, Multipart, Value};
use actix_web::{ use actix_web::{
http::StatusCode, http::StatusCode,
middleware::Logger, middleware::Logger,
web::{post, resource, Bytes}, web::{post, resource, Bytes},
App, HttpResponse, HttpServer, ResponseError, App, HttpRequest, HttpResponse, HttpServer, ResponseError,
}; };
use futures_util::stream::{Stream, StreamExt, TryStreamExt}; use futures_util::stream::{Stream, StreamExt, TryStreamExt};
use std::{ use std::{
@ -55,8 +55,49 @@ impl ResponseError for Errors {
} }
} }
async fn upload(uploaded_content: Value<PathBuf>) -> HttpResponse { struct UploadedContent(Value<PathBuf>);
info!("Uploaded Content: {:#?}", uploaded_content);
impl FormData for UploadedContent {
type Item = PathBuf;
type Error = Errors;
fn form(req: &HttpRequest) -> Form<Self::Item, Self::Error> {
let file_count = req.app_data::<Arc<AtomicUsize>>().expect("Set config");
let file_count = Arc::clone(file_count);
Form::new()
.field("Hey", Field::text())
.field(
"Hi",
Field::map()
.field("One", Field::int())
.field("Two", Field::float())
.finalize(),
)
.field(
"files",
Field::array(Field::file(move |_filename, _content_type, stream| {
let count = file_count.fetch_add(1, Ordering::Relaxed);
async move {
save_file(stream, count)
.await
.map(PathBuf::from)
.map_err(Errors::from)
}
})),
)
}
fn extract(value: Value<Self::Item>) -> Result<Self, Self::Error>
where
Self: Sized,
{
Ok(UploadedContent(value))
}
}
async fn upload(Multipart(UploadedContent(value)): Multipart<UploadedContent>) -> HttpResponse {
info!("Uploaded Content: {:#?}", value);
HttpResponse::Created().finish() HttpResponse::Created().finish()
} }
@ -91,34 +132,10 @@ async fn main() -> Result<(), anyhow::Error> {
let file_count = Arc::new(AtomicUsize::new(0)); let file_count = Arc::new(AtomicUsize::new(0));
let form = Form::new()
.field("Hey", Field::text())
.field(
"Hi",
Field::map()
.field("One", Field::int())
.field("Two", Field::float())
.finalize(),
)
.field(
"files",
Field::array(Field::file(move |_filename, _content_type, stream| {
let count = file_count.clone().fetch_add(1, Ordering::Relaxed);
async move {
save_file(stream, count)
.await
.map(PathBuf::from)
.map_err(Errors::from)
}
})),
);
info!("{:#?}", form);
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.wrap(form.clone())
.wrap(Logger::default()) .wrap(Logger::default())
.app_data(file_count.clone())
.service(resource("/upload").route(post().to(upload))) .service(resource("/upload").route(post().to(upload)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?

View file

@ -31,15 +31,15 @@ use actix_web::{
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Error parsing payload, {0}")] #[error("Error parsing payload")]
Payload(#[from] PayloadError), Payload(#[from] PayloadError),
#[error("Error in multipart creation, {0}")] #[error("Error in multipart creation")]
Multipart(MultipartError), Multipart(MultipartError),
#[error("Failed to parse field, {0}")] #[error("Failed to parse field")]
ParseField(#[from] FromUtf8Error), ParseField(#[from] FromUtf8Error),
#[error("Failed to parse int, {0}")] #[error("Failed to parse int")]
ParseInt(#[from] ParseIntError), ParseInt(#[from] ParseIntError),
#[error("Failed to parse float, {0}")] #[error("Failed to parse float")]
ParseFloat(#[from] ParseFloatError), ParseFloat(#[from] ParseFloatError),
#[error("Bad Content-Type")] #[error("Bad Content-Type")]
ContentType, ContentType,

48
src/extractor.rs Normal file
View file

@ -0,0 +1,48 @@
use crate::{
types::{Form, Value},
upload::handle_multipart,
};
use actix_web::{dev::Payload, FromRequest, HttpRequest, ResponseError};
use std::{future::Future, pin::Pin};
pub trait FormData {
type Item: 'static;
type Error: ResponseError + 'static;
fn form(req: &HttpRequest) -> Form<Self::Item, Self::Error>;
fn extract(value: Value<Self::Item>) -> Result<Self, Self::Error>
where
Self: Sized;
}
pub struct Multipart<T>(pub T);
impl<T> FromRequest for Multipart<T>
where
T: FormData,
{
type Error = actix_web::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + 'static>>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let multipart = actix_multipart::Multipart::new(req.headers(), payload.take());
let form = T::form(req);
Box::pin(async move {
let uploaded = match handle_multipart(multipart, &form).await {
Ok(Ok(uploaded)) => uploaded,
Ok(Err(e)) => return Err(e.into()),
Err(e) => {
if let Some(f) = form.transform_error {
return Err((f)(e));
} else {
return Err(e.into());
}
}
};
Ok(Multipart(T::extract(uploaded)?))
})
}
}

View file

@ -25,62 +25,73 @@
//! # Example //! # Example
//! //!
//!```rust //!```rust
//! use actix_form_data::{Error, Field, Form, Value}; //! use actix_form_data::{Error, Field, Form, FormData, Multipart, Value};
//! use actix_web::{ //! use actix_web::{
//! web::{post, resource}, //! web::{post, resource},
//! App, HttpResponse, HttpServer, //! App, HttpResponse, HttpServer,
//! }; //! };
//! use futures_util::stream::StreamExt; //! use futures_util::stream::StreamExt;
//! //!
//! async fn upload(uploaded_content: Value<()>) -> HttpResponse { //! struct UploadedContent(Value<()>);
//! println!("Uploaded Content: {:#?}", uploaded_content); //!
//! impl FormData for UploadedContent {
//! type Config = ();
//! type Item = ();
//! type Error = Error;
//!
//! fn form(_: Option<&()>) -> Form<Self::Item, Self::Error> {
//! Form::new()
//! .field("Hey", Field::text())
//! .field(
//! "Hi",
//! Field::map()
//! .field("One", Field::int())
//! .field("Two", Field::float())
//! .finalize(),
//! )
//! .field(
//! "files",
//! Field::array(Field::file(|_, _, mut stream| async move {
//! while let Some(res) = stream.next().await {
//! res?;
//! }
//! Ok(()) as Result<(), Error>
//! })),
//! )
//! }
//!
//! fn extract(value: Value<Self::Item>) -> Result<Self, Self::Error>
//! where
//! Self: Sized,
//! {
//! Ok(UploadedContent(value))
//! }
//! }
//!
//! async fn upload(Multipart(UploadedContent(value)): Multipart<UploadedContent>) -> HttpResponse {
//! println!("Uploaded Content: {:#?}", value);
//! HttpResponse::Created().finish() //! HttpResponse::Created().finish()
//! } //! }
//! //!
//! #[actix_rt::main] //! #[actix_rt::main]
//! async fn main() -> Result<(), anyhow::Error> { //! async fn main() -> Result<(), anyhow::Error> {
//! let form = Form::new() //! HttpServer::new(move || App::new().service(resource("/upload").route(post().to(upload))))
//! .field("Hey", Field::text()) //! .bind("127.0.0.1:8080")?
//! .field( //! .run();
//! "Hi", //! // .await?;
//! Field::map()
//! .field("One", Field::int())
//! .field("Two", Field::float())
//! .finalize(),
//! )
//! .field(
//! "files",
//! Field::array(Field::file(|_, _, mut stream| async move {
//! while let Some(_) = stream.next().await {
//! // do something
//! }
//! Ok(()) as Result<(), Error>
//! })),
//! );
//!
//! println!("{:?}", form);
//!
//! HttpServer::new(move || {
//! App::new()
//! .wrap(form.clone())
//! .service(resource("/upload").route(post().to(upload)))
//! })
//! .bind("127.0.0.1:8082")?;
//! // commented out to prevent infinite doctest
//! // .run()
//! // .await?;
//! //!
//! Ok(()) //! Ok(())
//! } //! }
//!``` //!```
mod error; mod error;
mod middleware; mod extractor;
mod types; mod types;
mod upload; mod upload;
pub use self::{ pub use self::{
error::Error, error::Error,
extractor::{FormData, Multipart},
types::{Field, FileMeta, Form, Value}, types::{Field, FileMeta, Form, Value},
upload::handle_multipart, upload::handle_multipart,
}; };

View file

@ -1,146 +0,0 @@
/*
* This file is part of Actix Form Data.
*
* Copyright © 2020 Riley Trautman
*
* Actix Form Data is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Actix Form Data is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Actix Form Data. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::{
types::{Form, Value},
upload::handle_multipart,
};
use actix_web::{
dev::{Payload, Service, ServiceRequest, Transform},
http::StatusCode,
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
};
use futures_util::future::LocalBoxFuture;
use std::{
future::{ready, Ready},
task::{Context, Poll},
};
use tokio::sync::oneshot::{channel, Receiver};
#[derive(Debug, thiserror::Error)]
pub enum FromRequestError {
#[error("Uploaded guard used without Multipart middleware")]
MissingMiddleware,
#[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")]
TxDropped,
}
impl ResponseError for FromRequestError {
fn status_code(&self) -> StatusCode {
match self {
Self::MissingMiddleware | Self::TxDropped => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
match self {
Self::MissingMiddleware | Self::TxDropped => {
HttpResponse::InternalServerError().finish()
}
}
}
}
struct Uploaded<T> {
rx: Receiver<Value<T>>,
}
pub struct MultipartMiddleware<S, T, E> {
form: Form<T, E>,
service: S,
}
impl<T> FromRequest for Value<T>
where
T: 'static,
{
type Error = FromRequestError;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let opt = req.extensions_mut().remove::<Uploaded<T>>();
Box::pin(async move {
let fut = opt.ok_or(FromRequestError::MissingMiddleware)?;
fut.rx.await.map_err(|_| FromRequestError::TxDropped)
})
}
}
impl<S, T, E> Transform<S, ServiceRequest> for Form<T, E>
where
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
E: ResponseError + 'static,
{
type Response = S::Response;
type Error = S::Error;
type InitError = ();
type Transform = MultipartMiddleware<S, T, E>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(MultipartMiddleware {
form: self.clone(),
service,
}))
}
}
impl<S, T, E> Service<ServiceRequest> for MultipartMiddleware<S, T, E>
where
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
E: ResponseError + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = LocalBoxFuture<'static, Result<S::Response, S::Error>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let (tx, rx) = channel();
req.extensions_mut().insert(Uploaded { rx });
let payload = req.take_payload();
let multipart = actix_multipart::Multipart::new(req.headers(), payload);
let form = self.form.clone();
let fut = self.service.call(req);
Box::pin(async move {
let uploaded = match handle_multipart(multipart, form.clone()).await {
Ok(Ok(uploaded)) => uploaded,
Ok(Err(e)) => return Err(e.into()),
Err(e) => {
if let Some(f) = form.transform_error.clone() {
return Err((f)(e));
} else {
return Err(e.into());
}
}
};
let _ = tx.send(uploaded);
fut.await
})
}
}

View file

@ -26,7 +26,6 @@ use std::{
fmt, fmt,
future::Future, future::Future,
pin::Pin, pin::Pin,
sync::Arc,
}; };
use tracing::trace; use tracing::trace;
@ -153,14 +152,12 @@ impl<T> From<MultipartContent<T>> for Value<T> {
} }
} }
pub type FileFn<T, E> = Arc< pub type FileFn<T, E> = Box<
dyn Fn( dyn Fn(
String, String,
Mime, Mime,
Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>, Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
) -> Pin<Box<dyn Future<Output = Result<T, E>>>> ) -> Pin<Box<dyn Future<Output = Result<T, E>>>>
+ Send
+ Sync,
>; >;
/// The field type represents a field in the form-data that is allowed to be parsed. /// The field type represents a field in the form-data that is allowed to be parsed.
@ -174,20 +171,6 @@ pub enum Field<T, E> {
Text, Text,
} }
impl<T, E> Clone for Field<T, E> {
fn clone(&self) -> Self {
match self {
Self::Array(a) => Self::Array(a.clone()),
Self::Map(m) => Self::Map(m.clone()),
Self::File(file_fn) => Self::File(Arc::clone(file_fn)),
Self::Bytes => Self::Bytes,
Self::Int => Self::Int,
Self::Float => Self::Float,
Self::Text => Self::Text,
}
}
}
impl<T, E> fmt::Debug for Field<T, E> { impl<T, E> fmt::Debug for Field<T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
@ -233,16 +216,14 @@ impl<T, E> Field<T, E> {
pub fn file<F, Fut>(f: F) -> Self pub fn file<F, Fut>(f: F) -> Self
where where
F: Fn(String, Mime, Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>) -> Fut F: Fn(String, Mime, Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>) -> Fut
+ Send
+ Sync
+ Clone + Clone
+ 'static, + 'static,
Fut: Future<Output = Result<T, E>> + 'static, Fut: Future<Output = Result<T, E>> + 'static,
E: 'static, E: 'static,
{ {
Field::File(Arc::new(move |filename, mime, stream| { Field::File(Box::new(move |filename, mime, stream| {
let f = f.clone(); let f = f.clone();
Box::pin(async move { (f)(filename, mime, stream).await }) Box::pin((f)(filename, mime, stream))
})) }))
} }
@ -252,7 +233,7 @@ impl<T, E> Field<T, E> {
/// ```rust /// ```rust
/// # use actix_form_data::{Error, Form, Field}; /// # use actix_form_data::{Error, Form, Field};
/// let form = Form::<(), Error>::new().field("text-field", Field::bytes()); /// let form = Form::<(), Error>::new().field("text-field", Field::bytes());
pub fn bytes() -> Self { pub const fn bytes() -> Self {
Field::Bytes Field::Bytes
} }
@ -262,7 +243,7 @@ impl<T, E> Field<T, E> {
/// ```rust /// ```rust
/// # use actix_form_data::{Error, Form, Field}; /// # use actix_form_data::{Error, Form, Field};
/// let form = Form::<(), Error>::new().field("text-field", Field::text()); /// let form = Form::<(), Error>::new().field("text-field", Field::text());
pub fn text() -> Self { pub const fn text() -> Self {
Field::Text Field::Text
} }
@ -273,7 +254,7 @@ impl<T, E> Field<T, E> {
/// # use actix_form_data::{Error, Form, Field}; /// # use actix_form_data::{Error, Form, Field};
/// let form = Form::<(), Error>::new().field("int-field", Field::int()); /// let form = Form::<(), Error>::new().field("int-field", Field::int());
/// ``` /// ```
pub fn int() -> Self { pub const fn int() -> Self {
Field::Int Field::Int
} }
@ -284,7 +265,7 @@ impl<T, E> Field<T, E> {
/// # use actix_form_data::{Error, Form, Field}; /// # use actix_form_data::{Error, Form, Field};
/// let form = Form::<(), Error>::new().field("float-field", Field::float()); /// let form = Form::<(), Error>::new().field("float-field", Field::float());
/// ``` /// ```
pub fn float() -> Self { pub const fn float() -> Self {
Field::Float Field::Float
} }
@ -321,18 +302,18 @@ impl<T, E> Field<T, E> {
/// ); /// );
/// # } /// # }
/// ``` /// ```
pub fn map() -> Map<T, E> { pub const fn map() -> Map<T, E> {
Map::new() Map::new()
} }
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> { fn valid_field<'a>(&'a self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<'a, T, E>> {
trace!("Checking {:?} and {:?}", self, name); trace!("Checking {:?} and {:?}", self, name);
match *self { match *self {
Field::Array(ref arr) => arr.valid_field(name), Field::Array(ref arr) => arr.valid_field(name),
Field::Map(ref map) => map.valid_field(name), Field::Map(ref map) => map.valid_field(name),
Field::File(ref file_fn) => { Field::File(ref file_fn) => {
if name.is_empty() { if name.is_empty() {
Some(FieldTerminator::File(file_fn.clone())) Some(FieldTerminator::File(file_fn))
} else { } else {
None None
} }
@ -377,14 +358,6 @@ pub struct Array<T, E> {
inner: Box<Field<T, E>>, inner: Box<Field<T, E>>,
} }
impl<T, E> Clone for Array<T, E> {
fn clone(&self) -> Self {
Array {
inner: Box::new((*self.inner).clone()),
}
}
}
impl<T, E> fmt::Debug for Array<T, E> { impl<T, E> fmt::Debug for Array<T, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Array").field("inner", &self.inner).finish() f.debug_struct("Array").field("inner", &self.inner).finish()
@ -398,7 +371,10 @@ impl<T, E> Array<T, E> {
} }
} }
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> { fn valid_field<'a>(
&'a self,
mut name: VecDeque<&NamePart>,
) -> Option<FieldTerminator<'a, T, E>> {
trace!("Checking {:?} and {:?}", self, name); trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() { match name.pop_front() {
Some(NamePart::Array) => self.inner.valid_field(name), Some(NamePart::Array) => self.inner.valid_field(name),
@ -412,14 +388,6 @@ pub struct Map<T, E> {
inner: Vec<(String, Field<T, E>)>, inner: Vec<(String, Field<T, E>)>,
} }
impl<T, E> Clone for Map<T, E> {
fn clone(&self) -> Self {
Map {
inner: self.inner.clone(),
}
}
}
impl<T, E> fmt::Debug for Map<T, E> { impl<T, E> fmt::Debug for Map<T, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map").field("inner", &self.inner).finish() f.debug_struct("Map").field("inner", &self.inner).finish()
@ -427,7 +395,7 @@ impl<T, E> fmt::Debug for Map<T, E> {
} }
impl<T, E> Map<T, E> { impl<T, E> Map<T, E> {
fn new() -> Self { const fn new() -> Self {
Map { inner: Vec::new() } Map { inner: Vec::new() }
} }
@ -456,11 +424,14 @@ impl<T, E> Map<T, E> {
/// .field("sub-field-two", Field::text()) /// .field("sub-field-two", Field::text())
/// .finalize(); /// .finalize();
/// ``` /// ```
pub fn finalize(self) -> Field<T, E> { pub const fn finalize(self) -> Field<T, E> {
Field::Map(self) Field::Map(self)
} }
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> { fn valid_field<'a>(
&'a self,
mut name: VecDeque<&NamePart>,
) -> Option<FieldTerminator<'a, T, E>> {
trace!("Checking {:?} and {:?}", self, name); trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() { match name.pop_front() {
Some(NamePart::Map(name_part)) => self Some(NamePart::Map(name_part)) => self
@ -502,23 +473,10 @@ pub struct Form<T, E> {
pub(crate) max_field_size: usize, pub(crate) max_field_size: usize,
pub(crate) max_files: u32, pub(crate) max_files: u32,
pub(crate) max_file_size: usize, pub(crate) max_file_size: usize,
pub(crate) transform_error: Option<Arc<dyn Fn(Error) -> actix_web::Error + Send + Sync>>, pub(crate) transform_error: Option<Box<dyn Fn(Error) -> actix_web::Error + Sync>>,
inner: Map<T, E>, inner: Map<T, E>,
} }
impl<T, E> Clone for Form<T, E> {
fn clone(&self) -> Self {
Form {
max_fields: self.max_fields,
max_field_size: self.max_field_size,
max_files: self.max_files,
max_file_size: self.max_file_size,
transform_error: None,
inner: self.inner.clone(),
}
}
}
impl<T, E> Default for Form<T, E> { impl<T, E> Default for Form<T, E> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -535,7 +493,7 @@ impl<T, E> Form<T, E> {
/// - max_field_size: 10_000 bytes /// - max_field_size: 10_000 bytes
/// - max_files: 20 /// - max_files: 20
/// - max_files_size: 10_000_000 bytes /// - max_files_size: 10_000_000 bytes
pub fn new() -> Self { pub const fn new() -> Self {
Form { Form {
max_fields: 100, max_fields: 100,
max_field_size: 10_000, max_field_size: 10_000,
@ -549,16 +507,16 @@ impl<T, E> Form<T, E> {
/// Set the Transform Error method to convert Error types into actix_web::Error by hand /// Set the Transform Error method to convert Error types into actix_web::Error by hand
pub fn transform_error( pub fn transform_error(
mut self, mut self,
f: impl Fn(Error) -> actix_web::Error + Send + Sync + 'static, f: impl Fn(Error) -> actix_web::Error + Sync + 'static,
) -> Self { ) -> Self {
self.transform_error = Some(Arc::new(f)); self.transform_error = Some(Box::new(f));
self self
} }
/// Set the maximum number of fields allowed in the upload /// Set the maximum number of fields allowed in the upload
/// ///
/// The upload will error if too many fields are provided. /// The upload will error if too many fields are provided.
pub fn max_fields(mut self, max: u32) -> Self { pub const fn max_fields(mut self, max: u32) -> Self {
self.max_fields = max; self.max_fields = max;
self self
@ -567,7 +525,7 @@ impl<T, E> Form<T, E> {
/// Set the maximum size of a field (in bytes) /// Set the maximum size of a field (in bytes)
/// ///
/// The upload will error if a provided field is too large. /// The upload will error if a provided field is too large.
pub fn max_field_size(mut self, max: usize) -> Self { pub const fn max_field_size(mut self, max: usize) -> Self {
self.max_field_size = max; self.max_field_size = max;
self self
@ -576,7 +534,7 @@ impl<T, E> Form<T, E> {
/// Set the maximum number of files allowed in the upload /// Set the maximum number of files allowed in the upload
/// ///
/// THe upload will error if too many files are provided. /// THe upload will error if too many files are provided.
pub fn max_files(mut self, max: u32) -> Self { pub const fn max_files(mut self, max: u32) -> Self {
self.max_files = max; self.max_files = max;
self self
@ -585,7 +543,7 @@ impl<T, E> Form<T, E> {
/// Set the maximum size for files (in bytes) /// Set the maximum size for files (in bytes)
/// ///
/// The upload will error if a provided file is too large. /// The upload will error if a provided file is too large.
pub fn max_file_size(mut self, max: usize) -> Self { pub const fn max_file_size(mut self, max: usize) -> Self {
self.max_file_size = max; self.max_file_size = max;
self self
@ -597,8 +555,11 @@ impl<T, E> Form<T, E> {
self self
} }
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> { pub(crate) fn valid_field<'a>(
self.inner.valid_field(name.clone()) &'a self,
name: VecDeque<&NamePart>,
) -> Option<FieldTerminator<'a, T, E>> {
self.inner.valid_field(name)
} }
} }
@ -621,21 +582,21 @@ pub(crate) enum NamePart {
} }
impl NamePart { impl NamePart {
pub fn is_map(&self) -> bool { pub const fn is_map(&self) -> bool {
matches!(self, NamePart::Map(_)) matches!(self, NamePart::Map(_))
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum FieldTerminator<T, E> { pub(crate) enum FieldTerminator<'a, T, E> {
File(FileFn<T, E>), File(&'a FileFn<T, E>),
Bytes, Bytes,
Int, Int,
Float, Float,
Text, Text,
} }
impl<T, E> fmt::Debug for FieldTerminator<T, E> { impl<'a, T, E> fmt::Debug for FieldTerminator<'a, T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
FieldTerminator::File(_) => write!(f, "File"), FieldTerminator::File(_) => write!(f, "File"),

View file

@ -101,8 +101,8 @@ fn parse_content_disposition(field: &actix_multipart::Field) -> ContentDispositi
async fn handle_file_upload<T, E>( async fn handle_file_upload<T, E>(
field: actix_multipart::Field, field: actix_multipart::Field,
filename: Option<String>, filename: Option<String>,
form: Form<T, E>, form: &Form<T, E>,
file_fn: FileFn<T, E>, file_fn: &FileFn<T, E>,
) -> Result<Result<MultipartContent<T>, E>, Error> ) -> Result<Result<MultipartContent<T>, E>, Error>
where where
T: 'static, T: 'static,
@ -119,18 +119,19 @@ where
let content_type = field.content_type().clone(); let content_type = field.content_type().clone();
let max_file_size = form.max_file_size;
let result = file_fn( let result = file_fn(
filename.clone(), filename.clone(),
content_type.clone(), content_type.clone(),
Box::pin(field.then(move |res| { Box::pin(field.then(move |res| {
let form = form.clone();
let file_size = file_size.clone(); let file_size = file_size.clone();
async move { async move {
match res { match res {
Ok(bytes) => { Ok(bytes) => {
let size = file_size.fetch_add(bytes.len(), Ordering::Relaxed); let size = file_size.fetch_add(bytes.len(), Ordering::Relaxed);
if size + bytes.len() > form.max_file_size { if size + bytes.len() > max_file_size {
return Err(Error::FileSize); return Err(Error::FileSize);
} }
@ -153,10 +154,10 @@ where
} }
} }
async fn handle_form_data<T, E>( async fn handle_form_data<'a, T, E>(
mut field: actix_multipart::Field, mut field: actix_multipart::Field,
term: FieldTerminator<T, E>, term: FieldTerminator<'a, T, E>,
form: Form<T, E>, form: &Form<T, E>,
) -> Result<MultipartContent<T>, Error> ) -> Result<MultipartContent<T>, Error>
where where
T: 'static, T: 'static,
@ -196,7 +197,7 @@ where
async fn handle_stream_field<T, E>( async fn handle_stream_field<T, E>(
field: actix_multipart::Field, field: actix_multipart::Field,
form: Form<T, E>, form: &Form<T, E>,
) -> Result<Result<MultipartHash<T>, E>, Error> ) -> Result<Result<MultipartHash<T>, E>, Error>
where where
T: 'static, T: 'static,
@ -218,7 +219,7 @@ where
Err(e) => return Ok(Err(e)), Err(e) => return Ok(Err(e)),
} }
} }
term => handle_form_data(field, term, form.clone()).await?, term => handle_form_data(field, term, form).await?,
}; };
Ok(Ok((name, content))) Ok(Ok((name, content)))
@ -227,7 +228,7 @@ where
/// Handle multipart streams from Actix Web /// Handle multipart streams from Actix Web
pub async fn handle_multipart<T, E>( pub async fn handle_multipart<T, E>(
m: actix_multipart::Multipart, m: actix_multipart::Multipart,
form: Form<T, E>, form: &Form<T, E>,
) -> Result<Result<Value<T>, E>, Error> ) -> Result<Result<Value<T>, E>, Error>
where where
T: 'static, T: 'static,
@ -245,7 +246,7 @@ where
select! { select! {
opt = m.next() => { opt = m.next() => {
if let Some(res) = opt { if let Some(res) = opt {
unordered.push(handle_stream_field(res?, form.clone())); unordered.push(handle_stream_field(res?, form));
} }
} }
opt = unordered.next() => { opt = unordered.next() => {
@ -255,7 +256,7 @@ where
Err(e) => return Ok(Err(e)), Err(e) => return Ok(Err(e)),
}; };
let (l, r) = count(&content, file_count, field_count, &form)?; let (l, r) = count(&content, file_count, field_count, form)?;
file_count = l; file_count = l;
field_count = r; field_count = r;