Don't force smuggling of user's error inside actix_web::Error
This commit is contained in:
parent
38e6a890f3
commit
1e635ce27c
|
@ -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 <asonix@asonix.dog>"]
|
||||
repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git"
|
||||
|
|
|
@ -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<dyn std::error::Error + 'static>,
|
||||
}
|
||||
|
||||
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>
|
||||
})),
|
||||
);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ use tracing::info;
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
struct AppState {
|
||||
form: Form<PathBuf>,
|
||||
form: Form<PathBuf, Errors>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
|
@ -67,7 +67,7 @@ async fn upload(uploaded_content: Value<PathBuf>) -> HttpResponse {
|
|||
}
|
||||
|
||||
async fn save_file(
|
||||
stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
|
||||
stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error<Errors>>>>>,
|
||||
count: usize,
|
||||
) -> Result<String, JsonError> {
|
||||
use futures_lite::io::AsyncWriteExt;
|
||||
|
|
27
src/error.rs
27
src/error.rs
|
@ -30,9 +30,9 @@ use actix_web::{
|
|||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
pub enum Error<E> {
|
||||
#[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<MultipartError> for Error {
|
||||
impl<E> From<MultipartError> for Error<E> {
|
||||
fn from(m: MultipartError) -> Self {
|
||||
Error::Multipart(m)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
impl<E> ResponseError for Error<E>
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
//! while let Some(_) = stream.next().await {
|
||||
//! // do something
|
||||
//! }
|
||||
//! Ok(()) as Result<(), std::convert::Infallible>
|
||||
//! Ok(()) as Result<(), std::io::Error>
|
||||
//! })),
|
||||
//! );
|
||||
//!
|
||||
|
|
|
@ -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<T> {
|
||||
rx: Receiver<Value<T>>,
|
||||
}
|
||||
|
||||
pub struct MultipartMiddleware<S, T> {
|
||||
form: Form<T>,
|
||||
pub struct MultipartMiddleware<S, T, E> {
|
||||
form: Form<T, E>,
|
||||
service: S,
|
||||
}
|
||||
|
||||
|
@ -46,30 +70,31 @@ impl<T> FromRequest for Value<T>
|
|||
where
|
||||
T: 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Error = FromRequestError;
|
||||
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
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(Error::MissingMiddleware)?;
|
||||
let fut = opt.ok_or(FromRequestError::MissingMiddleware)?;
|
||||
|
||||
fut.rx.await.map_err(|_| Error::TxDropped)
|
||||
fut.rx.await.map_err(|_| FromRequestError::TxDropped)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Transform<S, ServiceRequest> for Form<T>
|
||||
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>;
|
||||
type Transform = MultipartMiddleware<S, T, E>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
|
@ -80,11 +105,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, T> Service<ServiceRequest> for MultipartMiddleware<S, T>
|
||||
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;
|
||||
|
|
126
src/types.rs
126
src/types.rs
|
@ -31,7 +31,7 @@ use std::{
|
|||
use tracing::trace;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileMeta<T = ()> {
|
||||
pub struct FileMeta<T> {
|
||||
pub filename: String,
|
||||
pub content_type: Mime,
|
||||
pub result: T,
|
||||
|
@ -56,7 +56,7 @@ pub struct FileMeta<T = ()> {
|
|||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub enum Value<T = ()> {
|
||||
pub enum Value<T> {
|
||||
Map(HashMap<String, Value<T>>),
|
||||
Array(Vec<Value<T>>),
|
||||
File(FileMeta<T>),
|
||||
|
@ -153,28 +153,28 @@ impl<T> From<MultipartContent<T>> for Value<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub type FileFn<T> = Arc<
|
||||
pub type FileFn<T, E> = Arc<
|
||||
dyn Fn(
|
||||
String,
|
||||
Mime,
|
||||
Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<T, actix_web::Error>>>>
|
||||
Pin<Box<dyn Stream<Item = Result<Bytes, Error<E>>>>>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<T, Error<E>>>>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
/// The field type represents a field in the form-data that is allowed to be parsed.
|
||||
pub enum Field<T = ()> {
|
||||
Array(Array<T>),
|
||||
Map(Map<T>),
|
||||
File(FileFn<T>),
|
||||
pub enum Field<T, E> {
|
||||
Array(Array<T, E>),
|
||||
Map(Map<T, E>),
|
||||
File(FileFn<T, E>),
|
||||
Bytes,
|
||||
Int,
|
||||
Float,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl<T> Clone for Field<T> {
|
||||
impl<T, E> Clone for Field<T, E> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Array(a) => Self::Array(a.clone()),
|
||||
|
@ -188,7 +188,7 @@ impl<T> Clone for Field<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Field<T> {
|
||||
impl<T, E> fmt::Debug for Field<T, E> {
|
||||
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<T> fmt::Debug for Field<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Field<T> {
|
||||
impl<T, E> Field<T, E> {
|
||||
/// 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<T> Field<T> {
|
|||
/// # 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<T> Field<T> {
|
|||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// Ok(()) as Result<_, std::convert::Infallible>
|
||||
/// Ok(()) as Result<_, std::io::Error>
|
||||
/// }
|
||||
/// }));
|
||||
/// ```
|
||||
pub fn file<F, Fut, E>(f: F) -> Self
|
||||
pub fn file<F, Fut>(f: F) -> Self
|
||||
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<E>>>>>) -> Fut
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ 'static,
|
||||
Fut: Future<Output = Result<T, E>> + 'static,
|
||||
E: Into<actix_web::Error> + '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<T> Field<T> {
|
|||
/// # 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<T> Field<T> {
|
|||
/// # 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<T> Field<T> {
|
|||
/// # 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<T> Field<T> {
|
|||
/// # 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<T> Field<T> {
|
|||
/// ```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<T>) -> Self {
|
||||
pub fn array(field: Field<T, E>) -> Self {
|
||||
Field::Array(Array::new(field))
|
||||
}
|
||||
|
||||
|
@ -311,7 +311,7 @@ impl<T> Field<T> {
|
|||
/// ```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<T> Field<T> {
|
|||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn map() -> Map<T> {
|
||||
pub fn map() -> Map<T, E> {
|
||||
Map::new()
|
||||
}
|
||||
|
||||
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
|
||||
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
|
||||
trace!("Checking {:?} and {:?}", self, name);
|
||||
match *self {
|
||||
Field::Array(ref arr) => arr.valid_field(name),
|
||||
|
@ -373,11 +373,11 @@ impl<T> Field<T> {
|
|||
///
|
||||
/// The `Array` type should only be constructed in the context of a Form. See the `Form`
|
||||
/// documentation for more information.
|
||||
pub struct Array<T = ()> {
|
||||
inner: Box<Field<T>>,
|
||||
pub struct Array<T, E> {
|
||||
inner: Box<Field<T, E>>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Array<T> {
|
||||
impl<T, E> Clone for Array<T, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Array {
|
||||
inner: Box::new((*self.inner).clone()),
|
||||
|
@ -385,20 +385,20 @@ impl<T> Clone for Array<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Array<T> {
|
||||
impl<T, E> fmt::Debug for Array<T, E> {
|
||||
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 {
|
||||
impl<T, E> Array<T, E> {
|
||||
fn new(field: Field<T, E>) -> Self {
|
||||
Array {
|
||||
inner: Box::new(field),
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
|
||||
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
|
||||
trace!("Checking {:?} and {:?}", self, name);
|
||||
match name.pop_front() {
|
||||
Some(NamePart::Array) => self.inner.valid_field(name),
|
||||
|
@ -408,11 +408,11 @@ impl<T> Array<T> {
|
|||
}
|
||||
|
||||
/// A definition of key-value pairs to be parsed from form data.
|
||||
pub struct Map<T = ()> {
|
||||
inner: Vec<(String, Field<T>)>,
|
||||
pub struct Map<T, E> {
|
||||
inner: Vec<(String, Field<T, E>)>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Map<T> {
|
||||
impl<T, E> Clone for Map<T, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Map {
|
||||
inner: self.inner.clone(),
|
||||
|
@ -420,13 +420,13 @@ impl<T> Clone for Map<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Map<T> {
|
||||
impl<T, E> fmt::Debug for Map<T, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Map").field("inner", &self.inner).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Map<T> {
|
||||
impl<T, E> Map<T, E> {
|
||||
fn new() -> Self {
|
||||
Map { inner: Vec::new() }
|
||||
}
|
||||
|
@ -436,12 +436,12 @@ impl<T> Map<T> {
|
|||
/// ```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<T>) -> Self {
|
||||
pub fn field(mut self, key: &str, value: Field<T, E>) -> Self {
|
||||
self.inner.push((key.to_owned(), value));
|
||||
|
||||
self
|
||||
|
@ -451,16 +451,16 @@ impl<T> Map<T> {
|
|||
/// ```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<T> {
|
||||
pub fn finalize(self) -> Field<T, E> {
|
||||
Field::Map(self)
|
||||
}
|
||||
|
||||
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
|
||||
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
|
||||
trace!("Checking {:?} and {:?}", self, name);
|
||||
match name.pop_front() {
|
||||
Some(NamePart::Map(name_part)) => self
|
||||
|
@ -478,12 +478,12 @@ impl<T> Map<T> {
|
|||
/// # 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<T> Map<T> {
|
|||
/// Field::array(Field::text())
|
||||
/// );
|
||||
/// ```
|
||||
pub struct Form<T = ()> {
|
||||
pub struct Form<T, E> {
|
||||
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<Arc<dyn Fn(crate::error::Error) -> actix_web::Error + Send + Sync>>,
|
||||
inner: Map<T>,
|
||||
pub(crate) transform_error: Option<Arc<dyn Fn(Error<E>) -> actix_web::Error + Send + Sync>>,
|
||||
inner: Map<T, E>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Form<T> {
|
||||
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: self.transform_error.clone(),
|
||||
transform_error: None,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Form<T> {
|
||||
impl<T, E> Default for Form<T, E> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Form<T> {
|
||||
impl<T, E> Form<T, E> {
|
||||
/// Create a new form
|
||||
///
|
||||
/// If you wish to provide your own executor, use the `with_executor` method.
|
||||
|
@ -547,13 +546,12 @@ impl<T> Form<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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<E>) -> actix_web::Error + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.transform_error = Some(Arc::new(f));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -593,18 +591,18 @@ impl<T> Form<T> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn field(mut self, name: &str, field: Field<T>) -> Self {
|
||||
pub fn field(mut self, name: &str, field: Field<T, E>) -> Self {
|
||||
self.inner = self.inner.field(name, field);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
|
||||
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
|
||||
self.inner.valid_field(name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Form<T> {
|
||||
impl<T, E> fmt::Debug for Form<T, E> {
|
||||
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<T = ()> {
|
||||
File(FileFn<T>),
|
||||
pub(crate) enum FieldTerminator<T, E> {
|
||||
File(FileFn<T, E>),
|
||||
Bytes,
|
||||
Int,
|
||||
Float,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for FieldTerminator<T> {
|
||||
impl<T, E> fmt::Debug for FieldTerminator<T, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
FieldTerminator::File(_) => write!(f, "File"),
|
||||
|
@ -662,7 +660,7 @@ pub(crate) type MultipartHash<T> = (Vec<NamePart>, MultipartContent<T>);
|
|||
pub(crate) type MultipartForm<T> = Vec<MultipartHash<T>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum MultipartContent<T = ()> {
|
||||
pub(crate) enum MultipartContent<T> {
|
||||
File(FileMeta<T>),
|
||||
Bytes(Bytes),
|
||||
Text(String),
|
||||
|
|
|
@ -65,7 +65,7 @@ fn consolidate<T>(mf: MultipartForm<T>) -> Value<T> {
|
|||
)
|
||||
}
|
||||
|
||||
fn parse_multipart_name(name: String) -> Result<Vec<NamePart>, Error> {
|
||||
fn parse_multipart_name<E>(name: String) -> Result<Vec<NamePart>, Error<E>> {
|
||||
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<T>(
|
||||
async fn handle_file_upload<T, E>(
|
||||
field: actix_multipart::Field,
|
||||
filename: Option<String>,
|
||||
form: Form<T>,
|
||||
file_fn: FileFn<T>,
|
||||
) -> Result<MultipartContent<T>, Error>
|
||||
form: Form<T, E>,
|
||||
file_fn: FileFn<T, E>,
|
||||
) -> Result<MultipartContent<T>, Error<E>>
|
||||
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<T>(
|
||||
async fn handle_form_data<T, E>(
|
||||
mut field: actix_multipart::Field,
|
||||
term: FieldTerminator<T>,
|
||||
form: Form<T>,
|
||||
) -> Result<MultipartContent<T>, Error>
|
||||
term: FieldTerminator<T, E>,
|
||||
form: Form<T, E>,
|
||||
) -> Result<MultipartContent<T>, Error<E>>
|
||||
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<T>(
|
||||
async fn handle_stream_field<T, E>(
|
||||
field: actix_multipart::Field,
|
||||
form: Form<T>,
|
||||
) -> Result<MultipartHash<T>, Error>
|
||||
form: Form<T, E>,
|
||||
) -> Result<MultipartHash<T>, Error<E>>
|
||||
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<T>(
|
||||
pub async fn handle_multipart<T, E>(
|
||||
m: actix_multipart::Multipart,
|
||||
form: Form<T>,
|
||||
) -> Result<Value<T>, Error>
|
||||
form: Form<T, E>,
|
||||
) -> Result<Value<T>, Error<E>>
|
||||
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<T>(
|
||||
fn count<T, E>(
|
||||
content: &MultipartContent<T>,
|
||||
mut file_count: u32,
|
||||
mut field_count: u32,
|
||||
form: &Form<T>,
|
||||
) -> Result<(u32, u32), Error> {
|
||||
form: &Form<T, E>,
|
||||
) -> Result<(u32, u32), Error<E>> {
|
||||
match content {
|
||||
MultipartContent::File(_) => {
|
||||
file_count += 1;
|
||||
|
|
Loading…
Reference in a new issue