Repository: nestjs/swagger
Pull request #1454 (2021-07-15 20:14:06)

fix(transformer): prefer explicit config over auto-detected schema
## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [x] Tests for the changes have been added (for bug fixes / features)
- [x] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
```
[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:
```

## What is the current behavior?
@ApiOperation.responses was disregarded as the auto-detected responses were given preference. Auto-detection happens always, even if no return type is configured.

Issue Number: #1420


## What is the new behavior?
Explicit config is given preference over auto-detected schema

## Does this PR introduce a breaking change?
```
[ ] Yes
[x] No
```

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information
This PR only fixes the issue on a smaller level, however there is a scope for improving how the actual merging should happen for the schema extracted from different areas.

Each schema extracted should be assigned a priority status (like constants in the library code). For example, body extracted from controller function argument should be given a priority of 0, one that is defined as @ApiParam should be given a priority of 10, and the one in @ApiOperation a priority of 50. When making the final schema, we should be able to order all the configured schemas by priority and pick first one.

This is a very vague suggestion, and not sure if there is a way to implement this as we'd be dealing with nested json structures, but may provide a solution for easily deciding what's important over what!

kamilmysliwiec: Thanks for your contribution! Can you please update tests as well?
tiholic: @kamilmysliwiec updated tests, please review and release! Thanks :)
tiholic: @kamilmysliwiec please review and release changes in this PR. Thanks :)
tiholic: @kamilmysliwiec little attention here please. I'm desperate to get this released soon 😅 
erikstoll: I have a similar use case and also noticed this bug. I looked into the PR and tested it with my use case. It fixes the bug for me and enables my use case.

So I’d like to ask what the status of this PR is? Can it be merged? Can something be contributed here?

I’m also desperate to have this fix released.
tiholic: nudge @kamilmysliwiec
eugenk: nudge @kamilmysliwiec again ;)

Sorry for pushing it this hard. I just think that the importance of this fix for some projects is underestimated.
raphaelsoul: merge wanted. it is really small change

Patch:
diff --git a/lib/swagger-transformer.ts b/lib/swagger-transformer.ts
index ea8017e6..f1e5455d 100644
--- a/lib/swagger-transformer.ts
+++ b/lib/swagger-transformer.ts
@@ -18,9 +18,9 @@ export class SwaggerTransformer {
         ({ root }: Record<'root', any>) => root.method
       );
       return mapValues(keyByMethod, (route: any) => {
-        return {
-          ...omit(route.root, ['method', 'path']),
-          ...omit(route, 'root')
+        return {	
+          ...omit(route, 'root'),
+          ...omit(route.root, ['method', 'path'])
         };
       });
     });


Test patch:
diff --git a/e2e/api-spec.json b/e2e/api-spec.json
index ea765f81..66159814 100644
--- a/e2e/api-spec.json
+++ b/e2e/api-spec.json
@@ -729,6 +729,58 @@
           }
         ]
       }
+    },
+    "/api/cats/download": {
+      "get": {
+        "responses": {
+          "200": {
+            "description": "binary file for download",
+            "content": {
+              "application/pdf": {
+                "schema": {
+                  "type": "string",
+                  "format": "binary"
+                }
+              },
+              "image/jpeg": {
+                "schema": {
+                  "type": "string",
+                  "format": "binary"
+                }
+              }
+            }
+          }
+        },
+        "tags": [
+          "cats"
+        ],
+        "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
+          {
+            "bearer": []
+          },
+          {
+            "basic": []
+          }
+        ],
+        "operationId": "CatsController_download",
+        "summary": "",
+        "parameters": [
+          {
+            "name": "header",
+            "in": "header",
+            "description": "Test",
+            "required": false,
+            "schema": {
+              "default": "test",
+              "type": "string"
+            }
+          }
+        ]
+      }
     }
   },
   "info": {
diff --git a/e2e/src/cats/cats.controller.ts b/e2e/src/cats/cats.controller.ts
index 89300f13..3c6abcc6 100644
--- a/e2e/src/cats/cats.controller.ts
+++ b/e2e/src/cats/cats.controller.ts
@@ -108,4 +108,18 @@ export class CatsController {
     maxLength: 100
   } as any)
   getWithRandomQuery(@Query('type') type: string) {}
+
+  @Get('download')
+  @ApiOperation({
+    responses: {
+      '200': {
+        description: 'binary file for download',
+        content: {
+          'application/pdf': { schema: { type: 'string', format: 'binary' } },
+          'image/jpeg': { schema: { type: 'string', format: 'binary' } }
+        }
+      }
+    }
+  })
+  download() {}
 }
diff --git a/e2e/validate-schema.e2e-spec.ts b/e2e/validate-schema.e2e-spec.ts
index 06e6dd30..bdda08af 100644
--- a/e2e/validate-schema.e2e-spec.ts
+++ b/e2e/validate-schema.e2e-spec.ts
@@ -116,4 +116,19 @@ describe('Validate OpenAPI schema', () => {
     expect(api.components.schemas).toHaveProperty('Person');
     expect(api.components.schemas).toHaveProperty('Cat');
   });
+
+  it('should consider explicit config over auto-detected schema', async () => {
+    const document = SwaggerModule.createDocument(app, options);
+    expect(document.paths['/api/cats/download'].get.responses).toEqual({
+      '200': {
+        description: 'binary file for download',
+        content: {
+          'application/pdf': {
+            schema: { type: 'string', format: 'binary' }
+          },
+          'image/jpeg': { schema: { type: 'string', format: 'binary' } }
+        }
+      }
+    })
+  });
 });