test(multipart,derive): move trybuild tests (#4028)

* test(multipart,derive): move trybuild tests

* docs(multipart): move derive examples to the core crate
This commit is contained in:
Yuki Okushi 2026-06-21 13:51:22 +09:00 committed by GitHub
parent 7ded7ee478
commit afd2df2f69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 115 additions and 107 deletions

6
Cargo.lock generated
View File

@ -165,27 +165,25 @@ dependencies = [
"mime",
"multer",
"rand 0.10.1",
"rustversion-msrv",
"serde",
"serde_json",
"serde_plain",
"tempfile",
"tokio",
"tokio-stream",
"trybuild",
]
[[package]]
name = "actix-multipart-derive"
version = "0.8.0"
dependencies = [
"actix-multipart",
"actix-web",
"bytesize",
"darling",
"proc-macro2",
"quote",
"rustversion-msrv",
"syn",
"trybuild",
]
[[package]]

View File

@ -23,11 +23,5 @@ proc-macro2 = "1"
quote = "1"
syn = "2"
[dev-dependencies]
actix-multipart = "0.8"
actix-web = "4"
rustversion-msrv = "0.100"
trybuild = "1"
[lints]
workspace = true

View File

@ -1,6 +1,8 @@
//! Multipart form derive macro for Actix Web.
//!
//! See [`macro@MultipartForm`] for usage examples.
//! See [`actix_multipart::form::MultipartForm`] for usage examples.
//!
//! [`actix_multipart::form::MultipartForm`]: https://docs.rs/actix-multipart/latest/actix_multipart/form/struct.MultipartForm.html
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
@ -49,103 +51,9 @@ struct ParsedField<'t> {
/// Implements `MultipartCollect` for a struct so that it can be used with the `MultipartForm`
/// extractor.
///
/// # Basic Use
/// See [`actix_multipart::form::MultipartForm`] for supported fields and attributes.
///
/// Each field type should implement the `FieldReader` trait:
///
/// ```
/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct ImageUpload {
/// description: Text<String>,
/// timestamp: Text<i64>,
/// image: TempFile,
/// }
/// ```
///
/// # Optional and List Fields
///
/// You can also use `Vec<T>` and `Option<T>` 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, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// category: Option<Text<String>>,
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Field Renaming
///
/// You can use the `#[multipart(rename = "foo")]` attribute to receive a field by a different name.
///
/// ```
/// use actix_multipart::form::{tempfile::TempFile, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// #[multipart(rename = "files[]")]
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Field Limits
///
/// You can use the `#[multipart(limit = "<size>")]` attribute to set field level limits. The limit
/// string is parsed using [`bytesize`].
///
/// Note: the form is also subject to the global limits configured using `MultipartFormConfig`.
///
/// ```
/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// #[multipart(limit = "2 KiB")]
/// description: Text<String>,
///
/// #[multipart(limit = "512 MiB")]
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Unknown Fields
///
/// By default fields with an unknown name are ignored. They can be rejected using the
/// `#[multipart(deny_unknown_fields)]` attribute:
///
/// ```
/// # use actix_multipart::form::MultipartForm;
/// #[derive(MultipartForm)]
/// #[multipart(deny_unknown_fields)]
/// struct Form { }
/// ```
///
/// # Duplicate Fields
///
/// The behaviour for when multiple fields with the same name are received can be changed using the
/// `#[multipart(duplicate_field = "<behavior>")]` attribute:
///
/// - "ignore": (default) Extra fields are ignored. I.e., the first one is persisted.
/// - "deny": A `MultipartError::UnknownField` error response is returned.
/// - "replace": Each field is processed, but only the last one is persisted.
///
/// Note that `Vec` fields will ignore this option.
///
/// ```
/// # use actix_multipart::form::MultipartForm;
/// #[derive(MultipartForm)]
/// #[multipart(duplicate_field = "deny")]
/// struct Form { }
/// ```
///
/// [`bytesize`]: https://docs.rs/bytesize/2
/// [`actix_multipart::form::MultipartForm`]: https://docs.rs/actix-multipart/latest/actix_multipart/form/struct.MultipartForm.html
#[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);

View File

@ -12,10 +12,15 @@ homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
all-features = true
[[test]]
name = "trybuild"
required-features = ["derive", "tempfile"]
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"actix_http::*",
@ -68,8 +73,10 @@ env_logger = "0.11"
futures-test = "0.3"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
multer = "3"
rustversion-msrv = "0.100"
tokio = { version = "1.38.2", features = ["sync"] }
tokio-stream = "0.1"
trybuild = "1"
[lints]
workspace = true

View File

@ -336,6 +336,107 @@ pub async fn discard_field(mut field: Field, limits: &mut Limits) -> Result<(),
/// `multipart/related`, or non-multipart media types.
///
/// Add a [`MultipartFormConfig`] to your app data to configure extraction.
///
/// # Basic Use
///
/// Each field type should implement the [`FieldReader`] trait:
///
/// ```rust
/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct ImageUpload {
/// description: Text<String>,
/// timestamp: Text<i64>,
/// image: TempFile,
/// }
/// ```
///
/// # Optional and List Fields
///
/// You can also use [`Vec<T>`](Vec) and [`Option<T>`](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).
///
/// ```rust
/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// category: Option<Text<String>>,
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Field Renaming
///
/// You can use the `#[multipart(rename = "foo")]` attribute to receive a field by a different name.
///
/// ```rust
/// use actix_multipart::form::{tempfile::TempFile, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// #[multipart(rename = "files[]")]
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Field Limits
///
/// You can use the `#[multipart(limit = "<size>")]` attribute to set field level limits. The limit
/// string is parsed using [`bytesize`].
///
/// Note: the form is also subject to the global limits configured using [`MultipartFormConfig`].
///
/// ```rust
/// use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
///
/// #[derive(MultipartForm)]
/// struct Form {
/// #[multipart(limit = "2 KiB")]
/// description: Text<String>,
///
/// #[multipart(limit = "512 MiB")]
/// files: Vec<TempFile>,
/// }
/// ```
///
/// # Unknown Fields
///
/// By default fields with an unknown name are ignored. They can be rejected using the
/// `#[multipart(deny_unknown_fields)]` attribute:
///
/// ```rust
/// use actix_multipart::form::MultipartForm;
///
/// #[derive(MultipartForm)]
/// #[multipart(deny_unknown_fields)]
/// struct Form {}
/// ```
///
/// # Duplicate Fields
///
/// The behaviour for when multiple fields with the same name are received can be changed using the
/// `#[multipart(duplicate_field = "<behavior>")]` attribute:
///
/// - "ignore": (default) Extra fields are ignored. I.e., the first one is persisted.
/// - "deny": A [`MultipartError::DuplicateField`] error response is returned.
/// - "replace": Each field is processed, but only the last one is persisted.
///
/// Note that [`Vec`] fields will ignore this option.
///
/// ```rust
/// use actix_multipart::form::MultipartForm;
///
/// #[derive(MultipartForm)]
/// #[multipart(duplicate_field = "deny")]
/// struct Form {}
/// ```
///
/// [`bytesize`]: https://docs.rs/bytesize/2
/// [`MultipartError::DuplicateField`]: crate::MultipartError::DuplicateField
#[derive(Deref, DerefMut)]
pub struct MultipartForm<T: MultipartCollect>(pub T);