/*
* This file is part of Actix Form Data.
*
* Copyright © 2020 Riley Trautman
*
* Actix Form Data is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Actix Form Data is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Actix Form Data. If not, see .
*/
use crate::Error;
use actix_web::web::Bytes;
use futures::Stream;
use mime::Mime;
use std::{
collections::{HashMap, VecDeque},
fmt,
future::Future,
pin::Pin,
sync::Arc,
};
use tracing::trace;
#[derive(Debug)]
pub struct FileMeta {
pub filename: String,
pub content_type: Mime,
pub result: T,
}
/// The result of a succesfull parse through a given multipart stream.
///
/// This type represents all possible variations in structure of a Multipart Form.
///
/// # Example usage
///
/// ```rust
/// # use actix_form_data::Value;
/// # use std::collections::HashMap;
/// # let mut hm = HashMap::new();
/// # hm.insert("field-name".to_owned(), Value::Int(32));
/// # let value = Value::<()>::Map(hm);
/// match value {
/// Value::Map(mut hashmap) => {
/// match hashmap.remove("field-name") {
/// Some(value) => match value {
/// Value::Int(integer) => println!("{}", integer),
/// _ => (),
/// }
/// None => (),
/// }
/// }
/// _ => (),
/// }
/// ```
#[derive(Debug)]
pub enum Value {
Map(HashMap>),
Array(Vec>),
File(FileMeta),
Bytes(Bytes),
Text(String),
Int(i64),
Float(f64),
}
impl Value {
pub(crate) fn merge(&mut self, rhs: Self) {
match self {
Value::Map(ref mut hm) => {
if let Value::Map(other) = rhs {
other.into_iter().fold(hm, |hm, (key, value)| {
if let Some(v) = hm.get_mut(&key) {
v.merge(value);
} else {
hm.insert(key.to_owned(), value);
}
hm
});
}
}
Value::Array(ref mut v) => {
if let Value::Array(other) = rhs {
v.extend(other);
}
}
_ => (),
}
}
pub fn map(self) -> Option>> {
match self {
Value::Map(map) => Some(map),
_ => None,
}
}
pub fn array(self) -> Option>> {
match self {
Value::Array(vec) => Some(vec),
_ => None,
}
}
pub fn file(self) -> Option> {
match self {
Value::File(file_meta) => Some(file_meta),
_ => None,
}
}
pub fn bytes(self) -> Option {
match self {
Value::Bytes(bytes) => Some(bytes),
_ => None,
}
}
pub fn text(self) -> Option {
match self {
Value::Text(text) => Some(text),
_ => None,
}
}
pub fn int(self) -> Option {
match self {
Value::Int(int) => Some(int),
_ => None,
}
}
pub fn float(self) -> Option {
match self {
Value::Float(float) => Some(float),
_ => None,
}
}
}
impl From> for Value {
fn from(mc: MultipartContent) -> Self {
match mc {
MultipartContent::File(file_meta) => Value::File(file_meta),
MultipartContent::Bytes(bytes) => Value::Bytes(bytes),
MultipartContent::Text(string) => Value::Text(string),
MultipartContent::Int(i) => Value::Int(i),
MultipartContent::Float(f) => Value::Float(f),
}
}
}
pub type FileFn = Arc<
dyn Fn(
String,
Mime,
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),
Bytes,
Int,
Float,
Text,
}
impl Clone for Field {
fn clone(&self) -> Self {
match self {
Self::Array(a) => Self::Array(a.clone()),
Self::Map(m) => Self::Map(m.clone()),
Self::File(file_fn) => Self::File(Arc::clone(&file_fn)),
Self::Bytes => Self::Bytes,
Self::Int => Self::Int,
Self::Float => Self::Float,
Self::Text => Self::Text,
}
}
}
impl fmt::Debug for Field {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Field::Array(ref arr) => f.debug_tuple("Array").field(arr).finish(),
Field::Map(ref map) => f.debug_tuple("Map").field(map).finish(),
Field::File(_) => write!(f, "File"),
Field::Bytes => write!(f, "Bytes"),
Field::Int => write!(f, "Int"),
Field::Float => write!(f, "Float"),
Field::Text => write!(f, "Text"),
}
}
}
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
/// that each key/file pair will have it's own name-generator, so sharing a name-generator
/// between fields is up to the user.
///
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// # use tokio::sync::mpsc::channel;
/// # use futures::stream::StreamExt;
/// #
/// let (tx, rx) = channel(1);
/// 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 {
/// if let Ok(bytes) = res {
/// if let Err(_) = tx.send(bytes).await {
/// break;
/// }
/// }
/// }
/// Ok(()) as Result<_, std::convert::Infallible>
/// }
/// }));
/// ```
pub fn file(f: F) -> Self
where
F: Fn(String, Mime, Pin>>>) -> Fut
+ Send
+ Sync
+ Clone
+ 'static,
Fut: Future