diff --git a/Cargo.toml b/Cargo.toml index d9d4e3c..614d4c1 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.7" +version = "0.6.0-beta.8" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git" diff --git a/examples/simple.rs b/examples/simple.rs index 536fe1a..4db370b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,10 +1,24 @@ -use actix_form_data::{Error, Field, Form, Value}; +use actix_form_data::{Field, Form, Value}; use actix_web::{ web::{post, resource}, - App, HttpResponse, HttpServer, + App, HttpResponse, HttpServer, ResponseError, }; use futures_util::stream::StreamExt; +#[derive(Debug, thiserror::Error)] +#[error("{inner}")] +struct MyError { + inner: Box, +} + +impl MyError { + fn new(e: impl std::error::Error + 'static) -> Self { + Self { inner: Box::new(e) } + } +} + +impl ResponseError for MyError {} + async fn upload(uploaded_content: Value<()>) -> HttpResponse { println!("Uploaded Content: {:#?}", uploaded_content); HttpResponse::Created().finish() @@ -25,9 +39,9 @@ async fn main() -> Result<(), anyhow::Error> { "files", Field::array(Field::file(|_, _, mut stream| async move { while let Some(res) = stream.next().await { - res?; + res.map_err(MyError::new)?; } - Ok(()) as Result<(), Error> + Ok(()) as Result<(), MyError> })), ); diff --git a/examples/upload.rs b/examples/upload.rs index 192abdc..aad8815 100644 --- a/examples/upload.rs +++ b/examples/upload.rs @@ -19,7 +19,7 @@ use tracing::info; #[derive(Clone, Debug)] struct AppState { - form: Form, + form: Form, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -67,7 +67,7 @@ async fn upload(uploaded_content: Value) -> HttpResponse { } async fn save_file( - stream: Pin>>>, + stream: Pin>>>>, count: usize, ) -> Result { use futures_lite::io::AsyncWriteExt; diff --git a/src/error.rs b/src/error.rs index 0c4067b..76533be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,9 +30,9 @@ use actix_web::{ }; #[derive(Debug, thiserror::Error)] -pub enum Error { +pub enum Error { #[error("{0}")] - FileFn(#[from] actix_web::Error), + FileFn(E), #[error("Error parsing payload, {0}")] Payload(#[from] PayloadError), #[error("Error in multipart creation, {0}")] @@ -61,32 +61,30 @@ pub enum Error { FileCount, #[error("File too large")] FileSize, - #[error("Uploaded guard used without Multipart middleware")] - MissingMiddleware, - #[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")] - TxDropped, } -impl From for Error { +impl From for Error { fn from(m: MultipartError) -> Self { Error::Multipart(m) } } -impl ResponseError for Error { +impl ResponseError for Error +where + E: ResponseError, +{ fn status_code(&self) -> StatusCode { match *self { - Error::FileFn(ref e) => ResponseError::status_code(e.as_response_error()), - Error::Payload(ref e) => ResponseError::status_code(e), - Error::MissingMiddleware | Error::TxDropped => StatusCode::INTERNAL_SERVER_ERROR, + Error::FileFn(ref e) => e.status_code(), + Error::Payload(ref e) => e.status_code(), _ => StatusCode::BAD_REQUEST, } } fn error_response(&self) -> HttpResponse { match *self { - Error::FileFn(ref e) => ResponseError::error_response(e.as_response_error()), - Error::Payload(ref e) => ResponseError::error_response(e), + Error::FileFn(ref e) => e.error_response(), + Error::Payload(ref e) => e.error_response(), Error::Multipart(_) | Error::ParseField(_) | Error::ParseInt(_) @@ -100,9 +98,6 @@ impl ResponseError for Error { | Error::Filename | Error::FileCount | Error::FileSize => HttpResponse::BadRequest().finish(), - Error::MissingMiddleware | Error::TxDropped => { - HttpResponse::InternalServerError().finish() - } } } } diff --git a/src/lib.rs b/src/lib.rs index caa96ec..f9f4e26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ //! while let Some(_) = stream.next().await { //! // do something //! } -//! Ok(()) as Result<(), std::convert::Infallible> +//! Ok(()) as Result<(), std::io::Error> //! })), //! ); //! diff --git a/src/middleware.rs b/src/middleware.rs index 26e9e85..98e20c7 100644 --- a/src/middleware.rs +++ b/src/middleware.rs @@ -18,13 +18,13 @@ */ use crate::{ - error::Error, types::{Form, Value}, upload::handle_multipart, }; use actix_web::{ dev::{Payload, Service, ServiceRequest, Transform}, - FromRequest, HttpMessage, HttpRequest, + http::StatusCode, + FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, }; use futures_util::future::LocalBoxFuture; use std::{ @@ -33,12 +33,36 @@ use std::{ }; 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 { rx: Receiver>, } -pub struct MultipartMiddleware { - form: Form, +pub struct MultipartMiddleware { + form: Form, service: S, } @@ -46,30 +70,31 @@ impl FromRequest for Value where T: 'static, { - type Error = Error; + type Error = FromRequestError; type Future = LocalBoxFuture<'static, Result>; type Config = (); fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let opt = req.extensions_mut().remove::>(); Box::pin(async move { - let fut = opt.ok_or(Error::MissingMiddleware)?; + let fut = opt.ok_or(FromRequestError::MissingMiddleware)?; - fut.rx.await.map_err(|_| Error::TxDropped) + fut.rx.await.map_err(|_| FromRequestError::TxDropped) }) } } -impl Transform for Form +impl Transform for Form where S: Service, S::Future: 'static, T: 'static, + E: ResponseError + '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 { @@ -80,11 +105,12 @@ where } } -impl Service for MultipartMiddleware +impl Service for MultipartMiddleware where S: Service, S::Future: 'static, T: 'static, + E: ResponseError + 'static, { type Response = S::Response; type Error = S::Error; diff --git a/src/types.rs b/src/types.rs index c56c3bd..4a9e8f1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -31,7 +31,7 @@ use std::{ use tracing::trace; #[derive(Debug)] -pub struct FileMeta { +pub struct FileMeta { pub filename: String, pub content_type: Mime, pub result: T, @@ -56,7 +56,7 @@ pub struct FileMeta { /// } /// ``` #[derive(Debug)] -pub enum Value { +pub enum Value { Map(HashMap>), Array(Vec>), File(FileMeta), @@ -153,28 +153,28 @@ impl From> for Value { } } -pub type FileFn = Arc< +pub type FileFn = Arc< dyn Fn( String, Mime, - Pin>>>, - ) -> Pin>>> + Pin>>>>, + ) -> Pin>>>> + Send + Sync, >; /// The field type represents a field in the form-data that is allowed to be parsed. -pub enum Field { - Array(Array), - Map(Map), - File(FileFn), +pub enum Field { + Array(Array), + Map(Map), + File(FileFn), Bytes, Int, Float, Text, } -impl Clone for Field { +impl Clone for Field { fn clone(&self) -> Self { match self { Self::Array(a) => Self::Array(a.clone()), @@ -188,7 +188,7 @@ impl Clone for Field { } } -impl fmt::Debug for Field { +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(), @@ -202,7 +202,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 @@ -216,7 +216,7 @@ impl Field { /// # use futures_util::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 { @@ -226,23 +226,23 @@ impl Field { /// } /// } /// } - /// Ok(()) as Result<_, std::convert::Infallible> + /// Ok(()) as Result<_, std::io::Error> /// } /// })); /// ``` - pub fn file(f: F) -> Self + pub fn file(f: F) -> Self where - F: Fn(String, Mime, Pin>>>) -> Fut + F: Fn(String, Mime, Pin>>>>) -> Fut + Send + Sync + Clone + 'static, Fut: Future> + 'static, - E: Into + 'static, + E: 'static, { Field::File(Arc::new(move |filename, mime, stream| { let f = f.clone(); - Box::pin(async move { (f)(filename, mime, stream).await.map_err(|e| e.into()) }) + Box::pin(async move { (f)(filename, mime, stream).await.map_err(Error::FileFn) }) })) } @@ -251,7 +251,7 @@ impl Field { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; - /// let form = Form::<()>::new().field("text-field", Field::bytes()); + /// let form = Form::<(), std::io::Error>::new().field("text-field", Field::bytes()); pub fn bytes() -> Self { Field::Bytes } @@ -261,7 +261,7 @@ impl Field { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; - /// let form = Form::<()>::new().field("text-field", Field::text()); + /// let form = Form::<(), std::io::Error>::new().field("text-field", Field::text()); pub fn text() -> Self { Field::Text } @@ -271,7 +271,7 @@ impl Field { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; - /// let form = Form::<()>::new().field("int-field", Field::int()); + /// let form = Form::<(), std::io::Error>::new().field("int-field", Field::int()); /// ``` pub fn int() -> Self { Field::Int @@ -282,7 +282,7 @@ impl Field { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; - /// let form = Form::<()>::new().field("float-field", Field::float()); + /// let form = Form::<(), std::io::Error>::new().field("float-field", Field::float()); /// ``` pub fn float() -> Self { Field::Float @@ -294,14 +294,14 @@ impl Field { /// ```rust /// # use actix_form_data::{Form, Field}; /// # fn main() { - /// let form = Form::<()>::new() + /// let form = Form::<(), std::io::Error>::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)) } @@ -311,7 +311,7 @@ impl Field { /// ```rust /// # use actix_form_data::{Form, Field}; /// # fn main() { - /// let form = Form::<()>::new() + /// let form = Form::<(), std::io::Error>::new() /// .field( /// "map-field", /// Field::map() @@ -321,11 +321,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), @@ -373,11 +373,11 @@ impl Field { /// /// The `Array` type should only be constructed in the context of a Form. See the `Form` /// documentation for more information. -pub struct Array { - inner: Box>, +pub struct Array { + inner: Box>, } -impl Clone for Array { +impl Clone for Array { fn clone(&self) -> Self { Array { inner: Box::new((*self.inner).clone()), @@ -385,20 +385,20 @@ impl Clone for Array { } } -impl fmt::Debug for Array { +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 { +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(NamePart::Array) => self.inner.valid_field(name), @@ -408,11 +408,11 @@ impl Array { } /// A definition of key-value pairs to be parsed from form data. -pub struct Map { - inner: Vec<(String, Field)>, +pub struct Map { + inner: Vec<(String, Field)>, } -impl Clone for Map { +impl Clone for Map { fn clone(&self) -> Self { Map { inner: self.inner.clone(), @@ -420,13 +420,13 @@ impl Clone for Map { } } -impl fmt::Debug for Map { +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 { +impl Map { fn new() -> Self { Map { inner: Vec::new() } } @@ -436,12 +436,12 @@ impl Map { /// ```rust /// # use actix_form_data::Field; /// # - /// Field::<()>::map() + /// Field::<(), std::io::Error>::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 @@ -451,16 +451,16 @@ impl Map { /// ```rust /// # use actix_form_data::Field; /// # - /// Field::<()>::map() + /// Field::<(), std::io::Error>::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(NamePart::Map(name_part)) => self @@ -478,12 +478,12 @@ impl Map { /// # Example /// ```rust /// # use actix_form_data::{Form, Field}; -/// let form = Form::<()>::new() +/// let form = Form::<(), std::io::Error>::new() /// .field("field-name", Field::text()) /// .field("second-field", Field::int()) /// .field("third-field", Field::float()) /// .field("fifth-field", Field::file(|_, _, _| async move { -/// Ok(()) as Result<(), std::convert::Infallible> +/// Ok(()) /// })) /// .field( /// "map-field", @@ -497,36 +497,35 @@ impl Map { /// Field::array(Field::text()) /// ); /// ``` -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, + pub(crate) transform_error: Option) -> actix_web::Error + Send + Sync>>, + inner: Map, } -impl Clone for 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(), + transform_error: None, inner: self.inner.clone(), } } } -impl Default for Form { +impl Default for Form { fn default() -> Self { Self::new() } } -impl Form { +impl Form { /// Create a new form /// /// If you wish to provide your own executor, use the `with_executor` method. @@ -547,13 +546,12 @@ impl Form { } } - /// Add an optional error handler to transform errors produced by the middleware + /// Set the Transform Error method to convert Error types into actix_web::Error by hand pub fn transform_error( mut self, - f: impl Fn(crate::error::Error) -> actix_web::Error + Send + Sync + 'static, + f: impl Fn(Error) -> actix_web::Error + Send + Sync + 'static, ) -> Self { self.transform_error = Some(Arc::new(f)); - self } @@ -593,18 +591,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() } @@ -638,15 +636,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"), @@ -662,7 +660,7 @@ pub(crate) type MultipartHash = (Vec, MultipartContent); pub(crate) type MultipartForm = Vec>; #[derive(Debug)] -pub(crate) enum MultipartContent { +pub(crate) enum MultipartContent { File(FileMeta), Bytes(Bytes), Text(String), diff --git a/src/upload.rs b/src/upload.rs index 9dc2650..408ad9e 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -65,7 +65,7 @@ fn consolidate(mf: MultipartForm) -> Value { ) } -fn parse_multipart_name(name: String) -> Result, Error> { +fn parse_multipart_name(name: String) -> Result, Error> { name.split('[') .map(|part| { if part.len() == 1 && part.ends_with(']') { @@ -99,14 +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, Error> + form: Form, + file_fn: FileFn, +) -> Result, Error> where T: 'static, + E: 'static, { let filename = filename.ok_or(Error::Filename)?; let path: &Path = filename.as_ref(); @@ -150,13 +151,14 @@ where })) } -async fn handle_form_data( +async fn handle_form_data( mut field: actix_multipart::Field, - term: FieldTerminator, - form: Form, -) -> Result, Error> + term: FieldTerminator, + form: Form, +) -> Result, Error> where T: 'static, + E: 'static, { trace!("In handle_form_data, term: {:?}", term); let mut bytes = BytesMut::new(); @@ -190,12 +192,13 @@ where } } -async fn handle_stream_field( +async fn handle_stream_field( field: actix_multipart::Field, - form: Form, -) -> Result, Error> + form: Form, +) -> Result, Error> where T: 'static, + E: 'static, { let content_disposition = parse_content_disposition(&field); @@ -217,12 +220,13 @@ where } /// Handle multipart streams from Actix Web -pub async fn handle_multipart( +pub async fn handle_multipart( m: actix_multipart::Multipart, - form: Form, -) -> Result, Error> + form: Form, +) -> Result, Error> where T: 'static, + E: 'static, { let mut multipart_form = Vec::new(); let mut file_count: u32 = 0; @@ -257,12 +261,12 @@ where Ok(consolidate(multipart_form)) } -fn count( +fn count( content: &MultipartContent, mut file_count: u32, mut field_count: u32, - form: &Form, -) -> Result<(u32, u32), Error> { + form: &Form, +) -> Result<(u32, u32), Error> { match content { MultipartContent::File(_) => { file_count += 1;