Compare commits
3 commits
4cbc7cb78e
...
a2e1ffa091
Author | SHA1 | Date | |
---|---|---|---|
asonix | a2e1ffa091 | ||
asonix | 3525bcd09c | ||
asonix | c5265d286e |
|
@ -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"
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")?
|
||||||
|
|
10
src/error.rs
10
src/error.rs
|
@ -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
48
src/extractor.rs
Normal 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)?))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
79
src/lib.rs
79
src/lib.rs
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
113
src/types.rs
113
src/types.rs
|
@ -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"),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue