[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
This commit is contained in:
Elric Milon
2025-11-30 16:04:29 +01:00
committed by GitHub
parent 7a48bd8ef5
commit e9bc44bebe
23 changed files with 66 additions and 20 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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::<Vec<String>>().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(&param_value)?);
{{/isPrimitiveType}}
}
{{/required}}
{{/isFile}}

View File

@@ -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"]

View File

@@ -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()?;

View File

@@ -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]

View File

@@ -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()?;

View File

@@ -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"

View File

@@ -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()?;

View File

@@ -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"]

View File

@@ -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()?;

View File

@@ -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"]

View File

@@ -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()?;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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"]

View File

@@ -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()?;

View File

@@ -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);