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]
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 <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git"

View file

@ -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()
}

View file

@ -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>
})),
);

View file

@ -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<PathBuf>,
}
#[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);
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)
}
})),

View file

@ -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>
//! })),
//! );
//!

View file

@ -34,22 +34,25 @@ use std::{
};
use tokio::sync::oneshot::{channel, Receiver};
struct Uploaded {
rx: Receiver<Value>,
struct Uploaded<T> {
rx: Receiver<Value<T>>,
}
pub struct MultipartMiddleware<S> {
form: Form,
pub struct MultipartMiddleware<S, T> {
form: Form<T>,
service: S,
}
impl FromRequest for Value {
impl<T> FromRequest for Value<T>
where
T: 'static,
{
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
type Config = ();
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 {
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
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
{
type Response = S::Response;
type Error = S::Error;
type InitError = ();
type Transform = MultipartMiddleware<S>;
type Transform = MultipartMiddleware<S, T>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
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
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
{
type Response = S::Response;
type Error = S::Error;

View file

@ -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<T = ()> {
pub filename: String,
pub content_type: Mime,
pub saved_as: Option<PathBuf>,
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<String, Value>),
Array(Vec<Value>),
File(FileMeta),
pub enum Value<T = ()> {
Map(HashMap<String, Value<T>>),
Array(Vec<Value<T>>),
File(FileMeta<T>),
Bytes(Bytes),
Text(String),
Int(i64),
Float(f64),
}
impl Value {
impl<T> Value<T> {
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<HashMap<String, Value>> {
pub fn map(self) -> Option<HashMap<String, Value<T>>> {
match self {
Value::Map(map) => Some(map),
_ => None,
}
}
pub fn array(self) -> Option<Vec<Value>> {
pub fn array(self) -> Option<Vec<Value<T>>> {
match self {
Value::Array(vec) => Some(vec),
_ => None,
}
}
pub fn file(self) -> Option<FileMeta> {
pub fn file(self) -> Option<FileMeta<T>> {
match self {
Value::File(file_meta) => Some(file_meta),
_ => None,
@ -149,8 +148,8 @@ impl Value {
}
}
impl From<MultipartContent> for Value {
fn from(mc: MultipartContent) -> Self {
impl<T> From<MultipartContent<T>> for Value<T> {
fn from(mc: MultipartContent<T>) -> Self {
match mc {
MultipartContent::File(file_meta) => Value::File(file_meta),
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(
String,
Mime,
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
+ 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<T = ()> {
Array(Array<T>),
Map(Map<T>),
File(FileFn<T>),
Bytes,
Int,
Float,
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 {
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<T> Field<T> {
/// 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<Output = Result<Option<PathBuf>, E>> + 'static,
Fut: Future<Output = Result<T, E>> + 'static,
E: Into<actix_web::Error> + '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<T>) -> 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<T> {
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);
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<Field>,
pub struct Array<T = ()> {
inner: Box<Field<T>>,
}
impl Array {
fn new(field: Field) -> Self {
impl<T> Clone for Array<T> {
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 {
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);
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<T = ()> {
inner: Vec<(String, Field<T>)>,
}
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 {
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<T>) -> 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<T> {
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);
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<T = ()> {
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,
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
///
/// 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<T>) -> Self {
self.inner = self.inner.field(name, field);
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())
}
}
impl fmt::Debug for Form {
impl<T> fmt::Debug for Form<T> {
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<T = ()> {
File(FileFn<T>),
Bytes,
Int,
Float,
Text,
}
impl fmt::Debug for FieldTerminator {
impl<T> fmt::Debug for FieldTerminator<T> {
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<NamePart>, MultipartContent);
pub(crate) type MultipartForm = Vec<MultipartHash>;
pub(crate) type MultipartHash<T> = (Vec<NamePart>, MultipartContent<T>);
pub(crate) type MultipartForm<T> = Vec<MultipartHash<T>>;
#[derive(Debug)]
pub(crate) enum MultipartContent {
File(FileMeta),
pub(crate) enum MultipartContent<T = ()> {
File(FileMeta<T>),
Bytes(Bytes),
Text(String),
Int(i64),

View file

@ -39,7 +39,7 @@ use std::{
};
use tracing::trace;
fn consolidate(mf: MultipartForm) -> Value {
fn consolidate<T>(mf: MultipartForm<T>) -> Value<T> {
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<T>(
field: actix_multipart::Field,
filename: Option<String>,
form: Form,
file_fn: FileFn,
) -> Result<MultipartContent, Error> {
form: Form<T>,
file_fn: FileFn<T>,
) -> Result<MultipartContent<T>, 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<T>(
mut field: actix_multipart::Field,
term: FieldTerminator,
form: Form,
) -> Result<MultipartContent, Error> {
term: FieldTerminator<T>,
form: Form<T>,
) -> Result<MultipartContent<T>, 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<T>(
field: actix_multipart::Field,
form: Form,
) -> Result<MultipartHash, Error> {
form: Form<T>,
) -> Result<MultipartHash<T>, 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<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 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<T>(
content: &MultipartContent<T>,
mut file_count: u32,
mut field_count: u32,
form: &Form,
form: &Form<T>,
) -> Result<(u32, u32), Error> {
match content {
MultipartContent::File(_) => {