From e9bc44bebe5540c386a6df28b32ee06a89a9cdca Mon Sep 17 00:00:00 2001 From: Elric Milon Date: Sun, 30 Nov 2025 16:04:29 +0100 Subject: [PATCH] [Rust] Implement support for multipart file uploads for reqwest-async and reqwest-trait (#22454) * feat(Rust): Add support for multipart file uploads in reqwest generators * chore: Regen relevant samples affected by the updated multipart support --- ...rust-reqwest-petstore-async-middleware.yaml | 1 + ...ust-reqwest-petstore-async-tokensource.yaml | 1 + bin/configs/rust-reqwest-petstore-async.yaml | 1 + .../rust-reqwest-petstore-avoid-box.yaml | 1 + ...t-reqwest-petstore-serde-path-to-error.yaml | 1 + .../src/main/resources/rust/Cargo.mustache | 2 +- .../resources/rust/reqwest-trait/api.mustache | 9 ++++++++- .../main/resources/rust/reqwest/api.mustache | 18 +++++++++++++++--- .../rust/reqwest-trait/petstore/Cargo.toml | 2 +- .../reqwest-trait/petstore/src/apis/pet_api.rs | 4 +++- .../petstore-async-middleware/Cargo.toml | 4 +++- .../src/apis/pet_api.rs | 4 +++- .../petstore-async-tokensource/Cargo.toml | 4 +++- .../src/apis/pet_api.rs | 4 +++- .../rust/reqwest/petstore-async/Cargo.toml | 4 +++- .../reqwest/petstore-async/src/apis/pet_api.rs | 4 +++- .../rust/reqwest/petstore-avoid-box/Cargo.toml | 4 +++- .../petstore-avoid-box/src/apis/pet_api.rs | 4 +++- .../src/apis/pet_api.rs | 2 +- .../src/apis/pet_api.rs | 2 +- .../petstore-serde-path-to-error/Cargo.toml | 4 +++- .../src/apis/pet_api.rs | 4 +++- .../rust/reqwest/petstore/src/apis/pet_api.rs | 2 +- 23 files changed, 66 insertions(+), 20 deletions(-) diff --git a/bin/configs/rust-reqwest-petstore-async-middleware.yaml b/bin/configs/rust-reqwest-petstore-async-middleware.yaml index e79083159623..d63c50533b34 100644 --- a/bin/configs/rust-reqwest-petstore-async-middleware.yaml +++ b/bin/configs/rust-reqwest-petstore-async-middleware.yaml @@ -5,6 +5,7 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/rust additionalProperties: supportAsync: true + useAsyncFileStream: true supportMiddleware: true supportMultipleResponses: true packageName: petstore-reqwest-async-middleware diff --git a/bin/configs/rust-reqwest-petstore-async-tokensource.yaml b/bin/configs/rust-reqwest-petstore-async-tokensource.yaml index 3c4ef661caf2..a4bcf69cf83b 100644 --- a/bin/configs/rust-reqwest-petstore-async-tokensource.yaml +++ b/bin/configs/rust-reqwest-petstore-async-tokensource.yaml @@ -5,6 +5,7 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/rust additionalProperties: supportAsync: true + useAsyncFileStream: true supportTokenSource: true supportMultipleResponses: true packageName: petstore-reqwest-async-tokensource diff --git a/bin/configs/rust-reqwest-petstore-async.yaml b/bin/configs/rust-reqwest-petstore-async.yaml index ed156ec550ef..9dfd0da95486 100644 --- a/bin/configs/rust-reqwest-petstore-async.yaml +++ b/bin/configs/rust-reqwest-petstore-async.yaml @@ -5,6 +5,7 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/rust additionalProperties: supportAsync: true + useAsyncFileStream: true supportMultipleResponses: true packageName: petstore-reqwest-async useSingleRequestParameter: true diff --git a/bin/configs/rust-reqwest-petstore-avoid-box.yaml b/bin/configs/rust-reqwest-petstore-avoid-box.yaml index 58123ca3e573..6c7b958b4955 100644 --- a/bin/configs/rust-reqwest-petstore-avoid-box.yaml +++ b/bin/configs/rust-reqwest-petstore-avoid-box.yaml @@ -5,6 +5,7 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/rust additionalProperties: supportAsync: true + useAsyncFileStream: true supportMultipleResponses: true avoidBoxedModels: true packageName: petstore-reqwest-avoid-box diff --git a/bin/configs/rust-reqwest-petstore-serde-path-to-error.yaml b/bin/configs/rust-reqwest-petstore-serde-path-to-error.yaml index 0a7a2afb6717..0ef387d755aa 100644 --- a/bin/configs/rust-reqwest-petstore-serde-path-to-error.yaml +++ b/bin/configs/rust-reqwest-petstore-serde-path-to-error.yaml @@ -5,6 +5,7 @@ inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml templateDir: modules/openapi-generator/src/main/resources/rust additionalProperties: packageName: petstore-reqwest-serde-path-to-error + useAsyncFileStream: true useSerdePathToError: true enumNameMappings: delivered: shipped diff --git a/modules/openapi-generator/src/main/resources/rust/Cargo.mustache b/modules/openapi-generator/src/main/resources/rust/Cargo.mustache index 20c3ddb2839c..05846b4f1def 100644 --- a/modules/openapi-generator/src/main/resources/rust/Cargo.mustache +++ b/modules/openapi-generator/src/main/resources/rust/Cargo.mustache @@ -97,7 +97,7 @@ rustls-tls = ["reqwest/rustls-tls"] {{/reqwest}} {{#reqwestTrait}} async-trait = "^0.1" -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } {{#supportMiddleware}} reqwest-middleware = { version = "^0.4", features = ["json", "multipart"] } {{/supportMiddleware}} diff --git a/modules/openapi-generator/src/main/resources/rust/reqwest-trait/api.mustache b/modules/openapi-generator/src/main/resources/rust/reqwest-trait/api.mustache index ef0494e39942..cf16c0fc24b2 100644 --- a/modules/openapi-generator/src/main/resources/rust/reqwest-trait/api.mustache +++ b/modules/openapi-generator/src/main/resources/rust/reqwest-trait/api.mustache @@ -391,7 +391,14 @@ impl {{classname}} for {{classname}}Client { let mut local_var_form = reqwest::multipart::Form::new(); {{#formParams}} {{#isFile}} - // TODO: support file upload for '{{{baseName}}}' parameter + {{^isRequired}} + if let Some(ref path) = {{{baseName}}} { + local_var_form = local_var_form.file("{{{baseName}}}", path.as_os_str()).await?; + } + {{/isRequired}} + {{#isRequired}} + local_var_form = local_var_form.file("{{{baseName}}}", {{{baseName}}}.as_os_str()).await?; + {{/isRequired}} {{/isFile}} {{^isFile}} {{#required}} diff --git a/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache b/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache index 39410dd78948..11357a3752a6 100644 --- a/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache +++ b/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache @@ -397,13 +397,20 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: {{/isNullable}} {{/required}} {{^required}} - if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { multipart_form = multipart_form.file("{{{baseName}}}", param_value)?; } {{/required}} {{/supportAsync}} {{#supportAsync}} - // TODO: support file upload for '{{{baseName}}}' parameter + {{^required}} + if let Some(ref param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { + multipart_form = multipart_form.file("{{{baseName}}}", param_value.as_os_str()).await?; + } + {{/required}} + {{#required}} + multipart_form = multipart_form.file("{{{baseName}}}", {{{vendorExtensions.x-rust-param-identifier}}}.as_os_str()).await?; + {{/required}} {{/supportAsync}} {{/isFile}} {{^isFile}} @@ -420,7 +427,12 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: {{/required}} {{^required}} if let Some(param_value) = {{{vendorExtensions.x-rust-param-identifier}}} { - multipart_form = multipart_form.text("{{{baseName}}}", param_value{{#isArray}}.into_iter().map(|p| p.to_string()).collect::>().join(","){{/isArray}}.to_string()); + {{#isPrimitiveType}} + multipart_form = multipart_form.text("{{{baseName}}}", param_value.to_string()); + {{/isPrimitiveType}} + {{^isPrimitiveType}} + multipart_form = multipart_form.text("{{{baseName}}}", serde_json::to_string(¶m_value)?); + {{/isPrimitiveType}} } {{/required}} {{/isFile}} diff --git a/samples/client/petstore/rust/reqwest-trait/petstore/Cargo.toml b/samples/client/petstore/rust/reqwest-trait/petstore/Cargo.toml index 22361dbf667b..ce1ec4c1f002 100644 --- a/samples/client/petstore/rust/reqwest-trait/petstore/Cargo.toml +++ b/samples/client/petstore/rust/reqwest-trait/petstore/Cargo.toml @@ -14,7 +14,7 @@ serde_repr = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } async-trait = "^0.1" -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } mockall = { version = "^0.13", optional = true} [features] default = ["native-tls"] diff --git a/samples/client/petstore/rust/reqwest-trait/petstore/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest-trait/petstore/src/apis/pet_api.rs index 01ca14659339..b29b3844758b 100644 --- a/samples/client/petstore/rust/reqwest-trait/petstore/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest-trait/petstore/src/apis/pet_api.rs @@ -480,7 +480,9 @@ impl PetApi for PetApiClient { if let Some(local_var_param_value) = additional_metadata { local_var_form = local_var_form.text("additionalMetadata", local_var_param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref path) = file { + local_var_form = local_var_form.file("file", path.as_os_str()).await?; + } local_var_req_builder = local_var_req_builder.multipart(local_var_form); let local_var_req = local_var_req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore-async-middleware/Cargo.toml b/samples/client/petstore/rust/reqwest/petstore-async-middleware/Cargo.toml index fb2a58c58a61..9becf795bdbf 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async-middleware/Cargo.toml +++ b/samples/client/petstore/rust/reqwest/petstore-async-middleware/Cargo.toml @@ -13,7 +13,9 @@ serde_json = "^1.0" serde_repr = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } reqwest-middleware = { version = "^0.4", features = ["json", "multipart"] } [features] diff --git a/samples/client/petstore/rust/reqwest/petstore-async-middleware/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-async-middleware/src/apis/pet_api.rs index 7fc32c38810e..b6eb3e4a354d 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async-middleware/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-async-middleware/src/apis/pet_api.rs @@ -567,7 +567,9 @@ pub async fn upload_file(configuration: &configuration::Configuration, params: U if let Some(param_value) = params.additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref param_value) = params.file { + multipart_form = multipart_form.file("file", param_value.as_os_str()).await?; + } req_builder = req_builder.multipart(multipart_form); let req = req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore-async-tokensource/Cargo.toml b/samples/client/petstore/rust/reqwest/petstore-async-tokensource/Cargo.toml index 43f5ef391693..ee6b233ed2d5 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async-tokensource/Cargo.toml +++ b/samples/client/petstore/rust/reqwest/petstore-async-tokensource/Cargo.toml @@ -13,7 +13,9 @@ serde_json = "^1.0" serde_repr = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } async-trait = "^0.1" # TODO: propose to Yoshidan to externalize this as non google related crate, so that it can easily be extended for other cloud providers. google-cloud-token = "^0.1" diff --git a/samples/client/petstore/rust/reqwest/petstore-async-tokensource/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-async-tokensource/src/apis/pet_api.rs index b45357f33a7b..1c241f620a90 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async-tokensource/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-async-tokensource/src/apis/pet_api.rs @@ -578,7 +578,9 @@ pub async fn upload_file(configuration: &configuration::Configuration, params: U if let Some(param_value) = params.additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref param_value) = params.file { + multipart_form = multipart_form.file("file", param_value.as_os_str()).await?; + } req_builder = req_builder.multipart(multipart_form); let req = req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore-async/Cargo.toml b/samples/client/petstore/rust/reqwest/petstore-async/Cargo.toml index c4d6924f5c60..63b4eb8acfd8 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async/Cargo.toml +++ b/samples/client/petstore/rust/reqwest/petstore-async/Cargo.toml @@ -13,7 +13,9 @@ serde_json = "^1.0" serde_repr = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } [features] default = ["native-tls"] diff --git a/samples/client/petstore/rust/reqwest/petstore-async/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-async/src/apis/pet_api.rs index 7fc32c38810e..b6eb3e4a354d 100644 --- a/samples/client/petstore/rust/reqwest/petstore-async/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-async/src/apis/pet_api.rs @@ -567,7 +567,9 @@ pub async fn upload_file(configuration: &configuration::Configuration, params: U if let Some(param_value) = params.additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref param_value) = params.file { + multipart_form = multipart_form.file("file", param_value.as_os_str()).await?; + } req_builder = req_builder.multipart(multipart_form); let req = req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore-avoid-box/Cargo.toml b/samples/client/petstore/rust/reqwest/petstore-avoid-box/Cargo.toml index c146fb2824de..b1a31cbb3202 100644 --- a/samples/client/petstore/rust/reqwest/petstore-avoid-box/Cargo.toml +++ b/samples/client/petstore/rust/reqwest/petstore-avoid-box/Cargo.toml @@ -13,7 +13,9 @@ serde_json = "^1.0" serde_repr = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } [features] default = ["native-tls"] diff --git a/samples/client/petstore/rust/reqwest/petstore-avoid-box/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-avoid-box/src/apis/pet_api.rs index 7fc32c38810e..b6eb3e4a354d 100644 --- a/samples/client/petstore/rust/reqwest/petstore-avoid-box/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-avoid-box/src/apis/pet_api.rs @@ -567,7 +567,9 @@ pub async fn upload_file(configuration: &configuration::Configuration, params: U if let Some(param_value) = params.additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref param_value) = params.file { + multipart_form = multipart_form.file("file", param_value.as_os_str()).await?; + } req_builder = req_builder.multipart(multipart_form); let req = req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore-awsv4signature/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-awsv4signature/src/apis/pet_api.rs index 8e5e914c8669..48818b7fa5c7 100644 --- a/samples/client/petstore/rust/reqwest/petstore-awsv4signature/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-awsv4signature/src/apis/pet_api.rs @@ -597,7 +597,7 @@ pub fn upload_file(configuration: &configuration::Configuration, pet_id: i64, ad if let Some(param_value) = p_form_additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - if let Some(param_value) = p_form_file { + if let Some(ref param_value) = p_form_file { multipart_form = multipart_form.file("file", param_value)?; } req_builder = req_builder.multipart(multipart_form); diff --git a/samples/client/petstore/rust/reqwest/petstore-model-name-prefix/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-model-name-prefix/src/apis/pet_api.rs index da2c44352b41..3c12a505f578 100644 --- a/samples/client/petstore/rust/reqwest/petstore-model-name-prefix/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-model-name-prefix/src/apis/pet_api.rs @@ -493,7 +493,7 @@ pub fn upload_file(configuration: &configuration::Configuration, pet_id: i64, ad if let Some(param_value) = p_form_additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - if let Some(param_value) = p_form_file { + if let Some(ref param_value) = p_form_file { multipart_form = multipart_form.file("file", param_value)?; } req_builder = req_builder.multipart(multipart_form); diff --git a/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/Cargo.toml b/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/Cargo.toml index f51bf0e5dc06..d9a47c26ea2e 100644 --- a/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/Cargo.toml +++ b/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/Cargo.toml @@ -14,7 +14,9 @@ serde_repr = "^0.1" serde_path_to_error = "^0.1" url = "^2.5" uuid = { version = "^1.8", features = ["serde", "v4"] } -reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +tokio = { version = "^1.46.0", features = ["fs"] } +tokio-util = { version = "^0.7", features = ["codec"] } +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] } [features] default = ["native-tls"] diff --git a/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/src/apis/pet_api.rs index a9cd10bc8ba5..5daf6ba8737a 100644 --- a/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore-serde-path-to-error/src/apis/pet_api.rs @@ -493,7 +493,9 @@ pub async fn upload_file(configuration: &configuration::Configuration, pet_id: i if let Some(param_value) = p_form_additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - // TODO: support file upload for 'file' parameter + if let Some(ref param_value) = p_form_file { + multipart_form = multipart_form.file("file", param_value.as_os_str()).await?; + } req_builder = req_builder.multipart(multipart_form); let req = req_builder.build()?; diff --git a/samples/client/petstore/rust/reqwest/petstore/src/apis/pet_api.rs b/samples/client/petstore/rust/reqwest/petstore/src/apis/pet_api.rs index 10c473c38c28..b10a2ad35fc0 100644 --- a/samples/client/petstore/rust/reqwest/petstore/src/apis/pet_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore/src/apis/pet_api.rs @@ -493,7 +493,7 @@ pub fn upload_file(configuration: &configuration::Configuration, pet_id: i64, ad if let Some(param_value) = p_form_additional_metadata { multipart_form = multipart_form.text("additionalMetadata", param_value.to_string()); } - if let Some(param_value) = p_form_file { + if let Some(ref param_value) = p_form_file { multipart_form = multipart_form.file("file", param_value)?; } req_builder = req_builder.multipart(multipart_form);