From 0c231119acef04c44d618de257cbf581c8096347 Mon Sep 17 00:00:00 2001 From: "Aode (lion)" Date: Sat, 11 Sep 2021 17:35:48 -0500 Subject: [PATCH] Allow non-path states from upload --- Cargo.toml | 2 +- README.md | 4 +- examples/simple.rs | 4 +- examples/upload.rs | 8 +- src/lib.rs | 4 +- src/middleware.rs | 23 +++--- src/types.rs | 182 +++++++++++++++++++++++++++++---------------- src/upload.rs | 48 +++++++----- 8 files changed, 171 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cbbadac..71de1c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "actix-form-data" description = "Multipart Form Data for Actix Web" -version = "0.6.0-beta.4" +version = "0.6.0-beta.5" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git" diff --git a/README.md b/README.md index 999d8d7..f3982c0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ use form_data::{Field, Form, Value}; #### Overview First, you'd create a form structure you want to parse from the multipart stream. ```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. @@ -69,7 +69,7 @@ use actix_web::{ }; use futures::stream::StreamExt; -async fn upload(uploaded_content: Value) -> HttpResponse { +async fn upload(uploaded_content: Value<()>) -> HttpResponse { println!("Uploaded Content: {:#?}", uploaded_content); HttpResponse::Created().finish() } diff --git a/examples/simple.rs b/examples/simple.rs index 58a6dae..2442306 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,7 +5,7 @@ use actix_web::{ }; use futures::stream::StreamExt; -async fn upload(uploaded_content: Value) -> HttpResponse { +async fn upload(uploaded_content: Value<()>) -> HttpResponse { println!("Uploaded Content: {:#?}", uploaded_content); HttpResponse::Created().finish() } @@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> { while let Some(res) = stream.next().await { res?; } - Ok(None) as Result<_, Error> + Ok(()) as Result<(), Error> })), ); diff --git a/examples/upload.rs b/examples/upload.rs index 1b48c85..eee8df6 100644 --- a/examples/upload.rs +++ b/examples/upload.rs @@ -8,6 +8,7 @@ use actix_web::{ use futures::stream::{Stream, StreamExt, TryStreamExt}; use std::{ env, + path::PathBuf, pin::Pin, sync::{ atomic::{AtomicUsize, Ordering}, @@ -18,7 +19,7 @@ use tracing::info; #[derive(Clone, Debug)] struct AppState { - form: Form, + form: Form, } #[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) -> HttpResponse { info!("Uploaded Content: {:#?}", uploaded_content); HttpResponse::Created().finish() @@ -111,8 +112,7 @@ async fn main() -> Result<(), anyhow::Error> { async move { save_file(stream, count) .await - .map(Into::into) - .map(Some) + .map(PathBuf::from) .map_err(Errors::from) } })), diff --git a/src/lib.rs b/src/lib.rs index fd70480..9bd5d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! }; //! use futures::stream::StreamExt; //! -//! async fn upload(uploaded_content: Value) -> HttpResponse { +//! async fn upload(uploaded_content: Value<()>) -> HttpResponse { //! println!("Uploaded Content: {:#?}", uploaded_content); //! HttpResponse::Created().finish() //! } @@ -54,7 +54,7 @@ //! while let Some(_) = stream.next().await { //! // do something //! } -//! Ok(None) as Result<_, std::convert::Infallible> +//! Ok(()) as Result<(), std::convert::Infallible> //! })), //! ); //! diff --git a/src/middleware.rs b/src/middleware.rs index 442c075..c2c3451 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -34,22 +34,25 @@ use std::{ }; use tokio::sync::oneshot::{channel, Receiver}; -struct Uploaded { - rx: Receiver, +struct Uploaded { + rx: Receiver>, } -pub struct MultipartMiddleware { - form: Form, +pub struct MultipartMiddleware { + form: Form, service: S, } -impl FromRequest for Value { +impl FromRequest for Value +where + T: 'static, + { type Error = Error; type Future = Pin>>>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - let opt = req.extensions_mut().remove::(); + let opt = req.extensions_mut().remove::>(); Box::pin(async move { let fut = opt.ok_or(Error::MissingMiddleware)?; @@ -58,15 +61,16 @@ impl FromRequest for Value { } } -impl Transform for Form +impl Transform for Form where S: Service, S::Future: 'static, + T: 'static, { type Response = S::Response; type Error = S::Error; type InitError = (); - type Transform = MultipartMiddleware; + type Transform = MultipartMiddleware; type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -77,10 +81,11 @@ where } } -impl Service for MultipartMiddleware +impl Service for MultipartMiddleware where S: Service, S::Future: 'static, + T: 'static, { type Response = S::Response; type Error = S::Error; diff --git a/src/types.rs b/src/types.rs index a35be65..caeebf8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,17 +25,16 @@ use std::{ collections::{HashMap, VecDeque}, fmt, future::Future, - path::PathBuf, pin::Pin, sync::Arc, }; use tracing::trace; #[derive(Debug)] -pub struct FileMeta { +pub struct FileMeta { pub filename: String, pub content_type: Mime, - pub saved_as: Option, + pub result: T, } /// The result of a succesfull parse through a given multipart stream. @@ -49,7 +48,7 @@ pub struct FileMeta { /// # use std::collections::HashMap; /// # let mut hm = HashMap::new(); /// # hm.insert("field-name".to_owned(), Value::Int(32)); -/// # let value = Value::Map(hm); +/// # let value = Value::<()>::Map(hm); /// match value { /// Value::Map(mut hashmap) => { /// match hashmap.remove("field-name") { @@ -64,17 +63,17 @@ pub struct FileMeta { /// } /// ``` #[derive(Debug)] -pub enum Value { - Map(HashMap), - Array(Vec), - File(FileMeta), +pub enum Value { + Map(HashMap>), + Array(Vec>), + File(FileMeta), Bytes(Bytes), Text(String), Int(i64), Float(f64), } -impl Value { +impl Value { pub(crate) fn merge(&mut self, rhs: Self) { match self { Value::Map(ref mut hm) => { @@ -99,21 +98,21 @@ impl Value { } } - pub fn map(self) -> Option> { + pub fn map(self) -> Option>> { match self { Value::Map(map) => Some(map), _ => None, } } - pub fn array(self) -> Option> { + pub fn array(self) -> Option>> { match self { Value::Array(vec) => Some(vec), _ => None, } } - pub fn file(self) -> Option { + pub fn file(self) -> Option> { match self { Value::File(file_meta) => Some(file_meta), _ => None, @@ -149,8 +148,8 @@ impl Value { } } -impl From for Value { - fn from(mc: MultipartContent) -> Self { +impl From> for Value { + fn from(mc: MultipartContent) -> Self { match mc { MultipartContent::File(file_meta) => Value::File(file_meta), MultipartContent::Bytes(bytes) => Value::Bytes(bytes), @@ -161,29 +160,42 @@ impl From for Value { } } -pub type FileFn = Arc< +pub type FileFn = Arc< dyn Fn( String, Mime, Pin>>>, - ) -> Pin, actix_web::Error>>>> + ) -> Pin>>> + Send + Sync, >; /// The field type represents a field in the form-data that is allowed to be parsed. -#[derive(Clone)] -pub enum Field { - Array(Array), - Map(Map), - File(FileFn), +pub enum Field { + Array(Array), + Map(Map), + File(FileFn), Bytes, Int, Float, Text, } -impl fmt::Debug for Field { +impl Clone for Field { + 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 fmt::Debug for Field { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Field::Array(ref arr) => f.debug_tuple("Array").field(arr).finish(), @@ -197,7 +209,7 @@ impl fmt::Debug for Field { } } -impl Field { +impl Field { /// 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 @@ -211,7 +223,7 @@ impl Field { /// # use futures::stream::StreamExt; /// # /// 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(); /// async move { /// 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 + Clone + 'static, - Fut: Future, E>> + 'static, + Fut: Future> + 'static, E: Into + 'static, { Field::File(Arc::new(move |filename, mime, stream| { @@ -246,7 +258,7 @@ impl Field { /// # Example /// ```rust /// # 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 { Field::Bytes } @@ -256,7 +268,7 @@ impl Field { /// # Example /// ```rust /// # 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 { Field::Text } @@ -266,7 +278,7 @@ impl Field { /// # Example /// ```rust /// # 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 { Field::Int @@ -277,7 +289,7 @@ impl Field { /// # Example /// ```rust /// # 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 { Field::Float @@ -289,14 +301,14 @@ impl Field { /// ```rust /// # use actix_form_data::{Form, Field}; /// # fn main() { - /// let form = Form::new() + /// let form = Form::<()>::new() /// .field( /// "array-field", /// Field::array(Field::text()) /// ); /// # } /// ``` - pub fn array(field: Field) -> Self { + pub fn array(field: Field) -> Self { Field::Array(Array::new(field)) } @@ -306,7 +318,7 @@ impl Field { /// ```rust /// # use actix_form_data::{Form, Field}; /// # fn main() { - /// let form = Form::new() + /// let form = Form::<()>::new() /// .field( /// "map-field", /// Field::map() @@ -316,11 +328,11 @@ impl Field { /// ); /// # } /// ``` - pub fn map() -> Map { + pub fn map() -> Map { Map::new() } - fn valid_field(&self, name: VecDeque<&NamePart>) -> Option { + fn valid_field(&self, name: VecDeque<&NamePart>) -> Option> { trace!("Checking {:?} and {:?}", self, name); match *self { 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` /// documentation for more information. -#[derive(Debug, Clone)] -pub struct Array { - inner: Box, +pub struct Array { + inner: Box>, } -impl Array { - fn new(field: Field) -> Self { +impl Clone for Array { + fn clone(&self) -> Self { + Array { inner: Box::new((*self.inner).clone()) } + } +} + +impl fmt::Debug for Array { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Array") + .field("inner", &self.inner) + .finish() + } +} + +impl Array { + fn new(field: Field) -> Self { Array { inner: Box::new(field), } } - fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option { + fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option> { trace!("Checking {:?} and {:?}", self, name); match name.pop_front() { Some(name_part) => match name_part { @@ -393,12 +418,25 @@ impl Array { } /// A definition of key-value pairs to be parsed from form data. -#[derive(Debug, Clone)] -pub struct Map { - inner: Vec<(String, Field)>, +pub struct Map { + inner: Vec<(String, Field)>, } -impl Map { +impl Clone for Map { + fn clone(&self) -> Self { + Map { inner: self.inner.clone() } + } +} + +impl fmt::Debug for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Map") + .field("inner", &self.inner) + .finish() + } +} + +impl Map { fn new() -> Self { Map { inner: Vec::new() } } @@ -408,12 +446,12 @@ impl Map { /// ```rust /// # use actix_form_data::Field; /// # - /// Field::map() + /// Field::<()>::map() /// .field("sub-field", Field::text()) /// .field("sub-field-two", Field::text()) /// .finalize(); /// ``` - pub fn field(mut self, key: &str, value: Field) -> Self { + pub fn field(mut self, key: &str, value: Field) -> Self { self.inner.push((key.to_owned(), value)); self @@ -423,16 +461,16 @@ impl Map { /// ```rust /// # use actix_form_data::Field; /// # - /// Field::map() + /// Field::<()>::map() /// .field("sub-field", Field::text()) /// .field("sub-field-two", Field::text()) /// .finalize(); /// ``` - pub fn finalize(self) -> Field { + pub fn finalize(self) -> Field { Field::Map(self) } - fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option { + fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option> { trace!("Checking {:?} and {:?}", self, name); match name.pop_front() { Some(name_part) => match name_part { @@ -453,12 +491,12 @@ impl Map { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; -/// let form = Form::new() +/// let form = Form::<()>::new() /// .field("field-name", Field::text()) /// .field("second-field", Field::int()) /// .field("third-field", Field::float()) /// .field("fifth-field", Field::file(|_, _, _| async move { -/// Ok(None) as Result<_, std::convert::Infallible> +/// Ok(()) as Result<(), std::convert::Infallible> /// })) /// .field( /// "map-field", @@ -472,18 +510,30 @@ impl Map { /// Field::array(Field::text()) /// ); /// ``` -#[derive(Clone)] -pub struct Form { +pub struct Form { pub(crate) max_fields: u32, pub(crate) max_field_size: usize, pub(crate) max_files: u32, pub(crate) max_file_size: usize, pub(crate) transform_error: Option actix_web::Error + Send + Sync>>, - inner: Map, + inner: Map, } -impl Form { +impl Clone for Form { + 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 Form { /// Create a new form /// /// If you wish to provide your own executor, use the `with_executor` method. @@ -550,18 +600,18 @@ impl Form { self } - pub fn field(mut self, name: &str, field: Field) -> Self { + pub fn field(mut self, name: &str, field: Field) -> Self { self.inner = self.inner.field(name, field); self } - pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option { + pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option> { self.inner.valid_field(name.clone()) } } -impl fmt::Debug for Form { +impl fmt::Debug for Form { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Form").field("inner", &self.inner).finish() } @@ -598,15 +648,15 @@ impl NamePart { } #[derive(Clone)] -pub(crate) enum FieldTerminator { - File(FileFn), +pub(crate) enum FieldTerminator { + File(FileFn), Bytes, Int, Float, Text, } -impl fmt::Debug for FieldTerminator { +impl fmt::Debug for FieldTerminator { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { FieldTerminator::File(_) => write!(f, "File"), @@ -618,12 +668,12 @@ impl fmt::Debug for FieldTerminator { } } -pub(crate) type MultipartHash = (Vec, MultipartContent); -pub(crate) type MultipartForm = Vec; +pub(crate) type MultipartHash = (Vec, MultipartContent); +pub(crate) type MultipartForm = Vec>; #[derive(Debug)] -pub(crate) enum MultipartContent { - File(FileMeta), +pub(crate) enum MultipartContent { + File(FileMeta), Bytes(Bytes), Text(String), Int(i64), diff --git a/src/upload.rs b/src/upload.rs index f753110..73da014 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -39,7 +39,7 @@ use std::{ }; use tracing::trace; -fn consolidate(mf: MultipartForm) -> Value { +fn consolidate(mf: MultipartForm) -> Value { mf.into_iter().fold( Value::Map(HashMap::new()), |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( field: actix_multipart::Field, filename: Option, - form: Form, - file_fn: FileFn, -) -> Result { + form: Form, + file_fn: FileFn, +) -> Result, Error> +where + T: 'static, +{ let filename = filename.ok_or(Error::Filename)?; let path: &Path = filename.as_ref(); @@ -116,7 +119,7 @@ async fn handle_file_upload( let content_type = field.content_type().clone(); - let saved_as = file_fn( + let result = file_fn( filename.clone(), content_type.clone(), Box::pin(field.then(move |res| { @@ -143,15 +146,18 @@ async fn handle_file_upload( Ok(MultipartContent::File(FileMeta { filename, content_type, - saved_as, + result, })) } -async fn handle_form_data( +async fn handle_form_data( mut field: actix_multipart::Field, - term: FieldTerminator, - form: Form, -) -> Result { + term: FieldTerminator, + form: Form, +) -> Result, Error> +where + T: 'static, +{ trace!("In handle_form_data, term: {:?}", term); let mut bytes = BytesMut::new(); @@ -186,10 +192,13 @@ async fn handle_form_data( } } -async fn handle_stream_field( +async fn handle_stream_field( field: actix_multipart::Field, - form: Form, -) -> Result { + form: Form, +) -> Result, Error> +where + T: 'static, +{ let content_disposition = parse_content_disposition(&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 -pub async fn handle_multipart(m: actix_multipart::Multipart, form: Form) -> Result { +pub async fn handle_multipart(m: actix_multipart::Multipart, form: Form) -> Result, Error> +where + T: 'static, +{ let mut multipart_form = Vec::new(); let mut file_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)) } -fn count( - content: &MultipartContent, +fn count( + content: &MultipartContent, mut file_count: u32, mut field_count: u32, - form: &Form, + form: &Form, ) -> Result<(u32, u32), Error> { match content { MultipartContent::File(_) => {