diff --git a/Cargo.toml b/Cargo.toml index 8bc56d157..65e3c6ae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ actix-files = { path = "actix-files" } actix-http = { path = "actix-http" } actix-http-test = { path = "actix-http-test" } actix-multipart = { path = "actix-multipart" } +actix-multipart-derive = { path = "actix-multipart-derive" } actix-router = { path = "actix-router" } actix-test = { path = "actix-test" } actix-web = { path = "actix-web" } diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml index 9fb8bb523..a6fb1a2ca 100644 --- a/actix-multipart-derive/Cargo.toml +++ b/actix-multipart-derive/Cargo.toml @@ -1,16 +1,24 @@ [package] name = "actix-multipart-derive" -version = "0.2.0" +version = "0.4.0" authors = ["Jacob Halsey "] -edition = "2021" +description = "Multipart form derive macro for Actix Web" +keywords = ["http", "web", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-web.git" license = "MIT OR Apache-2.0" +edition = "2018" [lib] proc-macro = true [dependencies] -darling = "0.14.1" -proc-macro2 = "1.0.37" -quote = "1.0.18" -syn = "1.0.92" -parse-size = "1.0.0" +darling = "0.14" +parse-size = "1" +proc-macro2 = "1" +quote = "1" +syn = "1" + +[dev-dependencies] +actix-multipart = "0.4" +actix-web = "4" diff --git a/actix-multipart-derive/LICENSE-APACHE b/actix-multipart-derive/LICENSE-APACHE deleted file mode 100644 index 8f5ba39b8..000000000 --- a/actix-multipart-derive/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2017-NOW Actix Team - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/actix-multipart-derive/LICENSE-APACHE b/actix-multipart-derive/LICENSE-APACHE new file mode 120000 index 000000000..965b606f3 --- /dev/null +++ b/actix-multipart-derive/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/actix-multipart-derive/LICENSE-MIT b/actix-multipart-derive/LICENSE-MIT deleted file mode 100644 index d559b1cd1..000000000 --- a/actix-multipart-derive/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2017-NOW Actix Team - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/actix-multipart-derive/LICENSE-MIT b/actix-multipart-derive/LICENSE-MIT new file mode 120000 index 000000000..76219eb72 --- /dev/null +++ b/actix-multipart-derive/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/actix-multipart-derive/src/lib.rs b/actix-multipart-derive/src/lib.rs index 434af17c3..5738b600f 100644 --- a/actix-multipart-derive/src/lib.rs +++ b/actix-multipart-derive/src/lib.rs @@ -1,10 +1,17 @@ -extern crate proc_macro; +//! Multipart form derive macro for Actix Web. + +#![deny(rust_2018_idioms, nonstandard_style)] +#![warn(future_incompatible)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_cfg))] + +use std::{collections::HashSet, convert::TryFrom as _}; use darling::{FromDeriveInput, FromField, FromMeta}; use parse_size::parse_size; use proc_macro2::Ident; use quote::quote; -use std::collections::HashSet; use syn::{parse_macro_input, Type}; #[derive(FromDeriveInput, Default)] @@ -41,6 +48,110 @@ struct ParsedField<'t> { ty: &'t Type, } +/// Implements the `MultipartFormTrait` for a struct so that it can be used with the `MultipartForm` +/// extractor. +/// +/// # Examples +/// +/// ## Basic Use +/// +/// Each field type should implement the `FieldReader` trait: +/// +/// ``` +/// # use actix_multipart::form::tempfile::Tempfile; +/// # use actix_multipart::form::text::Text; +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// struct ImageUpload { +/// description: Text, +/// timestamp: Text, +/// image: Tempfile, +/// } +/// ``` +/// +/// ## Optional and List Fields +/// +/// You can also use `Vec` and `Option` provided that `T: FieldReader`. +/// +/// A [`Vec`] field corresponds to an upload with multiple parts under the +/// [same field name](https://www.rfc-editor.org/rfc/rfc7578#section-4.3). +/// +/// ``` +/// # use actix_multipart::form::tempfile::Tempfile; +/// # use actix_multipart::form::text::Text; +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// struct Form { +/// category: Option>, +/// files: Vec, +/// } +/// ``` +/// +/// ## Field Renaming +/// +/// You can use the `#[multipart(rename = "foo")]` attribute to receive a field by a different name. +/// +/// ``` +/// # use actix_multipart::form::tempfile::Tempfile; +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// struct Form { +/// #[multipart(rename = "files[]")] +/// files: Vec, +/// } +/// ``` +/// +/// ## Field Limits +/// +/// You can use the `#[multipart(limit = "")]` attribute to set field level limits. The limit +/// string is parsed using [parse_size]. +/// +/// Note: the form is also subject to the global limits configured using `MultipartFormConfig`. +/// +/// ``` +/// # use actix_multipart::form::tempfile::Tempfile; +/// # use actix_multipart::form::text::Text; +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// struct Form { +/// #[multipart(limit = "2KiB")] +/// description: Text, +/// #[multipart(limit = "512MiB")] +/// files: Vec, +/// } +/// ``` +/// +/// ## Unknown Fields +/// +/// By default fields with an unknown name are ignored. You can change this using the +/// `#[multipart(deny_unknown_fields)]` attribute: +/// +/// ``` +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// #[multipart(deny_unknown_fields)] +/// struct Form { } +/// ``` +/// +/// ## Duplicate Fields +/// +/// You can change the behaviour for when multiple fields are received with the same name using the +/// `#[multipart(duplicate_action = "")]` attribute: +/// +/// - "ignore": Extra fields are ignored (default). +/// - "replace": Each field is processed, but only the last one is persisted. +/// - "deny": A `MultipartError::UnsupportedField` error is returned. +/// +/// (Note this option does not apply to `Vec` fields) +/// +/// ``` +/// # use actix_multipart::form::MultipartForm; +/// #[derive(MultipartForm)] +/// #[multipart(duplicate_action = "deny")] +/// struct Form { } +/// ``` +/// +/// [parse_size]: https://docs.rs/parse-size/1/parse_size #[proc_macro_derive(MultipartForm, attributes(multipart))] pub fn impl_multipart_form(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: syn::DeriveInput = parse_macro_input!(input); @@ -69,9 +180,12 @@ pub fn impl_multipart_form(input: proc_macro::TokenStream) -> proc_macro::TokenS let attrs: FieldAttrs = FieldAttrs::from_field(field)?; let serialization_name = attrs.rename.unwrap_or_else(|| rust_name.to_string()); - let limit = attrs.limit.map(|l| { - parse_size(&l).unwrap_or_else(|_| panic!("Unable to parse limit `{l}`")) - as usize + let limit = attrs.limit.map(|limit| { + usize::try_from( + parse_size(&limit) + .unwrap_or_else(|_| panic!("Unable to parse limit `{}`", limit)), + ) + .unwrap() }); Ok(ParsedField { diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 5d15d37cf..0b27c29e1 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -10,7 +10,8 @@ license = "MIT OR Apache-2.0" edition = "2018" [features] -default = ["tempfile"] +default = ["tempfile", "derive"] +derive = ["actix-multipart-derive"] tempfile = ["tempfile-dep", "tokio/fs"] [lib] @@ -21,7 +22,7 @@ path = "src/lib.rs" actix-utils = "3" actix-web = { version = "4", default-features = false } actix-http = "3" -actix-multipart-derive = { path = "../actix-multipart-derive" } +actix-multipart-derive = { version = "=0.4.0", optional = true } bytes = "1" derive_more = "0.99.5" @@ -35,7 +36,7 @@ mime = "0.3" serde = "1.0" serde_plain = "1.0" serde_json = "1.0" -# TODO: Replace with dep: prefix in newer versions of Cargo +# TODO(MSRV 1.60): replace with dep: prefix tempfile-dep = { package = "tempfile", version = "3.3.0", optional = true } tokio = { version = "1.8.4", features = ["sync"] } diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs index e7f3df85e..979f4b0f7 100644 --- a/actix-multipart/src/error.rs +++ b/actix-multipart/src/error.rs @@ -1,7 +1,10 @@ //! Error and Result module -use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::StatusCode; -use actix_web::ResponseError; + +use actix_web::{ + error::{ParseError, PayloadError}, + http::StatusCode, + ResponseError, +}; use derive_more::{Display, Error, From}; /// A set of errors that can occur during parsing multipart streams diff --git a/actix-multipart/src/form/bytes.rs b/actix-multipart/src/form/bytes.rs index a359506a9..1d866d528 100644 --- a/actix-multipart/src/form/bytes.rs +++ b/actix-multipart/src/form/bytes.rs @@ -1,12 +1,16 @@ //! Reads a field into memory. -use crate::form::{FieldReader, Limits}; -use crate::{Field, MultipartError}; + use actix_web::HttpRequest; use bytes::BytesMut; use futures_core::future::LocalBoxFuture; use futures_util::{FutureExt, TryStreamExt}; use mime::Mime; +use crate::{ + form::{FieldReader, Limits}, + Field, MultipartError, +}; + /// Read the field into memory. #[derive(Debug)] pub struct Bytes { diff --git a/actix-multipart/src/form/json.rs b/actix-multipart/src/form/json.rs index 94a85554b..1a61add78 100644 --- a/actix-multipart/src/form/json.rs +++ b/actix-multipart/src/form/json.rs @@ -1,14 +1,17 @@ //! Deserializes a field as JSON. -use crate::form::bytes::Bytes; -use crate::form::{FieldReader, Limits}; -use crate::{Field, MultipartError}; -use actix_web::http::StatusCode; -use actix_web::{web, Error, HttpRequest, ResponseError}; + +use std::sync::Arc; + +use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use derive_more::{Deref, DerefMut, Display, Error}; use futures_core::future::LocalBoxFuture; use futures_util::FutureExt; use serde::de::DeserializeOwned; -use std::sync::Arc; + +use crate::{ + form::{bytes::Bytes, FieldReader, Limits}, + Field, MultipartError, +}; /// Deserialize from JSON. #[derive(Debug, Deref, DerefMut)] @@ -76,6 +79,7 @@ impl ResponseError for JsonFieldError { /// Configuration for the [`Json`] field reader. #[derive(Clone)] pub struct JsonConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, validate_content_type: bool, } @@ -125,14 +129,17 @@ impl Default for JsonConfig { #[cfg(test)] mod tests { - use crate::form::json::{Json, JsonConfig}; - use crate::form::tests::send_form; - use crate::form::MultipartForm; + use std::{collections::HashMap, io::Cursor}; + use actix_http::StatusCode; use actix_multipart_rfc7578::client::multipart; use actix_web::{web, App, HttpResponse, Responder}; - use std::collections::HashMap; - use std::io::Cursor; + + use crate::form::{ + json::{Json, JsonConfig}, + tests::send_form, + MultipartForm, + }; #[derive(MultipartForm)] struct JsonForm { diff --git a/actix-multipart/src/form/mod.rs b/actix-multipart/src/form/mod.rs index 6c591cf9c..e45b170d2 100644 --- a/actix-multipart/src/form/mod.rs +++ b/actix-multipart/src/form/mod.rs @@ -1,124 +1,29 @@ //! Process and extract typed data from a multipart stream. -pub mod bytes; -pub mod json; -#[cfg(feature = "tempfile")] -pub mod tempfile; -pub mod text; -use crate::{Field, Multipart, MultipartError}; -use actix_http::error::PayloadError; -use actix_web::dev::Payload; -use actix_web::{web, Error, FromRequest, HttpRequest}; +use std::{ + any::Any, + collections::HashMap, + future::{ready, Future}, + sync::Arc, +}; + +use actix_web::{dev::Payload, error::PayloadError, web, Error, FromRequest, HttpRequest}; use derive_more::{Deref, DerefMut}; use futures_core::future::LocalBoxFuture; use futures_util::TryFutureExt; use futures_util::{FutureExt, TryStreamExt}; -use std::any::Any; -use std::collections::HashMap; -use std::future::{ready, Future}; -use std::sync::Arc; -/// Implements the [`MultipartFormTrait`] for a struct so that it can be used with the -/// [`struct@MultipartForm`] extractor. -/// -/// ## Simple Example -/// -/// Each field type should implement the [`FieldReader`] trait: -/// -/// ``` -/// # use actix_multipart::form::tempfile::Tempfile; -/// # use actix_multipart::form::text::Text; -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// struct ImageUpload { -/// description: Text, -/// timestamp: Text, -/// image: Tempfile, -/// } -/// ``` -/// -/// ## Optional and List Fields -/// -/// You can also use `Vec` and `Option` provided that `T: FieldReader`. -/// -/// A [`Vec`] field corresponds to an upload with multiple parts under the -/// [same field name](https://www.rfc-editor.org/rfc/rfc7578#section-4.3). -/// -/// ``` -/// # use actix_multipart::form::tempfile::Tempfile; -/// # use actix_multipart::form::text::Text; -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// struct Form { -/// category: Option>, -/// files: Vec, -/// } -/// ``` -/// -/// ## Field Renaming -/// -/// You can use the `#[multipart(rename="")]` attribute to receive a field by a different name. -/// -/// ``` -/// # use actix_multipart::form::tempfile::Tempfile; -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// struct Form { -/// #[multipart(rename="files[]")] -/// files: Vec, -/// } -/// ``` -/// -/// ## Field Limits -/// -/// You can use the `#[multipart(limit="")]` attribute to set field level limits. The limit -/// string is parsed using [parse_size](https://docs.rs/parse-size/1.0.0/parse_size/). -/// -/// Note: the form is also subject to the global limits configured using the -/// [`MultipartFormConfig`]. -/// -/// ``` -/// # use actix_multipart::form::tempfile::Tempfile; -/// # use actix_multipart::form::text::Text; -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// struct Form { -/// #[multipart(limit="2KiB")] -/// description: Text, -/// #[multipart(limit="512MiB")] -/// files: Vec, -/// } -/// ``` -/// -/// ## Unknown Fields -/// -/// By default fields with an unknown name are ignored. You can change this using the -/// `#[multipart(deny_unknown_fields)]` attribute: -/// -/// ``` -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// #[multipart(deny_unknown_fields)] -/// struct Form { } -/// ``` -/// -/// ## Duplicate Fields -/// -/// You can change the behaviour for when multiple fields are received with the same name using the -/// `#[multipart(duplicate_action = "")]` attribute: -/// -/// - "ignore": Extra fields are ignored (default). -/// - "replace": Each field is processed, but only the last one is persisted. -/// - "deny": A [MultipartError::UnsupportedField] error is returned. -/// -/// (Note this option does not apply to `Vec` fields) -/// -/// ``` -/// # use actix_multipart::form::MultipartForm; -/// #[derive(MultipartForm)] -/// #[multipart(duplicate_action = "deny")] -/// struct Form { } -/// ``` +use crate::{Field, Multipart, MultipartError}; + +pub mod bytes; +pub mod json; +#[cfg_attr(docsrs, doc(cfg(feature = "tempfile")))] +#[cfg(feature = "tempfile")] +pub mod tempfile; +pub mod text; + +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] +#[cfg(feature = "derive")] pub use actix_multipart_derive::MultipartForm; /// Trait that data types to be used in a multipart form struct should implement. @@ -137,7 +42,7 @@ pub trait FieldReader<'t>: Sized + Any { #[derive(Default, Deref, DerefMut)] pub struct State(pub HashMap>); -// Trait that the field collection types implement, i.e. `Vec`, `Option`, or `T` itself. +/// Trait that the field collection types implement, i.e. `Vec`, `Option`, or `T` itself. #[doc(hidden)] pub trait FieldGroupReader<'t>: Sized + Any { type Future: Future>; @@ -472,11 +377,6 @@ impl Default for MultipartFormConfig { #[cfg(test)] mod tests { - use super::MultipartForm; - use crate::form::bytes::Bytes; - use crate::form::tempfile::Tempfile; - use crate::form::text::Text; - use crate::form::MultipartFormConfig; use actix_http::encoding::Decoder; use actix_http::Payload; use actix_multipart_rfc7578::client::multipart; @@ -485,6 +385,12 @@ mod tests { use actix_web::{web, App, HttpResponse, Responder}; use awc::{Client, ClientResponse}; + use super::MultipartForm; + use crate::form::bytes::Bytes; + use crate::form::tempfile::Tempfile; + use crate::form::text::Text; + use crate::form::MultipartFormConfig; + pub async fn send_form( srv: &TestServer, form: multipart::Form<'static>, @@ -709,7 +615,7 @@ mod tests { async fn test_upload_limits_memory( form: MultipartForm, ) -> impl Responder { - assert!(form.field.data.len() > 0); + assert!(!form.field.data.is_empty()); HttpResponse::Ok().finish() } @@ -787,7 +693,7 @@ mod tests { async fn test_field_level_limits_route( form: MultipartForm, ) -> impl Responder { - assert!(form.field.len() > 0); + assert!(!form.field.is_empty()); HttpResponse::Ok().finish() } diff --git a/actix-multipart/src/form/tempfile.rs b/actix-multipart/src/form/tempfile.rs index 5f7d8bfbf..dd7aee24c 100644 --- a/actix-multipart/src/form/tempfile.rs +++ b/actix-multipart/src/form/tempfile.rs @@ -1,18 +1,23 @@ //! Writes a field to a temporary file on disk. -use crate::form::tempfile::TempfileError::FileIo; -use crate::form::{FieldReader, Limits}; -use crate::{Field, MultipartError}; -use actix_web::http::StatusCode; -use actix_web::{web, Error, HttpRequest, ResponseError}; + +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use derive_more::{Display, Error}; use futures_core::future::LocalBoxFuture; -use futures_util::{FutureExt, TryStreamExt}; +use futures_util::{FutureExt as _, TryStreamExt as _}; use mime::Mime; -use std::path::{Path, PathBuf}; -use std::sync::Arc; use tempfile_dep::NamedTempFile; use tokio::io::AsyncWriteExt; +use crate::{ + form::{tempfile::TempfileError::FileIo, FieldReader, Limits}, + Field, MultipartError, +}; + /// Write the field to a temporary file on disk. #[derive(Debug)] pub struct Tempfile { @@ -95,6 +100,7 @@ impl ResponseError for TempfileError { /// Configuration for the [`Tempfile`] field reader. #[derive(Clone)] pub struct TempfileConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, directory: Option, } @@ -153,13 +159,15 @@ impl Default for TempfileConfig { #[cfg(test)] mod tests { - use crate::form::tempfile::Tempfile; - use crate::form::tests::send_form; - use crate::form::MultipartForm; + use std::io::{Cursor, Read}; + use actix_http::StatusCode; use actix_multipart_rfc7578::client::multipart; use actix_web::{web, App, HttpResponse, Responder}; - use std::io::{Cursor, Read}; + + use crate::form::tempfile::Tempfile; + use crate::form::tests::send_form; + use crate::form::MultipartForm; #[derive(MultipartForm)] struct FileForm { diff --git a/actix-multipart/src/form/text.rs b/actix-multipart/src/form/text.rs index c02ea9330..ae8b3e56e 100644 --- a/actix-multipart/src/form/text.rs +++ b/actix-multipart/src/form/text.rs @@ -1,14 +1,17 @@ //! Deserializes a field from plain text. -use crate::form::bytes::Bytes; -use crate::form::{FieldReader, Limits}; -use crate::{Field, MultipartError}; -use actix_web::http::StatusCode; -use actix_web::{web, Error, HttpRequest, ResponseError}; + +use std::sync::Arc; + +use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use derive_more::{Deref, DerefMut, Display, Error}; use futures_core::future::LocalBoxFuture; -use futures_util::FutureExt; +use futures_util::future::FutureExt as _; use serde::de::DeserializeOwned; -use std::sync::Arc; + +use crate::{ + form::{bytes::Bytes, FieldReader, Limits}, + Field, MultipartError, +}; /// Deserialize from plain text. /// @@ -92,6 +95,7 @@ impl ResponseError for TextError { /// Configuration for the [`Text`] field reader. #[derive(Clone)] pub struct TextConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, validate_content_type: bool, } @@ -143,13 +147,15 @@ impl Default for TextConfig { #[cfg(test)] mod tests { - use crate::form::tests::send_form; - use crate::form::text::{Text, TextConfig}; - use crate::form::MultipartForm; + use std::io::Cursor; + use actix_http::StatusCode; use actix_multipart_rfc7578::client::multipart; use actix_web::{web, App, HttpResponse, Responder}; - use std::io::Cursor; + + use crate::form::tests::send_form; + use crate::form::text::{Text, TextConfig}; + use crate::form::MultipartForm; #[derive(MultipartForm)] struct TextForm { diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 1d0510039..ea5eed555 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -860,7 +860,7 @@ impl PayloadBuffer { #[cfg(test)] mod tests { - use super::*; + use std::time::Duration; use actix_http::h1::Payload; use actix_web::http::header::{DispositionParam, DispositionType}; @@ -869,10 +869,11 @@ mod tests { use actix_web::FromRequest; use bytes::Bytes; use futures_util::{future::lazy, StreamExt}; - use std::time::Duration; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; + use super::*; + #[actix_rt::test] async fn test_boundary() { let headers = HeaderMap::new();