Repository: nestjs/swagger (ts)

Issue:
Invalid specification for nested schema with additional properties
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.

ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
-->

## I'm submitting a...
<!-- 
Please search GitHub for a similar issue or PR before submitting.
Check one of the following options with "x" -->
<pre><code>
[x] Regression <!--(a behavior that used to work and stopped working in a new release)-->
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
</code></pre>

## Current behavior
<!-- Describe how the issue manifests. -->

When using a nested object in a Swagger schema, additional properties (e.g. `description`) are not properly added to the schema. They are not visible in the Swagger UI. 

Generated Schema (extract from Specification):

```json5
// components.schemas:
{
  "NestedProperty": {
    "type": "object",
    "properties": {}
  },
  "NestedObjectDto": {
    "type": "object",
    "properties": {
      "foo": {
        "description": "Foo Bar", // <--- $ref does not allow any other properties
        "$ref": "#/components/schemas/NestedProperty"
      }
    },
    "required": ["foo"]
  }
}
```

## Expected behavior
<!-- Describe what the desired behavior would be. -->

Additional properties should be merged with the referenced schema using `allOf`. This was already the case in `@nestjs/swagger@3`.

The generated schema should look like this:

```json5
// components.schemas:
{
  "NestedProperty": {
    "type": "object",
    "properties": {}
  },
  "NestedObjectDto": {
    "type": "object",
    "properties": {
      "foo": {
        "allOf": [ // <- Using allOf to merge $ref with additional props
          { "description": "Foo Bar" },
          { "$ref": "#/components/schemas/NestedProperty" }
        ]
      }
    },
    "required": ["foo"]
  }
}
```

## Minimal reproduction of the problem with instructions
<!-- Please share a repo, a gist, or step-by-step instructions. -->

1. Create a new Nestjs App
2. Install swagger integration according to docs
3. Add this dto:

```typescript
import { ApiProperty } from '@nestjs/swagger';

class NestedProperty {}

export class NestedObjectDto {
  @ApiProperty({ description: 'Foo Bar', type: NestedProperty })
  foo: NestedProperty;
}
```

4. Change default `AppController#getHello` to use the DTO:

```diff
  @Get()
- getHello(): string {
+ getHello(@Body() body: NestedObjectDto): string {
    return this.appService.getHello();
  }
```
5. Check Swagger UI for missing description

## What is the motivation / use case for changing the behavior?
<!-- Describe the motivation or the concrete use case. -->
I want to add additional properties to nested schema objects (`description`, `nullable`, etc.).

## Environment

<pre><code>
Nest version: 6.11.5
<!-- Check whether this is still an issue in the most recent Nest version -->
 
For Tooling issues:
- Node version: v12.13.0
- Platform:  Linux
Others:
- @nestjs/swagger: 4.2.6

</code></pre>


Pull request description:
fix(): nested schema with additional properties causes invalid spec
Fixes #549

## 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)
- [ ] 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?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: #549

Behavious is described in ticket


## What is the new behavior?

New behaviour is describe in ticket and also in the test suite.


## Does this PR introduce a breaking change?
```
[x] Yes
[ ] No
```

If anyone was relying on the exact generated spec for nested properties, this would break it for them.
As the generated spec was not a valid OAS 3.0 specification, I think it is okay to break the existing behaviour.

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information

Patch:
diff --git a/lib/services/schema-object-factory.ts b/lib/services/schema-object-factory.ts
index da04fd65..0c17eb6a 100644
--- a/lib/services/schema-object-factory.ts
+++ b/lib/services/schema-object-factory.ts
@@ -233,9 +233,8 @@ export class SchemaObjectFactory {
     const keysToRemove = ['type', 'isArray'];
     const validMetadataObject = omit(metadata, keysToRemove);
     return ({
-      ...validMetadataObject,
       name: metadata.name || key,
-      $ref
+      allOf: [{ $ref }, { ...validMetadataObject }]
     } as any) as BaseParameterObject;
   }
 


Test patch:
diff --git a/e2e/api-spec.json b/e2e/api-spec.json
index ae35f968..0103ed64 100644
--- a/e2e/api-spec.json
+++ b/e2e/api-spec.json
@@ -118,8 +118,14 @@
             }
           },
           "tag": {
-            "description": "tag",
-            "$ref": "#/components/schemas/TagDto"
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/TagDto"
+              },
+              {
+                "description": "tag"
+              }
+            ]
           }
         },
         "required": [
diff --git a/test/services/fixtures/create-user.dto.ts b/test/services/fixtures/create-user.dto.ts
index be88fe42..9cef41a7 100644
--- a/test/services/fixtures/create-user.dto.ts
+++ b/test/services/fixtures/create-user.dto.ts
@@ -35,6 +35,7 @@ export class CreateUserDto {
   custom: any;
 
   @ApiProperty({
+    description: 'Profile',
     type: () => CreateProfileDto
   })
   profile: CreateProfileDto;
diff --git a/test/services/schema-object-factory.spec.ts b/test/services/schema-object-factory.spec.ts
index 7450ebe5..ff4929e1 100644
--- a/test/services/schema-object-factory.spec.ts
+++ b/test/services/schema-object-factory.spec.ts
@@ -64,7 +64,14 @@ describe('SchemaObjectFactory', () => {
             }
           },
           profile: {
-            $ref: '#/components/schemas/CreateProfileDto'
+            allOf: [
+              {
+                $ref: '#/components/schemas/CreateProfileDto'
+              },
+              {
+                description: 'Profile'
+              }
+            ]
           },
           tags: {
             items: {
@@ -119,7 +126,12 @@ describe('SchemaObjectFactory', () => {
             type: 'string'
           },
           parent: {
-            $ref: '#/components/schemas/CreateUserDto'
+            allOf: [
+              {
+                $ref: '#/components/schemas/CreateUserDto'
+              },
+              {}
+            ]
           }
         },
         required: ['firstname', 'lastname', 'parent']