Allow non-path states from upload

This commit is contained in:
Aode (lion) 2021-09-11 17:35:48 -05:00
parent bdad3f5145
commit 0c231119ac
8 changed files with 171 additions and 104 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.0-beta.4" version = "0.6.0-beta.5"
license = "GPL-3.0" license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git" repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git"

View file

@ -27,7 +27,7 @@ use form_data::{Field, Form, Value};
#### Overview #### Overview
First, you'd create a form structure you want to parse from the multipart stream. First, you'd create a form structure you want to parse from the multipart stream.
```rust ```rust
let form = Form::new().field("field-name", Field::text()); let form = Form::<()>::new().field("field-name", Field::text());
``` ```
This creates a form with one required field named "field-name" that will be parsed as text. This creates a form with one required field named "field-name" that will be parsed as text.
@ -69,7 +69,7 @@ use actix_web::{
}; };
use futures::stream::StreamExt; use futures::stream::StreamExt;
async fn upload(uploaded_content: Value) -> HttpResponse { async fn upload(uploaded_content: Value<()>) -> HttpResponse {
println!("Uploaded Content: {:#?}", uploaded_content); println!("Uploaded Content: {:#?}", uploaded_content);
HttpResponse::Created().finish() HttpResponse::Created().finish()
} }

View file

@ -5,7 +5,7 @@ use actix_web::{
}; };
use futures::stream::StreamExt; use futures::stream::StreamExt;
async fn upload(uploaded_content: Value) -> HttpResponse { async fn upload(uploaded_content: Value<()>) -> HttpResponse {
println!("Uploaded Content: {:#?}", uploaded_content); println!("Uploaded Content: {:#?}", uploaded_content);
HttpResponse::Created().finish() HttpResponse::Created().finish()
} }
@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> {
while let Some(res) = stream.next().await { while let Some(res) = stream.next().await {
res?; res?;
} }
Ok(None) as Result<_, Error> Ok(()) as Result<(), Error>
})), })),
); );

View file

@ -8,6 +8,7 @@ use actix_web::{
use futures::stream::{Stream, StreamExt, TryStreamExt}; use futures::stream::{Stream, StreamExt, TryStreamExt};
use std::{ use std::{
env, env,
path::PathBuf,
pin::Pin, pin::Pin,
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
@ -18,7 +19,7 @@ use tracing::info;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct AppState { struct AppState {
form: Form, form: Form<PathBuf>,
} }
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
@ -59,7 +60,7 @@ impl ResponseError for Errors {
} }
} }
async fn upload(uploaded_content: Value) -> HttpResponse { async fn upload(uploaded_content: Value<PathBuf>) -> HttpResponse {
info!("Uploaded Content: {:#?}", uploaded_content); info!("Uploaded Content: {:#?}", uploaded_content);
HttpResponse::Created().finish() HttpResponse::Created().finish()
@ -111,8 +112,7 @@ async fn main() -> Result<(), anyhow::Error> {
async move { async move {
save_file(stream, count) save_file(stream, count)
.await .await
.map(Into::into) .map(PathBuf::from)
.map(Some)
.map_err(Errors::from) .map_err(Errors::from)
} }
})), })),

View file

@ -32,7 +32,7 @@
//! }; //! };
//! use futures::stream::StreamExt; //! use futures::stream::StreamExt;
//! //!
//! async fn upload(uploaded_content: Value) -> HttpResponse { //! async fn upload(uploaded_content: Value<()>) -> HttpResponse {
//! println!("Uploaded Content: {:#?}", uploaded_content); //! println!("Uploaded Content: {:#?}", uploaded_content);
//! HttpResponse::Created().finish() //! HttpResponse::Created().finish()
//! } //! }
@ -54,7 +54,7 @@
//! while let Some(_) = stream.next().await { //! while let Some(_) = stream.next().await {
//! // do something //! // do something
//! } //! }
//! Ok(None) as Result<_, std::convert::Infallible> //! Ok(()) as Result<(), std::convert::Infallible>
//! })), //! })),
//! ); //! );
//! //!

View file

@ -34,22 +34,25 @@ use std::{
}; };
use tokio::sync::oneshot::{channel, Receiver}; use tokio::sync::oneshot::{channel, Receiver};
struct Uploaded { struct Uploaded<T> {
rx: Receiver<Value>, rx: Receiver<Value<T>>,
} }
pub struct MultipartMiddleware<S> { pub struct MultipartMiddleware<S, T> {
form: Form, form: Form<T>,
service: S, service: S,
} }
impl FromRequest for Value { impl<T> FromRequest for Value<T>
where
T: 'static,
{
type Error = Error; type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
type Config = (); type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let opt = req.extensions_mut().remove::<Uploaded>(); let opt = req.extensions_mut().remove::<Uploaded<T>>();
Box::pin(async move { Box::pin(async move {
let fut = opt.ok_or(Error::MissingMiddleware)?; let fut = opt.ok_or(Error::MissingMiddleware)?;
@ -58,15 +61,16 @@ impl FromRequest for Value {
} }
} }
impl<S> Transform<S, ServiceRequest> for Form impl<S, T> Transform<S, ServiceRequest> for Form<T>
where where
S: Service<ServiceRequest, Error = actix_web::Error>, S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static, S::Future: 'static,
T: 'static,
{ {
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type InitError = (); type InitError = ();
type Transform = MultipartMiddleware<S>; type Transform = MultipartMiddleware<S, T>;
type Future = Ready<Result<Self::Transform, Self::InitError>>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
@ -77,10 +81,11 @@ where
} }
} }
impl<S> Service<ServiceRequest> for MultipartMiddleware<S> impl<S, T> Service<ServiceRequest> for MultipartMiddleware<S, T>
where where
S: Service<ServiceRequest, Error = actix_web::Error>, S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static, S::Future: 'static,
T: 'static,
{ {
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;

View file

@ -25,17 +25,16 @@ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
fmt, fmt,
future::Future, future::Future,
path::PathBuf,
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
}; };
use tracing::trace; use tracing::trace;
#[derive(Debug)] #[derive(Debug)]
pub struct FileMeta { pub struct FileMeta<T = ()> {
pub filename: String, pub filename: String,
pub content_type: Mime, pub content_type: Mime,
pub saved_as: Option<PathBuf>, pub result: T,
} }
/// The result of a succesfull parse through a given multipart stream. /// The result of a succesfull parse through a given multipart stream.
@ -49,7 +48,7 @@ pub struct FileMeta {
/// # use std::collections::HashMap; /// # use std::collections::HashMap;
/// # let mut hm = HashMap::new(); /// # let mut hm = HashMap::new();
/// # hm.insert("field-name".to_owned(), Value::Int(32)); /// # hm.insert("field-name".to_owned(), Value::Int(32));
/// # let value = Value::Map(hm); /// # let value = Value::<()>::Map(hm);
/// match value { /// match value {
/// Value::Map(mut hashmap) => { /// Value::Map(mut hashmap) => {
/// match hashmap.remove("field-name") { /// match hashmap.remove("field-name") {
@ -64,17 +63,17 @@ pub struct FileMeta {
/// } /// }
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub enum Value { pub enum Value<T = ()> {
Map(HashMap<String, Value>), Map(HashMap<String, Value<T>>),
Array(Vec<Value>), Array(Vec<Value<T>>),
File(FileMeta), File(FileMeta<T>),
Bytes(Bytes), Bytes(Bytes),
Text(String), Text(String),
Int(i64), Int(i64),
Float(f64), Float(f64),
} }
impl Value { impl<T> Value<T> {
pub(crate) fn merge(&mut self, rhs: Self) { pub(crate) fn merge(&mut self, rhs: Self) {
match self { match self {
Value::Map(ref mut hm) => { Value::Map(ref mut hm) => {
@ -99,21 +98,21 @@ impl Value {
} }
} }
pub fn map(self) -> Option<HashMap<String, Value>> { pub fn map(self) -> Option<HashMap<String, Value<T>>> {
match self { match self {
Value::Map(map) => Some(map), Value::Map(map) => Some(map),
_ => None, _ => None,
} }
} }
pub fn array(self) -> Option<Vec<Value>> { pub fn array(self) -> Option<Vec<Value<T>>> {
match self { match self {
Value::Array(vec) => Some(vec), Value::Array(vec) => Some(vec),
_ => None, _ => None,
} }
} }
pub fn file(self) -> Option<FileMeta> { pub fn file(self) -> Option<FileMeta<T>> {
match self { match self {
Value::File(file_meta) => Some(file_meta), Value::File(file_meta) => Some(file_meta),
_ => None, _ => None,
@ -149,8 +148,8 @@ impl Value {
} }
} }
impl From<MultipartContent> for Value { impl<T> From<MultipartContent<T>> for Value<T> {
fn from(mc: MultipartContent) -> Self { fn from(mc: MultipartContent<T>) -> Self {
match mc { match mc {
MultipartContent::File(file_meta) => Value::File(file_meta), MultipartContent::File(file_meta) => Value::File(file_meta),
MultipartContent::Bytes(bytes) => Value::Bytes(bytes), MultipartContent::Bytes(bytes) => Value::Bytes(bytes),
@ -161,29 +160,42 @@ impl From<MultipartContent> for Value {
} }
} }
pub type FileFn = Arc< pub type FileFn<T> = Arc<
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<Option<PathBuf>, actix_web::Error>>>> ) -> Pin<Box<dyn Future<Output = Result<T, actix_web::Error>>>>
+ Send + Send
+ Sync, + 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.
#[derive(Clone)] pub enum Field<T = ()> {
pub enum Field { Array(Array<T>),
Array(Array), Map(Map<T>),
Map(Map), File(FileFn<T>),
File(FileFn),
Bytes, Bytes,
Int, Int,
Float, Float,
Text, Text,
} }
impl fmt::Debug for Field { impl<T> Clone for Field<T> {
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> fmt::Debug for Field<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Field::Array(ref arr) => f.debug_tuple("Array").field(arr).finish(), Field::Array(ref arr) => f.debug_tuple("Array").field(arr).finish(),
@ -197,7 +209,7 @@ impl fmt::Debug for Field {
} }
} }
impl Field { impl<T> Field<T> {
/// Add a File field with a name generator. /// Add a File field with a name generator.
/// ///
/// The name generator will be called for each file matching this field's key. Keep in mind /// The name generator will be called for each file matching this field's key. Keep in mind
@ -211,7 +223,7 @@ impl Field {
/// # use futures::stream::StreamExt; /// # use futures::stream::StreamExt;
/// # /// #
/// let (tx, rx) = channel(1); /// let (tx, rx) = channel(1);
/// let form = Form::new().field("file-field", Field::file(move |_, _, mut stream| { /// let form = Form::<()>::new().field("file-field", Field::file(move |_, _, mut stream| {
/// let mut tx = tx.clone(); /// let mut tx = tx.clone();
/// async move { /// async move {
/// while let Some(res) = stream.next().await { /// while let Some(res) = stream.next().await {
@ -221,7 +233,7 @@ impl Field {
/// } /// }
/// } /// }
/// } /// }
/// Ok(None) as Result<_, std::convert::Infallible> /// Ok(()) as Result<_, std::convert::Infallible>
/// } /// }
/// })); /// }));
/// ``` /// ```
@ -232,7 +244,7 @@ impl Field {
+ Sync + Sync
+ Clone + Clone
+ 'static, + 'static,
Fut: Future<Output = Result<Option<PathBuf>, E>> + 'static, Fut: Future<Output = Result<T, E>> + 'static,
E: Into<actix_web::Error> + 'static, E: Into<actix_web::Error> + 'static,
{ {
Field::File(Arc::new(move |filename, mime, stream| { Field::File(Arc::new(move |filename, mime, stream| {
@ -246,7 +258,7 @@ impl Field {
/// # Example /// # Example
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// let form = Form::new().field("text-field", Field::bytes()); /// let form = Form::<()>::new().field("text-field", Field::bytes());
pub fn bytes() -> Self { pub fn bytes() -> Self {
Field::Bytes Field::Bytes
} }
@ -256,7 +268,7 @@ impl Field {
/// # Example /// # Example
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// let form = Form::new().field("text-field", Field::text()); /// let form = Form::<()>::new().field("text-field", Field::text());
pub fn text() -> Self { pub fn text() -> Self {
Field::Text Field::Text
} }
@ -266,7 +278,7 @@ impl Field {
/// # Example /// # Example
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// let form = Form::new().field("int-field", Field::int()); /// let form = Form::<()>::new().field("int-field", Field::int());
/// ``` /// ```
pub fn int() -> Self { pub fn int() -> Self {
Field::Int Field::Int
@ -277,7 +289,7 @@ impl Field {
/// # Example /// # Example
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// let form = Form::new().field("float-field", Field::float()); /// let form = Form::<()>::new().field("float-field", Field::float());
/// ``` /// ```
pub fn float() -> Self { pub fn float() -> Self {
Field::Float Field::Float
@ -289,14 +301,14 @@ impl Field {
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// # fn main() { /// # fn main() {
/// let form = Form::new() /// let form = Form::<()>::new()
/// .field( /// .field(
/// "array-field", /// "array-field",
/// Field::array(Field::text()) /// Field::array(Field::text())
/// ); /// );
/// # } /// # }
/// ``` /// ```
pub fn array(field: Field) -> Self { pub fn array(field: Field<T>) -> Self {
Field::Array(Array::new(field)) Field::Array(Array::new(field))
} }
@ -306,7 +318,7 @@ impl Field {
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// # fn main() { /// # fn main() {
/// let form = Form::new() /// let form = Form::<()>::new()
/// .field( /// .field(
/// "map-field", /// "map-field",
/// Field::map() /// Field::map()
@ -316,11 +328,11 @@ impl Field {
/// ); /// );
/// # } /// # }
/// ``` /// ```
pub fn map() -> Map { pub fn map() -> Map<T> {
Map::new() Map::new()
} }
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator> { fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
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),
@ -368,19 +380,32 @@ impl Field {
/// ///
/// The `Array` type should only be constructed in the context of a Form. See the `Form` /// The `Array` type should only be constructed in the context of a Form. See the `Form`
/// documentation for more information. /// documentation for more information.
#[derive(Debug, Clone)] pub struct Array<T = ()> {
pub struct Array { inner: Box<Field<T>>,
inner: Box<Field>,
} }
impl Array { impl<T> Clone for Array<T> {
fn new(field: Field) -> Self { fn clone(&self) -> Self {
Array { inner: Box::new((*self.inner).clone()) }
}
}
impl<T> fmt::Debug for Array<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Array")
.field("inner", &self.inner)
.finish()
}
}
impl<T> Array<T> {
fn new(field: Field<T>) -> Self {
Array { Array {
inner: Box::new(field), inner: Box::new(field),
} }
} }
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator> { fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
trace!("Checking {:?} and {:?}", self, name); trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() { match name.pop_front() {
Some(name_part) => match name_part { Some(name_part) => match name_part {
@ -393,12 +418,25 @@ impl Array {
} }
/// A definition of key-value pairs to be parsed from form data. /// A definition of key-value pairs to be parsed from form data.
#[derive(Debug, Clone)] pub struct Map<T = ()> {
pub struct Map { inner: Vec<(String, Field<T>)>,
inner: Vec<(String, Field)>,
} }
impl Map { impl<T> Clone for Map<T> {
fn clone(&self) -> Self {
Map { inner: self.inner.clone() }
}
}
impl<T> fmt::Debug for Map<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map")
.field("inner", &self.inner)
.finish()
}
}
impl<T> Map<T> {
fn new() -> Self { fn new() -> Self {
Map { inner: Vec::new() } Map { inner: Vec::new() }
} }
@ -408,12 +446,12 @@ impl Map {
/// ```rust /// ```rust
/// # use actix_form_data::Field; /// # use actix_form_data::Field;
/// # /// #
/// Field::map() /// Field::<()>::map()
/// .field("sub-field", Field::text()) /// .field("sub-field", Field::text())
/// .field("sub-field-two", Field::text()) /// .field("sub-field-two", Field::text())
/// .finalize(); /// .finalize();
/// ``` /// ```
pub fn field(mut self, key: &str, value: Field) -> Self { pub fn field(mut self, key: &str, value: Field<T>) -> Self {
self.inner.push((key.to_owned(), value)); self.inner.push((key.to_owned(), value));
self self
@ -423,16 +461,16 @@ impl Map {
/// ```rust /// ```rust
/// # use actix_form_data::Field; /// # use actix_form_data::Field;
/// # /// #
/// Field::map() /// Field::<()>::map()
/// .field("sub-field", Field::text()) /// .field("sub-field", Field::text())
/// .field("sub-field-two", Field::text()) /// .field("sub-field-two", Field::text())
/// .finalize(); /// .finalize();
/// ``` /// ```
pub fn finalize(self) -> Field { pub fn finalize(self) -> Field<T> {
Field::Map(self) Field::Map(self)
} }
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator> { fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
trace!("Checking {:?} and {:?}", self, name); trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() { match name.pop_front() {
Some(name_part) => match name_part { Some(name_part) => match name_part {
@ -453,12 +491,12 @@ impl Map {
/// # Example /// # Example
/// ```rust /// ```rust
/// # use actix_form_data::{Form, Field}; /// # use actix_form_data::{Form, Field};
/// let form = Form::new() /// let form = Form::<()>::new()
/// .field("field-name", Field::text()) /// .field("field-name", Field::text())
/// .field("second-field", Field::int()) /// .field("second-field", Field::int())
/// .field("third-field", Field::float()) /// .field("third-field", Field::float())
/// .field("fifth-field", Field::file(|_, _, _| async move { /// .field("fifth-field", Field::file(|_, _, _| async move {
/// Ok(None) as Result<_, std::convert::Infallible> /// Ok(()) as Result<(), std::convert::Infallible>
/// })) /// }))
/// .field( /// .field(
/// "map-field", /// "map-field",
@ -472,18 +510,30 @@ impl Map {
/// Field::array(Field::text()) /// Field::array(Field::text())
/// ); /// );
/// ``` /// ```
#[derive(Clone)] pub struct Form<T = ()> {
pub struct Form {
pub(crate) max_fields: u32, pub(crate) max_fields: u32,
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: pub(crate) transform_error:
Option<Arc<dyn Fn(crate::error::Error) -> actix_web::Error + Send + Sync>>, Option<Arc<dyn Fn(crate::error::Error) -> actix_web::Error + Send + Sync>>,
inner: Map, inner: Map<T>,
} }
impl Form { impl<T> Clone for Form<T> {
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: self.transform_error.clone(),
inner: self.inner.clone(),
}
}
}
impl<T> Form<T> {
/// Create a new form /// Create a new form
/// ///
/// If you wish to provide your own executor, use the `with_executor` method. /// If you wish to provide your own executor, use the `with_executor` method.
@ -550,18 +600,18 @@ impl Form {
self self
} }
pub fn field(mut self, name: &str, field: Field) -> Self { pub fn field(mut self, name: &str, field: Field<T>) -> Self {
self.inner = self.inner.field(name, field); self.inner = self.inner.field(name, field);
self self
} }
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator> { pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
self.inner.valid_field(name.clone()) self.inner.valid_field(name.clone())
} }
} }
impl fmt::Debug for Form { impl<T> fmt::Debug for Form<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Form").field("inner", &self.inner).finish() f.debug_struct("Form").field("inner", &self.inner).finish()
} }
@ -598,15 +648,15 @@ impl NamePart {
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum FieldTerminator { pub(crate) enum FieldTerminator<T = ()> {
File(FileFn), File(FileFn<T>),
Bytes, Bytes,
Int, Int,
Float, Float,
Text, Text,
} }
impl fmt::Debug for FieldTerminator { impl<T> fmt::Debug for FieldTerminator<T> {
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"),
@ -618,12 +668,12 @@ impl fmt::Debug for FieldTerminator {
} }
} }
pub(crate) type MultipartHash = (Vec<NamePart>, MultipartContent); pub(crate) type MultipartHash<T> = (Vec<NamePart>, MultipartContent<T>);
pub(crate) type MultipartForm = Vec<MultipartHash>; pub(crate) type MultipartForm<T> = Vec<MultipartHash<T>>;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum MultipartContent { pub(crate) enum MultipartContent<T = ()> {
File(FileMeta), File(FileMeta<T>),
Bytes(Bytes), Bytes(Bytes),
Text(String), Text(String),
Int(i64), Int(i64),

View file

@ -39,7 +39,7 @@ use std::{
}; };
use tracing::trace; use tracing::trace;
fn consolidate(mf: MultipartForm) -> Value { fn consolidate<T>(mf: MultipartForm<T>) -> Value<T> {
mf.into_iter().fold( mf.into_iter().fold(
Value::Map(HashMap::new()), Value::Map(HashMap::new()),
|mut acc, (mut nameparts, content)| { |mut acc, (mut nameparts, content)| {
@ -99,12 +99,15 @@ fn parse_content_disposition(field: &actix_multipart::Field) -> ContentDispositi
} }
} }
async fn handle_file_upload( async fn handle_file_upload<T>(
field: actix_multipart::Field, field: actix_multipart::Field,
filename: Option<String>, filename: Option<String>,
form: Form, form: Form<T>,
file_fn: FileFn, file_fn: FileFn<T>,
) -> Result<MultipartContent, Error> { ) -> Result<MultipartContent<T>, Error>
where
T: 'static,
{
let filename = filename.ok_or(Error::Filename)?; let filename = filename.ok_or(Error::Filename)?;
let path: &Path = filename.as_ref(); let path: &Path = filename.as_ref();
@ -116,7 +119,7 @@ async fn handle_file_upload(
let content_type = field.content_type().clone(); let content_type = field.content_type().clone();
let saved_as = 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| {
@ -143,15 +146,18 @@ async fn handle_file_upload(
Ok(MultipartContent::File(FileMeta { Ok(MultipartContent::File(FileMeta {
filename, filename,
content_type, content_type,
saved_as, result,
})) }))
} }
async fn handle_form_data( async fn handle_form_data<T>(
mut field: actix_multipart::Field, mut field: actix_multipart::Field,
term: FieldTerminator, term: FieldTerminator<T>,
form: Form, form: Form<T>,
) -> Result<MultipartContent, Error> { ) -> Result<MultipartContent<T>, Error>
where
T: 'static,
{
trace!("In handle_form_data, term: {:?}", term); trace!("In handle_form_data, term: {:?}", term);
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
@ -186,10 +192,13 @@ async fn handle_form_data(
} }
} }
async fn handle_stream_field( async fn handle_stream_field<T>(
field: actix_multipart::Field, field: actix_multipart::Field,
form: Form, form: Form<T>,
) -> Result<MultipartHash, Error> { ) -> Result<MultipartHash<T>, Error>
where
T: 'static,
{
let content_disposition = parse_content_disposition(&field); let content_disposition = parse_content_disposition(&field);
let name = content_disposition.name.ok_or(Error::Field)?; let name = content_disposition.name.ok_or(Error::Field)?;
@ -210,7 +219,10 @@ async fn handle_stream_field(
} }
/// Handle multipart streams from Actix Web /// Handle multipart streams from Actix Web
pub async fn handle_multipart(m: actix_multipart::Multipart, form: Form) -> Result<Value, Error> { pub async fn handle_multipart<T>(m: actix_multipart::Multipart, form: Form<T>) -> Result<Value<T>, Error>
where
T: 'static,
{
let mut multipart_form = Vec::new(); let mut multipart_form = Vec::new();
let mut file_count: u32 = 0; let mut file_count: u32 = 0;
let mut field_count: u32 = 0; let mut field_count: u32 = 0;
@ -244,11 +256,11 @@ pub async fn handle_multipart(m: actix_multipart::Multipart, form: Form) -> Resu
Ok(consolidate(multipart_form)) Ok(consolidate(multipart_form))
} }
fn count( fn count<T>(
content: &MultipartContent, content: &MultipartContent<T>,
mut file_count: u32, mut file_count: u32,
mut field_count: u32, mut field_count: u32,
form: &Form, form: &Form<T>,
) -> Result<(u32, u32), Error> { ) -> Result<(u32, u32), Error> {
match content { match content {
MultipartContent::File(_) => { MultipartContent::File(_) => {