mirror of https://github.com/fafhrd91/actix-web
rename Tempfile -> TempFile
This commit is contained in:
parent
8578dadbb2
commit
facc43e5ad
|
@ -3,13 +3,12 @@
|
||||||
## Unreleased - 2022-xx-xx
|
## Unreleased - 2022-xx-xx
|
||||||
- Added `MultipartForm` typed data extractor. [#2883]
|
- Added `MultipartForm` typed data extractor. [#2883]
|
||||||
|
|
||||||
[#2880]: https://github.com/actix/actix-web/pull/2880
|
|
||||||
[#2883]: https://github.com/actix/actix-web/pull/2883
|
[#2883]: https://github.com/actix/actix-web/pull/2883
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0 - 2023-01-21
|
## 0.5.0 - 2023-01-21
|
||||||
|
- `Field::content_type()` now returns `Option<&mime::Mime>`. [#2885]
|
||||||
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
|
||||||
- `Field::content_type()` now returns `Option<&mime::Mime>` [#2885]
|
|
||||||
|
|
||||||
[#2885]: https://github.com/actix/actix-web/pull/2885
|
[#2885]: https://github.com/actix/actix-web/pull/2885
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "actix-multipart"
|
name = "actix-multipart"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Jacob Halsey <jacob@jhalsey.com>",
|
||||||
|
]
|
||||||
description = "Multipart form support for Actix Web"
|
description = "Multipart form support for Actix Web"
|
||||||
keywords = ["http", "web", "framework", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://actix.rs"
|
homepage = "https://actix.rs"
|
||||||
|
@ -30,6 +33,7 @@ actix-web = { version = "4", default-features = false }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
|
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
local-waker = "0.1"
|
local-waker = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -39,15 +43,15 @@ serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_plain = "1"
|
serde_plain = "1"
|
||||||
# TODO(MSRV 1.60): replace with dep: prefix
|
# TODO(MSRV 1.60): replace with dep: prefix
|
||||||
tempfile-dep = { package = "tempfile", version = "3.3.0", optional = true }
|
tempfile-dep = { package = "tempfile", version = "3.4", optional = true }
|
||||||
tokio = { version = "1.13.1", features = ["sync"] }
|
tokio = { version = "1.18.5", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = "3"
|
actix-http = "3"
|
||||||
actix-multipart-rfc7578 = "0.10"
|
actix-multipart-rfc7578 = "0.10"
|
||||||
actix-rt = "2.2"
|
actix-rt = "2.2"
|
||||||
actix-test = "0.1.0"
|
actix-test = "0.1"
|
||||||
awc = "3.0.1"
|
awc = "3"
|
||||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
tokio = { version = "1.18.5", features = ["sync"] }
|
tokio = { version = "1.18.5", features = ["sync"] }
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl<'t> FieldReader<'t> for Bytes {
|
||||||
limits: &'t mut Limits,
|
limits: &'t mut Limits,
|
||||||
) -> Self::Future {
|
) -> Self::Future {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::with_capacity(131_072);
|
||||||
|
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
limits.try_consume_limits(chunk.len(), true)?;
|
limits.try_consume_limits(chunk.len(), true)?;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
use actix_web::{dev, error::PayloadError, web, Error, FromRequest, HttpRequest};
|
use actix_web::{dev, error::PayloadError, web, Error, FromRequest, HttpRequest};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use futures_util::{TryFutureExt, TryStreamExt as _};
|
use futures_util::{TryFutureExt as _, TryStreamExt as _};
|
||||||
|
|
||||||
use crate::{Field, Multipart, MultipartError};
|
use crate::{Field, Multipart, MultipartError};
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ where
|
||||||
|
|
||||||
DuplicateField::Deny => {
|
DuplicateField::Deny => {
|
||||||
return Box::pin(ready(Err(MultipartError::DuplicateField(
|
return Box::pin(ready(Err(MultipartError::DuplicateField(
|
||||||
field.name().to_string(),
|
field.name().to_owned(),
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let field_name = field.name().to_string();
|
let field_name = field.name().to_owned();
|
||||||
let t = T::read_field(req, field, limits).await?;
|
let t = T::read_field(req, field, limits).await?;
|
||||||
state.insert(field_name, Box::new(t));
|
state.insert(field_name, Box::new(t));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -117,11 +117,11 @@ where
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// Note: Vec GroupReader always allows duplicates
|
// Note: Vec GroupReader always allows duplicates
|
||||||
|
|
||||||
let field_name = field.name().to_string();
|
let field_name = field.name().to_owned();
|
||||||
|
|
||||||
let vec = state
|
let vec = state
|
||||||
.entry(field_name)
|
.entry(field_name)
|
||||||
.or_insert_with(|| Box::new(Vec::<T>::new()))
|
.or_insert_with(|| Box::<Vec<T>>::default())
|
||||||
.downcast_mut::<Vec<T>>()
|
.downcast_mut::<Vec<T>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -159,15 +159,16 @@ where
|
||||||
|
|
||||||
DuplicateField::Deny => {
|
DuplicateField::Deny => {
|
||||||
return Box::pin(ready(Err(MultipartError::DuplicateField(
|
return Box::pin(ready(Err(MultipartError::DuplicateField(
|
||||||
field.name().to_string(),
|
field.name().to_owned(),
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
DuplicateField::Replace => {}
|
DuplicateField::Replace => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let field_name = field.name().to_string();
|
let field_name = field.name().to_owned();
|
||||||
let t = T::read_field(req, field, limits).await?;
|
let t = T::read_field(req, field, limits).await?;
|
||||||
state.insert(field_name, Box::new(t));
|
state.insert(field_name, Box::new(t));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -182,8 +183,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait that allows a type to be used in the [`struct@MultipartForm`] extractor. You should use
|
/// Trait that allows a type to be used in the [`struct@MultipartForm`] extractor.
|
||||||
/// the [`macro@MultipartForm`] to implement this for your struct.
|
///
|
||||||
|
/// You should use the [`macro@MultipartForm`] macro to derive this for your struct.
|
||||||
pub trait MultipartCollect: Sized {
|
pub trait MultipartCollect: Sized {
|
||||||
/// An optional limit in bytes to be applied a given field name. Note this limit will be shared
|
/// An optional limit in bytes to be applied a given field name. Note this limit will be shared
|
||||||
/// across all fields sharing the same name.
|
/// across all fields sharing the same name.
|
||||||
|
@ -205,13 +207,13 @@ pub trait MultipartCollect: Sized {
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub enum DuplicateField {
|
pub enum DuplicateField {
|
||||||
/// Additional fields are not processed
|
/// Additional fields are not processed.
|
||||||
Ignore,
|
Ignore,
|
||||||
|
|
||||||
/// An error will be raised
|
/// An error will be raised.
|
||||||
Deny,
|
Deny,
|
||||||
|
|
||||||
/// All fields will be processed, the last one will replace all previous
|
/// All fields will be processed, the last one will replace all previous.
|
||||||
Replace,
|
Replace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,10 +272,10 @@ impl Limits {
|
||||||
/// Typed `multipart/form-data` extractor.
|
/// Typed `multipart/form-data` extractor.
|
||||||
///
|
///
|
||||||
/// To extract typed data from a multipart stream, the inner type `T` must implement the
|
/// To extract typed data from a multipart stream, the inner type `T` must implement the
|
||||||
/// [`MultipartCollect`] trait, you should use the [`macro@MultipartForm`] macro to derive this
|
/// [`MultipartCollect`] trait. You should use the [`macro@MultipartForm`] macro to derive this
|
||||||
/// for your struct.
|
/// for your struct.
|
||||||
///
|
///
|
||||||
/// Use [`MultipartFormConfig`] to configure extraction options.
|
/// Add a [`MultipartFormConfig`] to your app data to configure extraction.
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct MultipartForm<T: MultipartCollect>(pub T);
|
pub struct MultipartForm<T: MultipartCollect>(pub T);
|
||||||
|
|
||||||
|
@ -338,6 +340,8 @@ type MultipartFormErrorHandler =
|
||||||
Option<Arc<dyn Fn(MultipartError, &HttpRequest) -> Error + Send + Sync>>;
|
Option<Arc<dyn Fn(MultipartError, &HttpRequest) -> Error + Send + Sync>>;
|
||||||
|
|
||||||
/// [`struct@MultipartForm`] extractor configuration.
|
/// [`struct@MultipartForm`] extractor configuration.
|
||||||
|
///
|
||||||
|
/// Add to your app data to have it picked up by [`struct@MultipartForm`] extractors.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MultipartFormConfig {
|
pub struct MultipartFormConfig {
|
||||||
total_limit: usize,
|
total_limit: usize,
|
||||||
|
@ -346,19 +350,19 @@ pub struct MultipartFormConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MultipartFormConfig {
|
impl MultipartFormConfig {
|
||||||
/// Set maximum accepted payload size for the entire form. By default this limit is 50MiB.
|
/// Sets maximum accepted payload size for the entire form. By default this limit is 50MiB.
|
||||||
pub fn total_limit(mut self, total_limit: usize) -> Self {
|
pub fn total_limit(mut self, total_limit: usize) -> Self {
|
||||||
self.total_limit = total_limit;
|
self.total_limit = total_limit;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set maximum accepted data that will be read into memory. By default this limit is 2MiB.
|
/// Sets maximum accepted data that will be read into memory. By default this limit is 2MiB.
|
||||||
pub fn memory_limit(mut self, memory_limit: usize) -> Self {
|
pub fn memory_limit(mut self, memory_limit: usize) -> Self {
|
||||||
self.memory_limit = memory_limit;
|
self.memory_limit = memory_limit;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set custom error handler.
|
/// Sets custom error handler.
|
||||||
pub fn error_handler<F>(mut self, f: F) -> Self
|
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(MultipartError, &HttpRequest) -> Error + Send + Sync + 'static,
|
F: Fn(MultipartError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
|
@ -367,7 +371,7 @@ impl MultipartFormConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
/// Extracts payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||||
/// back to the default payload config.
|
/// back to the default payload config.
|
||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
|
@ -384,7 +388,7 @@ const DEFAULT_CONFIG: MultipartFormConfig = MultipartFormConfig {
|
||||||
|
|
||||||
impl Default for MultipartFormConfig {
|
impl Default for MultipartFormConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DEFAULT_CONFIG.clone()
|
DEFAULT_CONFIG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,7 +401,7 @@ mod tests {
|
||||||
use awc::{Client, ClientResponse};
|
use awc::{Client, ClientResponse};
|
||||||
|
|
||||||
use super::MultipartForm;
|
use super::MultipartForm;
|
||||||
use crate::form::{bytes::Bytes, tempfile::Tempfile, text::Text, MultipartFormConfig};
|
use crate::form::{bytes::Bytes, tempfile::TempFile, text::Text, MultipartFormConfig};
|
||||||
|
|
||||||
pub async fn send_form(
|
pub async fn send_form(
|
||||||
srv: &TestServer,
|
srv: &TestServer,
|
||||||
|
@ -611,7 +615,7 @@ mod tests {
|
||||||
|
|
||||||
#[derive(MultipartForm)]
|
#[derive(MultipartForm)]
|
||||||
struct TestFileUploadLimits {
|
struct TestFileUploadLimits {
|
||||||
field: Tempfile,
|
field: TempFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test_upload_limits_memory(
|
async fn test_upload_limits_memory(
|
||||||
|
|
|
@ -16,13 +16,13 @@ use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use super::FieldErrorHandler;
|
use super::FieldErrorHandler;
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{tempfile::TempfileError::FileIo, FieldReader, Limits},
|
form::{FieldReader, Limits},
|
||||||
Field, MultipartError,
|
Field, MultipartError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Write the field to a temporary file on disk.
|
/// Write the field to a temporary file on disk.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tempfile {
|
pub struct TempFile {
|
||||||
/// The temporary file on disk.
|
/// The temporary file on disk.
|
||||||
pub file: NamedTempFile,
|
pub file: NamedTempFile,
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ pub struct Tempfile {
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> FieldReader<'t> for Tempfile {
|
impl<'t> FieldReader<'t> for TempFile {
|
||||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||||
|
|
||||||
fn read_field(
|
fn read_field(
|
||||||
|
@ -45,34 +45,31 @@ impl<'t> FieldReader<'t> for Tempfile {
|
||||||
limits: &'t mut Limits,
|
limits: &'t mut Limits,
|
||||||
) -> Self::Future {
|
) -> Self::Future {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let config = TempfileConfig::from_req(req);
|
let config = TempFileConfig::from_req(req);
|
||||||
let field_name = field.name().to_owned();
|
let field_name = field.name().to_owned();
|
||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
|
|
||||||
let file = config
|
let file = config.create_tempfile().map_err(|err| {
|
||||||
.create_tempfile()
|
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||||
.map_err(|err| config.map_error(req, &field_name, FileIo(err)))?;
|
})?;
|
||||||
|
|
||||||
let mut file_async = tokio::fs::File::from_std(
|
let mut file_async = tokio::fs::File::from_std(file.reopen().map_err(|err| {
|
||||||
file.reopen()
|
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||||
.map_err(|err| config.map_error(req, &field_name, FileIo(err)))?,
|
})?);
|
||||||
);
|
|
||||||
|
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
limits.try_consume_limits(chunk.len(), false)?;
|
limits.try_consume_limits(chunk.len(), false)?;
|
||||||
size += chunk.len();
|
size += chunk.len();
|
||||||
file_async
|
file_async.write_all(chunk.as_ref()).await.map_err(|err| {
|
||||||
.write_all(chunk.as_ref())
|
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||||
.await
|
})?;
|
||||||
.map_err(|err| config.map_error(req, &field_name, FileIo(err)))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file_async
|
file_async.flush().await.map_err(|err| {
|
||||||
.flush()
|
config.map_error(req, &field_name, TempFileError::FileIo(err))
|
||||||
.await
|
})?;
|
||||||
.map_err(|err| config.map_error(req, &field_name, FileIo(err)))?;
|
|
||||||
|
|
||||||
Ok(Tempfile {
|
Ok(TempFile {
|
||||||
file,
|
file,
|
||||||
content_type: field.content_type().map(ToOwned::to_owned),
|
content_type: field.content_type().map(ToOwned::to_owned),
|
||||||
file_name: field
|
file_name: field
|
||||||
|
@ -87,28 +84,28 @@ impl<'t> FieldReader<'t> for Tempfile {
|
||||||
|
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum TempfileError {
|
pub enum TempFileError {
|
||||||
/// File I/O Error
|
/// File I/O Error
|
||||||
#[display(fmt = "File I/O error: {}", _0)]
|
#[display(fmt = "File I/O error: {}", _0)]
|
||||||
FileIo(std::io::Error),
|
FileIo(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for TempfileError {
|
impl ResponseError for TempFileError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for the [`Tempfile`] field reader.
|
/// Configuration for the [`TempFile`] field reader.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TempfileConfig {
|
pub struct TempFileConfig {
|
||||||
err_handler: FieldErrorHandler<TempfileError>,
|
err_handler: FieldErrorHandler<TempFileError>,
|
||||||
directory: Option<PathBuf>,
|
directory: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TempfileConfig {
|
impl TempFileConfig {
|
||||||
fn create_tempfile(&self) -> io::Result<NamedTempFile> {
|
fn create_tempfile(&self) -> io::Result<NamedTempFile> {
|
||||||
if let Some(dir) = self.directory.as_deref() {
|
if let Some(ref dir) = self.directory {
|
||||||
NamedTempFile::new_in(dir)
|
NamedTempFile::new_in(dir)
|
||||||
} else {
|
} else {
|
||||||
NamedTempFile::new()
|
NamedTempFile::new()
|
||||||
|
@ -116,21 +113,17 @@ impl TempfileConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG: TempfileConfig = TempfileConfig {
|
impl TempFileConfig {
|
||||||
err_handler: None,
|
/// Sets custom error handler.
|
||||||
directory: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl TempfileConfig {
|
|
||||||
pub fn error_handler<F>(mut self, f: F) -> Self
|
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(TempfileError, &HttpRequest) -> Error + Send + Sync + 'static,
|
F: Fn(TempFileError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
self.err_handler = Some(Arc::new(f));
|
self.err_handler = Some(Arc::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
/// Extracts payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||||
/// back to the default payload config.
|
/// back to the default payload config.
|
||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
|
@ -142,10 +135,10 @@ impl TempfileConfig {
|
||||||
&self,
|
&self,
|
||||||
req: &HttpRequest,
|
req: &HttpRequest,
|
||||||
field_name: &str,
|
field_name: &str,
|
||||||
err: TempfileError,
|
err: TempFileError,
|
||||||
) -> MultipartError {
|
) -> MultipartError {
|
||||||
let source = if let Some(err_handler) = self.err_handler.as_ref() {
|
let source = if let Some(ref err_handler) = self.err_handler {
|
||||||
(*err_handler)(err, req)
|
(err_handler)(err, req)
|
||||||
} else {
|
} else {
|
||||||
err.into()
|
err.into()
|
||||||
};
|
};
|
||||||
|
@ -165,7 +158,12 @@ impl TempfileConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TempfileConfig {
|
const DEFAULT_CONFIG: TempFileConfig = TempFileConfig {
|
||||||
|
err_handler: None,
|
||||||
|
directory: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Default for TempFileConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DEFAULT_CONFIG
|
DEFAULT_CONFIG
|
||||||
}
|
}
|
||||||
|
@ -178,11 +176,11 @@ mod tests {
|
||||||
use actix_multipart_rfc7578::client::multipart;
|
use actix_multipart_rfc7578::client::multipart;
|
||||||
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
|
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
|
||||||
|
|
||||||
use crate::form::{tempfile::Tempfile, tests::send_form, MultipartForm};
|
use crate::form::{tempfile::TempFile, tests::send_form, MultipartForm};
|
||||||
|
|
||||||
#[derive(MultipartForm)]
|
#[derive(MultipartForm)]
|
||||||
struct FileForm {
|
struct FileForm {
|
||||||
file: Tempfile,
|
file: TempFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn test_file_route(form: MultipartForm<FileForm>) -> impl Responder {
|
async fn test_file_route(form: MultipartForm<FileForm>) -> impl Responder {
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::{
|
||||||
pub struct Text<T: DeserializeOwned>(pub T);
|
pub struct Text<T: DeserializeOwned>(pub T);
|
||||||
|
|
||||||
impl<T: DeserializeOwned> Text<T> {
|
impl<T: DeserializeOwned> Text<T> {
|
||||||
|
/// Unwraps into inner value.
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,7 @@ where
|
||||||
let valid = if let Some(mime) = field.content_type() {
|
let valid = if let Some(mime) = field.content_type() {
|
||||||
mime.subtype() == mime::PLAIN || mime.suffix() == Some(mime::PLAIN)
|
mime.subtype() == mime::PLAIN || mime.suffix() == Some(mime::PLAIN)
|
||||||
} else {
|
} else {
|
||||||
// https://www.rfc-editor.org/rfc/rfc7578#section-4.4
|
// https://datatracker.ietf.org/doc/html/rfc7578#section-4.4
|
||||||
// content type defaults to text/plain, so None should be considered valid
|
// content type defaults to text/plain, so None should be considered valid
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
@ -100,12 +101,8 @@ pub struct TextConfig {
|
||||||
validate_content_type: bool,
|
validate_content_type: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG: TextConfig = TextConfig {
|
|
||||||
err_handler: None,
|
|
||||||
validate_content_type: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl TextConfig {
|
impl TextConfig {
|
||||||
|
/// Sets custom error handler.
|
||||||
pub fn error_handler<F>(mut self, f: F) -> Self
|
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(TextError, &HttpRequest) -> Error + Send + Sync + 'static,
|
F: Fn(TextError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
|
@ -114,7 +111,7 @@ impl TextConfig {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
/// Extracts payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
|
||||||
/// back to the default payload config.
|
/// back to the default payload config.
|
||||||
fn from_req(req: &HttpRequest) -> &Self {
|
fn from_req(req: &HttpRequest) -> &Self {
|
||||||
req.app_data::<Self>()
|
req.app_data::<Self>()
|
||||||
|
@ -123,8 +120,8 @@ impl TextConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_error(&self, req: &HttpRequest, err: TextError) -> Error {
|
fn map_error(&self, req: &HttpRequest, err: TextError) -> Error {
|
||||||
if let Some(err_handler) = self.err_handler.as_ref() {
|
if let Some(ref err_handler) = self.err_handler {
|
||||||
(*err_handler)(err, req)
|
(err_handler)(err, req)
|
||||||
} else {
|
} else {
|
||||||
err.into()
|
err.into()
|
||||||
}
|
}
|
||||||
|
@ -140,6 +137,11 @@ impl TextConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: TextConfig = TextConfig {
|
||||||
|
err_handler: None,
|
||||||
|
validate_content_type: true,
|
||||||
|
};
|
||||||
|
|
||||||
impl Default for TextConfig {
|
impl Default for TextConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
DEFAULT_CONFIG
|
DEFAULT_CONFIG
|
||||||
|
|
Loading…
Reference in New Issue