Repository: swagger-api/swagger-ui (js)

Issue:
Incorrect curl command for file upload (without multipart/form-data)
### Q&A (please complete the following information)

 - OS: macOS
 - Browser: Firefox
 - Version: 92
 - Method of installation: swagger-ui-react via npm
 - Swagger-UI version: 3.51.0
 - Swagger/OpenAPI version: OpenAPI 3.0

### Content & configuration

We use `swagger-ui-react` but this can also be reproduced using https://editor.swagger.io/.

Example Swagger/OpenAPI definition:
```yaml
openapi: 3.0.0
info:
  title: Test API
  version: '1.0'
servers:
  - url: 'http://localhost:3000'

paths:

  # Generated CURL request uses:
  #   -d '{}'
  # (This doesn't work; it could use something like --data-binary '@<file name>' instead.)
  /upload1:
    post:
      summary: Simple upload
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: OK

  # Generated CURL request uses:
  #   -F 'fileName=@<file name>;type=<mime type>'
  # (This works fine.)
  /upload2:
    post:
      summary: Multipart upload
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                fileName:
                  type: string
                  format: binary
      responses:
        '200':
          description: OK
```

### Describe the bug you're encountering

In my OpenAPI 3.0 spec, I define a file upload endpoint by following the first example in [the relevant docs](https://swagger.io/docs/specification/describing-request-body/file-upload/) (_not_ using `multipart/form-data`). Calling that endpoint directly from Swagger UI works as expected (the UI offers to upload a file), but the generated CURL command doesn't show the expected option for the request body (it uses the option `-d '{}'` rather than something like `--data-binary '@<file name>'`).

### To reproduce...

Steps to reproduce the behavior:
1. Open the above Open API spec in Swagger UI or Swagger Editor
2. Open `/upload1`
3. Click on "Try it out"
4. The UI offers to chose a file to upload for the request body
5. Click execute; the request works as expected
6. The generated CURL request displayed shows the request body `-d '{}'`

### Expected behavior

Since the UI does offer to upload a file, I would expect the generated CURL command to also treat this as a binary file upload. I'd probably use the option `--data-binary '@<file name>'` (`--file-upload` would also work but only for `PUT` requests, not for `POST` requests).

### Additional context or thoughts

This would be consistent with what is already done for multi-part file uploads, where the generated CURL command uses the option `-F 'fileName=@<file name>;type=<mime type>'`.

I found a few existing issues regarding multipart file uploads (which were fixed and led to the behavior of using `-F 'fileName=@<file name>;type=<mime type>'`), but not for the case where multipart is not used; hopefully I didn't miss anything and this isn't a duplicate...

Pull request:
support curl binary data when body type: string format: binary or base64
<!--- Provide a general summary of your changes in the Title above -->

### Description
<!--- Describe your changes in detail -->

Have added request snippets generation case when body is `win.File` => `--data-binary '@{file name}'`

### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- Use the magic "Fixes #1234" format, so the issues are -->
<!--- automatically closed when this PR is merged. -->
Fixes #7544 


### How Has This Been Tested?
<!--- Please describe in detail how you manually tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

unit

### Screenshots (if appropriate):

![image](https://user-images.githubusercontent.com/11584315/136473868-010ba001-db7f-4f10-9fa0-ed02152baf13.png)


## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

### My PR contains... 
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] No code changes (`src/` is unmodified: changes to documentation, CI, metadata, etc.)
- [ ] Dependency changes (any modification to dependencies in `package.json`)
- [ ] Bug fixes (non-breaking change which fixes an issue)
- [x] Improvements (misc. changes to existing features)
- [x] Features (non-breaking change which adds functionality)

### My changes...
- [ ] are breaking changes to a public API (config options, System API, major UI change, etc).
- [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.).
- [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
- [x] are not breaking changes.

### Documentation
- [x] My changes do not require a change to the project documentation.
- [ ] My changes require a change to the project documentation.
- [ ] If yes to above: I have updated the documentation accordingly.

### Automated tests
- [ ] My changes can not or do not need to be tested.
- [x] My changes can and should be tested by unit and/or integration tests.
- [x] If yes to above: I have added tests to cover my changes.
- [x] If yes to above: I have taken care to cover edge cases in my tests.
- [x] All new and existing tests passed.


Patch:
diff --git a/src/core/plugins/request-snippets/fn.js b/src/core/plugins/request-snippets/fn.js
index d456426c..7da9115e 100644
--- a/src/core/plugins/request-snippets/fn.js
+++ b/src/core/plugins/request-snippets/fn.js
@@ -103,9 +103,10 @@ const curlify = (request, escape, newLine, ext = "") => {
     }
   }
 
-  if (request.get("body")) {
+  const body = request.get("body")
+  if (body) {
     if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
-      for (let [k, v] of request.get("body").entrySeq()) {
+      for (let [k, v] of body.entrySeq()) {
         let extractedKey = extractKey(k)
         addNewLine()
         addIndent()
@@ -116,11 +117,15 @@ const curlify = (request, escape, newLine, ext = "") => {
           addWords(`${extractedKey}=${v}`)
         }
       }
+    } else if(body instanceof win.File) {
+      addNewLine()
+      addIndent()
+      addWordsWithoutLeadingSpace(`--data-binary '@${body.name}'`)
     } else {
       addNewLine()
       addIndent()
       addWordsWithoutLeadingSpace("-d ")
-      let reqBody = request.get("body")
+      let reqBody = body
       if (!Map.isMap(reqBody)) {
         if (typeof reqBody !== "string") {
           reqBody = JSON.stringify(reqBody)
@@ -130,7 +135,7 @@ const curlify = (request, escape, newLine, ext = "") => {
         addWordsWithoutLeadingSpace(getStringBodyOfMap(request))
       }
     }
-  } else if (!request.get("body") && request.get("method") === "POST") {
+  } else if (!body && request.get("method") === "POST") {
     addNewLine()
     addIndent()
     addWordsWithoutLeadingSpace("-d ''")


Test patch:
diff --git a/test/unit/core/curlify.js b/test/unit/core/curlify.js
index aa168d07..d67b122d 100644
--- a/test/unit/core/curlify.js
+++ b/test/unit/core/curlify.js
@@ -220,6 +220,21 @@ describe("curlify", function () {
     expect(curlified).toEqual("curl -X 'POST' \\\n  'http://example.com' \\\n  -H 'content-type: multipart/form-data' \\\n  -F 'id=123' \\\n  -F 'file=@file.txt'")
   })
 
+  it("should print a curl with data-binary if body is instance of File and it is not a multipart form data request", function () {
+    let file = new win.File([""], "file.txt", { type: "" })
+
+    let req = {
+      url: "http://example.com",
+      method: "POST",
+      headers: { "content-type": "application/octet-stream" },
+      body: file
+    }
+
+    let curlified = curl(Im.fromJS(req))
+
+    expect(curlified).toEqual("curl -X 'POST' \\\n  'http://example.com' \\\n  -H 'content-type: application/octet-stream' \\\n  --data-binary '@file.txt'")
+  })
+
   it("prints a curl post statement from an object", function () {
     let req = {
       url: "http://example.com",