Skip to content

rust: better support for binary body parameters #9021

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jclulow
Copy link

@jclulow jclulow commented Mar 19, 2021

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<Body>
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.

Note possible related issue: #5218

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package 
    ./bin/generate-samples.sh
    ./bin/utils/export_docs_generators.sh
    
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    For Windows users, please run the script in Git BASH.
  • File the PR against the correct branch: master, 5.1.x, 6.0.x
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request. (cc @ahl @frol @farcaller @richardwhiuk @paladinzh)

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<Body>
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.
@jclulow
Copy link
Author

jclulow commented Mar 19, 2021

While the sample application does not currently have any binary body parameters, I have one in another application I am working on. This portion of the API definition document:

    "/v1/worker/task/{task}/chunk": {
      "post": {
        "operationId": "worker_task_upload_chunk",
        "parameters": [
          {
            "in": "path",
            "name": "task",
            "required": true,
            "schema": {
              "type": "string"
            },
            "style": "simple"
          }
        ],
        "requestBody": {
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "successful creation",
            "content": {
              "application/json": {
                "schema": {
                  "title": "UploadedChunk",
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          }
        }
      }
    },

... becomes the following generated routine ...

pub async fn worker_task_upload_chunk<B: Into<reqwest::Body>>(
    configuration: &configuration::Configuration,
    task: &str,
    body: B,
) -> Result<crate::models::UploadedChunk, Error<WorkerTaskUploadChunkError>> {

    let local_var_client = &configuration.client;

    let local_var_uri_str = format!("{}/v1/worker/task/{task}/chunk", configuration.base_path, task=crate::apis::urlencode(task));
    let mut local_var_req_builder = local_var_client.post(local_var_uri_str.as_str());

    if let Some(ref local_var_user_agent) = configuration.user_agent {
        local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone());
    }
    local_var_req_builder = local_var_req_builder.body(body);

    let local_var_req = local_var_req_builder.build()?;
    let local_var_resp = local_var_client.execute(local_var_req).await?;

    let local_var_status = local_var_resp.status();
    let local_var_content = local_var_resp.text().await?;

    if !local_var_status.is_client_error() && !local_var_status.is_server_error() {
        serde_json::from_str(&local_var_content).map_err(Error::from)
    } else {
        let local_var_entity: Option<WorkerTaskUploadChunkError> = serde_json::from_str(&local_var_content).ok();
        let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity };
        Err(Error::ResponseError(local_var_error))
    }
}

seen++;
}
}
assert seen == 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use assert in test code only?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about simply using isBinary in the mustache template instead of creating x-rust-type-parameter ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use assert in test code only?

I put the assert here because it should be an invariant. The generated code won't be correct (something is seriously wrong) if there is more than one binary body parameter in the allParams list, when there was only one in the bodyParams list. It seems like it would be better to crash rather than to generate incorrect code if that is ever true, as it presumably represents a bug in the code.

What about simply using isBinary in the mustache template instead of creating x-rust-type-parameter ?

I'm not sure how to do that with the limited functionality available in the templates. We need three things to be true:

  • that it's binary
  • that it's a body parameter
  • that it's the only body parameter

In addition to these three conditions, we need to make that determination outside of the {{#allParams}} loop in the template where we emit function arguments. I could not see how to use the isBinary property of a specific index in the bodyParams list as a predicate in the appropriate part of the template, or how to use the length of the list as a predicate.

I also expect that as the Rust code generator becomes more complete we will need to do more of this. That is, add more trait bounds and generic parameters as certain other types of arguments are added; e.g., today for strings we generate code like:

async fn endpoint(
    arg1: &str,
    arg2: &str,
)

But it would be more common to accept something like an AsRef<str>; e.g.,

async fn endpoint<S1: AsRef<str>, S2: AsRef<str>>(
    arg1: S1,
    arg2: S2,
)

This means you can pass either a &str or a String or, I believe, something that implements Display.

What are your thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the assert here because it should be an invariant. The generated code won't be correct (something is seriously wrong)

Don't think we should use assert in non-test code. We usually use condition to perform a check and throw an exception instead.

Copy link
Contributor

@jayvdb jayvdb Oct 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 which exception should be raised here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just a RuntimeException

@wing328 wing328 modified the milestones: 5.1.1, 5.2.0 May 10, 2021
@wing328 wing328 modified the milestones: 5.2.0, 5.2.1 Jul 13, 2021
@wing328 wing328 modified the milestones: 5.2.1, 5.3.0 Aug 17, 2021
@dkatz23238
Copy link

Thanks Joshua for your work on implementing a more generic approach to uploading bytes, this specific feature is quite useful for a project we are building at www.smartlayers.io

@wing328 wing328 modified the milestones: 5.3.0, 5.3.1 Oct 25, 2021
@wing328 wing328 modified the milestones: 5.3.1, 5.4.0 Dec 29, 2021
@wing328 wing328 modified the milestones: 5.4.0, 6.0.0 Jan 31, 2022
@wing328 wing328 modified the milestones: 6.0.0, 6.1.0, 6.0.1 May 26, 2022
@wing328 wing328 modified the milestones: 6.0.1, 6.1.0 Jul 5, 2022
@wing328 wing328 modified the milestones: 6.1.0, 6.1.1 Sep 11, 2022
@wing328 wing328 removed this from the 6.1.1 milestone Sep 24, 2022
@wing328 wing328 added this to the 6.2.1 milestone Sep 24, 2022
@jayvdb
Copy link
Contributor

jayvdb commented Oct 17, 2022

@bcourtine this seems relevant to the TODO you put in #1890

@jayvdb
Copy link
Contributor

jayvdb commented Oct 31, 2022

I believe that this PR should enhance https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/test/resources/3_0/rust/petstore.yaml to include an endpoint which triggers this new code, and then the samples will generate the new code.

@wing328 wing328 modified the milestones: 6.2.1, 6.3.0 Nov 1, 2022
@wing328 wing328 modified the milestones: 6.3.0, 6.3.1 Jan 20, 2023
@wing328 wing328 modified the milestones: 6.4.0, 6.5.0 Feb 19, 2023
@wing328 wing328 modified the milestones: 6.5.0, 6.6.0 Apr 1, 2023
@wing328 wing328 modified the milestones: 6.6.0, 7.0.0 May 11, 2023
@wing328 wing328 removed this from the 7.0.0 milestone Aug 11, 2023
@aDogCalledSpot
Copy link

aDogCalledSpot commented May 13, 2024

What's the status here? I need a Vec<u8> in the body and PathBuf isn't very helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants