From cd6cf8a5a2677fc91e547d831f549fd555cc0851 Mon Sep 17 00:00:00 2001 From: "Joshua M. Clulow" Date: Fri, 19 Mar 2021 14:36:33 -0700 Subject: [PATCH] rust: better support for binary body parameters The Rust-based reqwest client does not correctly handle a binary body parameter today. It creates a PathBuf parameter and attempts to pass that to the RequestBuilder json() method, which sends the path itself as the body of the request. Instead, it would help to parameterise operations that accept a binary body so that they may accept any object that implements the Into trait. This would allow the caller to pass a binary body in several forms, such as a slice of bytes or a Stream that reads from some other source. --- .../codegen/languages/RustClientCodegen.java | 34 +++++++ .../main/resources/rust/reqwest/api.mustache | 93 +++++++++++++++++-- .../rust/reqwest/petstore/src/apis/pet_api.rs | 45 +++++++-- .../reqwest/petstore/src/apis/store_api.rs | 19 +++- .../reqwest/petstore/src/apis/user_api.rs | 41 ++++++-- 5 files changed, 205 insertions(+), 27 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java index fde0ae96d8b4..d778d2e31966 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustClientCodegen.java @@ -18,9 +18,11 @@ package org.openapitools.codegen.languages; import com.google.common.base.Strings; +import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.parser.util.SchemaTypeUtil; import org.openapitools.codegen.*; import org.openapitools.codegen.meta.features.*; @@ -503,6 +505,38 @@ public String toOperationId(String operationId) { return StringUtils.underscore(sanitizedOperationId); } + @Override + public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) { + CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers); + + // If this endpoint has a single binary body parameter, we want + // to accept a body argument with the trait bound Into. + // This requires a type parameter in the function definition, + // in advance of actual parameter, and is thus far easier to decide + // here than in the template. + if (REQWEST_LIBRARY.equals(getLibrary()) && op.bodyParams != null && op.bodyParams.size() == 1) { + CodegenParameter cp = op.bodyParams.get(0); + + if (cp.isBinary) { + cp.baseType = cp.dataType = "B"; + op.vendorExtensions.put("x-rust-type-parameter", "B: Into"); + + // A copy of the body parameter will appear somewhere in the + // allParams list. We need to doctor that one as well. + int seen = 0; + for (CodegenParameter ap : op.allParams) { + if (ap.isBinary && ap.isBodyParam) { + ap.baseType = ap.dataType = "B"; + seen++; + } + } + assert seen == 1; + } + } + + return op; + } + @Override public Map postProcessOperationsWithModels(Map objs, List allModels) { @SuppressWarnings("unchecked") 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 8ff7ce8d99e6..235116d65342 100644 --- a/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache +++ b/modules/openapi-generator/src/main/resources/rust/reqwest/api.mustache @@ -88,7 +88,65 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: {{/vendorExtensions.x-group-parameters}} {{^vendorExtensions.x-group-parameters}} -pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: &configuration::Configuration, {{#allParams}}{{{paramName}}}: {{^required}}Option<{{/required}}{{#required}}{{#isNullable}}Option<{{/isNullable}}{{/required}}{{#isString}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isString}}{{#isUuid}}{{#isArray}}Vec<{{/isArray}}&str{{#isArray}}>{{/isArray}}{{/isUuid}}{{^isString}}{{^isUuid}}{{^isPrimitiveType}}{{^isContainer}}{{#isBodyParam}}crate::models::{{/isBodyParam}}{{/isContainer}}{{/isPrimitiveType}}{{{dataType}}}{{/isUuid}}{{/isString}}{{^required}}>{{/required}}{{#required}}{{#isNullable}}>{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Result<{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}{{/supportMultipleResponses}}, Error<{{{operationIdCamelCase}}}Error>> { +pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}{{! + }}{{#vendorExtensions.x-rust-type-parameter}}{{! + }}<{{{vendorExtensions.x-rust-type-parameter}}}>{{! + }}{{/vendorExtensions.x-rust-type-parameter}}{{! + }}( + configuration: &configuration::Configuration, +{{! + }}{{#allParams}} {{{paramName}}}: {{! + ! + ! If this is an optional parameter, or a required parameter that + ! is nullable, wrap the type in an Option: + ! + }}{{^required}}Option<{{/required}}{{! + }}{{#required}}{{! + }}{{#isNullable}}Option<{{/isNullable}}{{! + }}{{/required}}{{! + + ! + ! Represent a string as a string slice (&str). + ! TODO: Array should be a slice, not a Vector; i.e., &[&str]. + ! + }}{{#isString}}{{! + }}{{#isArray}}Vec<{{/isArray}}{{! + }}&str{{! + }}{{#isArray}}>{{/isArray}}{{! + }}{{/isString}}{{! + + ! + ! Represent a UUID as a string slice (&str). + ! TODO: Array should be a slice, not a Vector; i.e., &[&str]. + ! + }}{{#isUuid}}{{! + }}{{#isArray}}Vec<{{/isArray}}{{! + }}&str{{! + }}{{#isArray}}>{{/isArray}}{{! + }}{{/isUuid}}{{! + + }}{{^isString}}{{^isUuid}}{{! + }}{{^isPrimitiveType}}{{^isContainer}}{{#isBodyParam}}{{! + ! + ! Complex body parameters need a prefix to refer to the + ! struct we defined in the models module: + ! + }}crate::models::{{! + }}{{/isBodyParam}}{{/isContainer}}{{/isPrimitiveType}}{{! + }}{{{dataType}}}{{! + }}{{/isUuid}}{{/isString}}{{! + + ! + ! Close out the Option wrapper: + ! + }}{{^required}}>{{/required}}{{! + }}{{#required}}{{! + }}{{#isNullable}}>{{/isNullable}}{{! + }}{{/required}}{{! + }}, +{{! + }}{{/allParams}}{{! +}}) -> Result<{{#supportMultipleResponses}}ResponseContent<{{{operationIdCamelCase}}}Success>{{/supportMultipleResponses}}{{^supportMultipleResponses}}{{^returnType}}(){{/returnType}}{{#returnType}}{{{returnType}}}{{/returnType}}{{/supportMultipleResponses}}, Error<{{{operationIdCamelCase}}}Error>> { {{/vendorExtensions.x-group-parameters}} let local_var_client = &configuration.client; @@ -271,12 +329,33 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration: local_var_req_builder = local_var_req_builder.form(&local_var_form_params); {{/hasFormParams}} {{/isMultipart}} - {{#hasBodyParam}} - {{#bodyParams}} - local_var_req_builder = local_var_req_builder.json(&{{{paramName}}}); - {{/bodyParams}} - {{/hasBodyParam}} - +{{! + ! + ! Body parameter: + ! + !}}{{#hasBodyParam}}{{! + }}{{#bodyParams}}{{! + ! + ! If the body parameter is binary, we pass it to the reqwest body() + ! method. This requires the body parameter argument to this function + ! to be generic, and match the Into bound, allowing + ! a stream or a byte buffer to be used: + ! + !}}{{#isBinary}}{{! + }} local_var_req_builder = local_var_req_builder.body({{{paramName}}}); +{{! + }}{{/isBinary}}{{! + ! + ! Otherwise, we assume the parameter has the Serializable trait, and + ! pass it to the reqwest json() method: + ! + }}{{^isBinary}}{{! + }} local_var_req_builder = local_var_req_builder.json(&{{{paramName}}}); +{{! + }}{{/isBinary}}{{! + }}{{/bodyParams}}{{! + }}{{/hasBodyParam}}{{! +}} let local_var_req = local_var_req_builder.build()?; let {{^supportAsync}}mut {{/supportAsync}}local_var_resp = local_var_client.execute(local_var_req){{#supportAsync}}.await{{/supportAsync}}?; 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 47cf47f1d59b..d0dceb32af17 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 @@ -82,7 +82,10 @@ pub enum UploadFileError { } -pub fn add_pet(configuration: &configuration::Configuration, body: crate::models::Pet) -> Result<(), Error> { +pub fn add_pet( + configuration: &configuration::Configuration, + body: crate::models::Pet, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -112,7 +115,11 @@ pub fn add_pet(configuration: &configuration::Configuration, body: crate::models } } -pub fn delete_pet(configuration: &configuration::Configuration, pet_id: i64, api_key: Option<&str>) -> Result<(), Error> { +pub fn delete_pet( + configuration: &configuration::Configuration, + pet_id: i64, + api_key: Option<&str>, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -145,7 +152,10 @@ pub fn delete_pet(configuration: &configuration::Configuration, pet_id: i64, api } /// Multiple status values can be provided with comma separated strings -pub fn find_pets_by_status(configuration: &configuration::Configuration, status: Vec) -> Result, Error> { +pub fn find_pets_by_status( + configuration: &configuration::Configuration, + status: Vec, +) -> Result, Error> { let local_var_client = &configuration.client; @@ -176,7 +186,10 @@ pub fn find_pets_by_status(configuration: &configuration::Configuration, status: } /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. -pub fn find_pets_by_tags(configuration: &configuration::Configuration, tags: Vec) -> Result, Error> { +pub fn find_pets_by_tags( + configuration: &configuration::Configuration, + tags: Vec, +) -> Result, Error> { let local_var_client = &configuration.client; @@ -207,7 +220,10 @@ pub fn find_pets_by_tags(configuration: &configuration::Configuration, tags: Vec } /// Returns a single pet -pub fn get_pet_by_id(configuration: &configuration::Configuration, pet_id: i64) -> Result> { +pub fn get_pet_by_id( + configuration: &configuration::Configuration, + pet_id: i64, +) -> Result> { let local_var_client = &configuration.client; @@ -241,7 +257,10 @@ pub fn get_pet_by_id(configuration: &configuration::Configuration, pet_id: i64) } } -pub fn update_pet(configuration: &configuration::Configuration, body: crate::models::Pet) -> Result<(), Error> { +pub fn update_pet( + configuration: &configuration::Configuration, + body: crate::models::Pet, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -271,7 +290,12 @@ pub fn update_pet(configuration: &configuration::Configuration, body: crate::mod } } -pub fn update_pet_with_form(configuration: &configuration::Configuration, pet_id: i64, name: Option<&str>, status: Option<&str>) -> Result<(), Error> { +pub fn update_pet_with_form( + configuration: &configuration::Configuration, + pet_id: i64, + name: Option<&str>, + status: Option<&str>, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -308,7 +332,12 @@ pub fn update_pet_with_form(configuration: &configuration::Configuration, pet_id } } -pub fn upload_file(configuration: &configuration::Configuration, pet_id: i64, additional_metadata: Option<&str>, file: Option) -> Result> { +pub fn upload_file( + configuration: &configuration::Configuration, + pet_id: i64, + additional_metadata: Option<&str>, + file: Option, +) -> Result> { let local_var_client = &configuration.client; diff --git a/samples/client/petstore/rust/reqwest/petstore/src/apis/store_api.rs b/samples/client/petstore/rust/reqwest/petstore/src/apis/store_api.rs index 898599f9b2fc..8d00aef93dca 100644 --- a/samples/client/petstore/rust/reqwest/petstore/src/apis/store_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore/src/apis/store_api.rs @@ -50,7 +50,10 @@ pub enum PlaceOrderError { /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors -pub fn delete_order(configuration: &configuration::Configuration, order_id: &str) -> Result<(), Error> { +pub fn delete_order( + configuration: &configuration::Configuration, + order_id: &str, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -77,7 +80,9 @@ pub fn delete_order(configuration: &configuration::Configuration, order_id: &str } /// Returns a map of status codes to quantities -pub fn get_inventory(configuration: &configuration::Configuration, ) -> Result<::std::collections::HashMap, Error> { +pub fn get_inventory( + configuration: &configuration::Configuration, +) -> Result<::std::collections::HashMap, Error> { let local_var_client = &configuration.client; @@ -112,7 +117,10 @@ pub fn get_inventory(configuration: &configuration::Configuration, ) -> Result<: } /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions -pub fn get_order_by_id(configuration: &configuration::Configuration, order_id: i64) -> Result> { +pub fn get_order_by_id( + configuration: &configuration::Configuration, + order_id: i64, +) -> Result> { let local_var_client = &configuration.client; @@ -138,7 +146,10 @@ pub fn get_order_by_id(configuration: &configuration::Configuration, order_id: i } } -pub fn place_order(configuration: &configuration::Configuration, body: crate::models::Order) -> Result> { +pub fn place_order( + configuration: &configuration::Configuration, + body: crate::models::Order, +) -> Result> { let local_var_client = &configuration.client; diff --git a/samples/client/petstore/rust/reqwest/petstore/src/apis/user_api.rs b/samples/client/petstore/rust/reqwest/petstore/src/apis/user_api.rs index 97f0f3c4e146..073312d55c07 100644 --- a/samples/client/petstore/rust/reqwest/petstore/src/apis/user_api.rs +++ b/samples/client/petstore/rust/reqwest/petstore/src/apis/user_api.rs @@ -84,7 +84,10 @@ pub enum UpdateUserError { /// This can only be done by the logged in user. -pub fn create_user(configuration: &configuration::Configuration, body: crate::models::User) -> Result<(), Error> { +pub fn create_user( + configuration: &configuration::Configuration, + body: crate::models::User, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -111,7 +114,10 @@ pub fn create_user(configuration: &configuration::Configuration, body: crate::mo } } -pub fn create_users_with_array_input(configuration: &configuration::Configuration, body: Vec) -> Result<(), Error> { +pub fn create_users_with_array_input( + configuration: &configuration::Configuration, + body: Vec, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -138,7 +144,10 @@ pub fn create_users_with_array_input(configuration: &configuration::Configuratio } } -pub fn create_users_with_list_input(configuration: &configuration::Configuration, body: Vec) -> Result<(), Error> { +pub fn create_users_with_list_input( + configuration: &configuration::Configuration, + body: Vec, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -166,7 +175,10 @@ pub fn create_users_with_list_input(configuration: &configuration::Configuration } /// This can only be done by the logged in user. -pub fn delete_user(configuration: &configuration::Configuration, username: &str) -> Result<(), Error> { +pub fn delete_user( + configuration: &configuration::Configuration, + username: &str, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -192,7 +204,10 @@ pub fn delete_user(configuration: &configuration::Configuration, username: &str) } } -pub fn get_user_by_name(configuration: &configuration::Configuration, username: &str) -> Result> { +pub fn get_user_by_name( + configuration: &configuration::Configuration, + username: &str, +) -> Result> { let local_var_client = &configuration.client; @@ -218,7 +233,11 @@ pub fn get_user_by_name(configuration: &configuration::Configuration, username: } } -pub fn login_user(configuration: &configuration::Configuration, username: &str, password: &str) -> Result> { +pub fn login_user( + configuration: &configuration::Configuration, + username: &str, + password: &str, +) -> Result> { let local_var_client = &configuration.client; @@ -246,7 +265,9 @@ pub fn login_user(configuration: &configuration::Configuration, username: &str, } } -pub fn logout_user(configuration: &configuration::Configuration, ) -> Result<(), Error> { +pub fn logout_user( + configuration: &configuration::Configuration, +) -> Result<(), Error> { let local_var_client = &configuration.client; @@ -273,7 +294,11 @@ pub fn logout_user(configuration: &configuration::Configuration, ) -> Result<(), } /// This can only be done by the logged in user. -pub fn update_user(configuration: &configuration::Configuration, username: &str, body: crate::models::User) -> Result<(), Error> { +pub fn update_user( + configuration: &configuration::Configuration, + username: &str, + body: crate::models::User, +) -> Result<(), Error> { let local_var_client = &configuration.client;