Allow non-path states from upload
This commit is contained in:
parent
bdad3f5145
commit
0c231119ac
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -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>
|
||||||
//! })),
|
//! })),
|
||||||
//! );
|
//! );
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -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;
|
||||||
|
|
182
src/types.rs
182
src/types.rs
|
@ -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),
|
||||||
|
|
|
@ -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(_) => {
|
||||||
|
|
Loading…
Reference in a new issue