Repository: nestjs/swagger
Pull request #2871 (2024-03-11 01:24:14)

feat: add response example
## 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". -->
- [ ] Bugfix
- [x] 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. -->
```typescript
@ApiNotFoundResponse(
	{
	description: 'Unauthorized.',
	type: HttpExceptionDTO,
	example: {
		message: 'Unauthorized',
		status: 401
	}
)
```
Cannot add example

Issue Number: #2260 


## What is the new behavior?
Will add example alongside `$ref`


## 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



Patch:
diff --git a/lib/decorators/api-response.decorator.ts b/lib/decorators/api-response.decorator.ts
index 092c0b76..c30224a9 100644
--- a/lib/decorators/api-response.decorator.ts
+++ b/lib/decorators/api-response.decorator.ts
@@ -14,6 +14,7 @@ export interface ApiResponseMetadata
   type?: Type<unknown> | Function | [Function] | string;
   isArray?: boolean;
   description?: string;
+  example?: any;
 }
 
 export interface ApiResponseSchemaHost
diff --git a/lib/services/mimetype-content-wrapper.ts b/lib/services/mimetype-content-wrapper.ts
index 545d0444..0f505406 100644
--- a/lib/services/mimetype-content-wrapper.ts
+++ b/lib/services/mimetype-content-wrapper.ts
@@ -1,12 +1,21 @@
 import { ContentObject } from '../interfaces/open-api-spec.interface';
 
+function removeUndefinedKeys(obj: { [x: string]: any }) {
+  Object.entries(obj).forEach(([key, value]) => {
+    if (value === undefined) {
+      delete obj[key];
+    }
+  });
+  return obj;
+}
+
 export class MimetypeContentWrapper {
   wrap(
     mimetype: string[],
     obj: Record<string, any>
   ): Record<'content', ContentObject> {
     const content = mimetype.reduce(
-      (acc, item) => ({ ...acc, [item]: obj }),
+      (acc, item) => ({ ...acc, [item]: removeUndefinedKeys(obj) }),
       {}
     );
     return { content };
diff --git a/lib/services/response-object-mapper.ts b/lib/services/response-object-mapper.ts
index 48889da0..82d0dcc8 100644
--- a/lib/services/response-object-mapper.ts
+++ b/lib/services/response-object-mapper.ts
@@ -1,5 +1,5 @@
 import { omit } from 'lodash';
-import { ApiResponseSchemaHost } from '../decorators';
+import { ApiResponseMetadata, ApiResponseSchemaHost } from '../decorators';
 import { getSchemaPath } from '../utils';
 import { MimetypeContentWrapper } from './mimetype-content-wrapper';
 
@@ -19,7 +19,8 @@ export class ResponseObjectMapper {
           items: {
             $ref: getSchemaPath(name)
           }
-        }
+        },
+        example: response.example
       })
     };
   }
@@ -30,20 +31,25 @@ export class ResponseObjectMapper {
       ...this.mimetypeContentWrapper.wrap(produces, {
         schema: {
           $ref: getSchemaPath(name)
-        }
+        },
+        example: response.example
       })
     };
   }
 
-  wrapSchemaWithContent(response: ApiResponseSchemaHost, produces: string[]) {
-    if (!response.schema) {
+  wrapSchemaWithContent(
+    response: ApiResponseSchemaHost & ApiResponseMetadata,
+    produces: string[]
+  ) {
+    if (!response.schema && !response.example) {
       return response;
     }
     const content = this.mimetypeContentWrapper.wrap(produces, {
-      schema: response.schema
+      schema: response.schema,
+      example: response.example
     });
     return {
-      ...omit(response, 'schema'),
+      ...omit(response, ['schema', 'example']),
       ...content
     };
   }


Test patch:
diff --git a/test/explorer/swagger-explorer.spec.ts b/test/explorer/swagger-explorer.spec.ts
index 2870f792..fc1c46e0 100644
--- a/test/explorer/swagger-explorer.spec.ts
+++ b/test/explorer/swagger-explorer.spec.ts
@@ -97,7 +97,11 @@ describe('SwaggerExplorer', () => {
       @ApiOperation({ summary: 'Create foo' })
       @ApiCreatedResponse({
         type: Foo,
-        description: 'Newly created Foo object'
+        description: 'Newly created Foo object',
+        example: {
+          id: 'foo',
+          name: 'Foo'
+        }
       })
       create(
         @Body() createFoo: CreateFoo,
@@ -138,7 +142,11 @@ describe('SwaggerExplorer', () => {
       @Post('foos')
       @ApiCreatedResponse({
         type: Foo,
-        description: 'Newly created Foo object'
+        description: 'Newly created Foo object',
+        example: {
+          id: 'foo',
+          name: 'Foo'
+        }
       })
       create(
         @Body() createFoo: CreateFoo,
@@ -158,7 +166,11 @@ describe('SwaggerExplorer', () => {
       static [METADATA_FACTORY_NAME]() {
         return {
           create: {
-            summary: 'Create foo'
+            summary: 'Create foo',
+            example: {
+              id: 'foo',
+              name: 'Foo'
+            }
           },
           find: {
             summary: 'List all Foos',
@@ -301,7 +313,8 @@ describe('SwaggerExplorer', () => {
       ).toEqual({
         schema: {
           $ref: '#/components/schemas/Foo'
-        }
+        },
+        example: { id: 'foo', name: 'Foo' }
       });
 
       // GET