nestjs / swagger

OpenAPI (Swagger) module for Nest framework (node.js) :earth_americas:
https://nestjs.com
MIT License
1.68k stars 471 forks source link

[Feature Request] Multiple response with the same status code #225

Open ziadalzarka opened 5 years ago

ziadalzarka commented 5 years ago

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

Swagger only allows one response per status code, but sometimes we have more than one response body.

Expected behavior

We can work around this in the decorator by:

Minimal reproduction of the problem

@ApiForbiddenResponse({ type: UnauthorizedTokenException })
@ApiForbiddenResponse({ type: InsufficientRolesException })

What is the motivation / use case for changing the behavior?

I have more than one error response with different Dto classes using the same status code.

kamilmysliwiec commented 5 years ago

Would you like to create a PR? :)

ziadalzarka commented 5 years ago

Would you like to create a PR? :)

Yes I have created a PR resolving the issue.

ziadalzarka commented 5 years ago

Any updates?

wilsson commented 4 years ago

something new?

piotrgomola commented 4 years ago

+1

peterwbeck commented 4 years ago

Would love to see this merged.

fwoelffel commented 4 years ago

I might be late for this but why don't you use the SchemaObject interface to document your responses?

@ApiForbiddenResponse({
  schema: {
    anyOf: refs([
      UnauthorizedTokenException,
      InsufficientRolesException
    ])
  }
})
melangyun commented 4 years ago

@fwoelffel Hi I came in while Googleing because I needed a multiple response from swagger. The multi error response you have attached is resolved, but it does not apply to the 200 success responses. Because the arrangement of "refs" only request "funcion types." What kind of settings do I need to set to respond to 200 multiple responses? I can't see anything in browser with Schema Object like this:

  @ApiOkResponse({
    schema: { anyOf: [{ $ref: getSchemaPath(UserOrderInfoDto) }] },
  })

// or

  @ApiOkResponse({
    schema: { anyOf: [{ type: getSchemaPath(UserOrderInfoDto) }] },
  })

;(

fwoelffel commented 4 years ago

@fwoelffel Hi I came in while Googleing because I needed a multiple response from swagger. The multi error response you have attached is resolved, but it does not apply to the 200 success responses. Because the arrangement of "refs" only request "funcion types." What kind of settings do I need to set to respond to 200 multiple responses? I can't see anything with Schema Object setting like this:

I'm not sure I fully understand your issue, but I'll try to help.

Have you tried with refs like I did in my example? refs can be imported from @nestjs/swagger.

import {
  ApiOkResponse,
  refs,
} from '@nestjs/swagger';

// ...

@ApiOkResponse({
  schema: {
    anyOf: refs([
      UserOrderInfoDto
    ]),
  },
})
melangyun commented 4 years ago

@fwoelffel Yes, I tried. However, refs require function array type and my UserOrerInfoDto is Class type, so type error occured.

스크린샷 2020-08-05 16 57 50
fwoelffel commented 4 years ago

@fwoelffel Yes, I tried. However, refs require function array type and my UserOrerInfoDto is Class type, so type error occured.

스크린샷 2020-08-05 16 57 50

Could you share your UserOrderInfoDto implementation? Make sure it is a class and not an interface.


EDIT: Ok, I'm sorry I didn't realize this earlier. Your DTOs should not be wrapped in an array:

@ApiOkResponse({
  schema: {
    anyOf: refs(
      UserOrderInfoDto
    ),
  },
})

Sorry for misleading you. 😞

melangyun commented 4 years ago

Thank you very much! Your code works well and expresses multiple responses well. In fact, the essential problem was that what I was going to designate as a response "schema" was not registered as a "schemas", so swagger showed empty object.

For those who have missed the official document like me, write additional. If your DTO is not represented or read by an empty object, you must register the DTO through @ApiExtraModels()

스크린샷 2020-08-06 00 55 43

Thank you for your help!!

woodcockjosh commented 3 years ago

I would like this as well

micalevisk commented 3 years ago

If it exists, we add a space to the response code

but OpenAPI v3 doesn't allow that :(

error

micalevisk commented 3 years ago

Another issue that comes with using multiple @ApiX() decorators in this way is that we won't be able to implement the following pattern:

@Controller()
@ApiOkResponse({ description: 'fallback ok' })
class SomeController {
  @Get('foo1')
  foo1() {} // Will have 200 with 'fallback ok' description
  @Get('foo2')
  foo2() {} // Same as above

  @Get('bar')
  @ApiOkResponse({ description: 'only this one will appear in the docs for this endpoint' })
  bar() {}
}
kasvith commented 3 years ago

Isn't this essentially adding multiple response examples to the same swagger path?

VulchiSarath commented 2 years ago

I tried the same but nothing will work. @ApiOkResponse({ schema: { oneOf: refs( UpdateDeviceTokenResponse ) } }) UpdateDeviceTokenResponse - This will be a class. image

micalevisk commented 2 years ago

@VulchiSarath have you registered UpdateDeviceTokenResponse? either configuring the nestjs swagger plugin or using @ApiExtraModels(UpdateDeviceTokenResponse)

https://docs.nestjs.com/openapi/types-and-parameters#extra-models

brandart commented 2 years ago

does someone know how to return either UserOrderInfoDto | null?

this does not work unfortunately..:

@ApiOkResponse({
  schema: {
    anyOf: refs(
      UserOrderInfoDto,
      null
    ),
  },
})
Displee commented 2 years ago

What if my responses are just strings? This doesn't seem to work:

    @ApiBadRequestResponse({
        schema: {
            example: 'Response 1',
            anyOf: refs(
                () => 'Response 1',
                () => 'Response 2',
                () => 'Response 3'
            )
        }
    })
JoshApplauso commented 1 year ago

Try passing a SchemaObjectinstead of ReferenceObject

{
    status: HttpStatus.NOT_FOUND,
    schema: {
      anyOf: [
        {
          title: 'Customer',
          description: `The customer couldn't be found in our system, please verify and try again.`,
          example: `The customer couldn't be found in our system, please verify and try again.`,
        },
        {
          title: 'Supplier',
          description: `The supplier couldn't be found in our system, please verify and try again.`,
          example: `The supplier couldn't be found in our system, please verify and try again.`,
        },
        {
          title: 'PartNumbers',
          description: `The information provided couldn't be found in the part numbers master, please verify and try again.`,
          example: `The information provided couldn't be found in the part numbers master, please verify and try again.`,
        },
      ],
    },
  },

imagen

lytaitruong commented 1 year ago

One solution I try a lot of time to handle. Maybe it can help your guys

export interface IAppError {
  name: string
  code: string
  status: HttpStatus
  message: string
}

export const ApiSchemaRes = (schema: IAppError): Type<IError> => {
  class SchemaResponse implements IError {
    @ApiProperty({ name: 'type', type: 'enum', enum: ErrorType, default: ErrorType.REST })
    type: ErrorType

    @ApiProperty({ name: 'time', type: 'string', default: new Date().toISOString() })
    time: string

    @ApiProperty({ name: 'code', type: 'string', default: schema.code })
    code: string

    @ApiProperty({ name: 'code', type: 'string', default: schema.message })
    message: string
  }
  Object.defineProperty(SchemaResponse, 'name', { writable: true, value: schema.name })
  return SchemaResponse
}

export const ApiFailedRes = (...schemas: IAppError[]) => {
  return applyDecorators(
    ApiResponse({
      status: schemas[0].status,
      content: {
        'application/json': {
          examples: schemas.reduce((list, schema) => {
            list[schema.name] = { value: schema }
            return list
          }, {}),
        },
      },
    }),
  )
}

Then I add this decorator to any controller I want to

ControllerAttachSchemaSwagger

The result will be look like

Swagger Result Response

cassioscofield commented 6 months ago

Thanks for this great idea @lytaitruong I have been searching the internet for this for 2 days now.

I've built something similar base on this but using the already existing ApiResponse interface for compatibility. I'll post this here in case its helpful for someone else:

Decorator apiMultipleResponse.decorator.ts

import { applyDecorators } from "@nestjs/common"
import { ApiResponse, ApiResponseSchemaHost } from "@nestjs/swagger"

export const ApiMultipleResponse = (...entries: ApiResponseSchemaHost[]) => {
    return applyDecorators(
      ApiResponse({
        status: entries[0].status || 200,
        content: {
          'application/json': {
            schema: entries[0].schema,
            examples: entries.reduce((list, entry) => {
              list[entry.description] = {
                value: entry.schema.example,
              }
              return list;
            }, {}),
          },
        },
      }),
    )
  }

Sample usage: test.controller.ts

@Get('/test')
@ApiMultipleResponse(
      {
        status: 200,
        description: 'Example not empty',
        schema: {
          type: 'object',
          example: [{ test: 1}],
        },
      },
      {
        status: 200,
        description: 'Example empty',
        schema: {
          type: 'object',
          example: [],
        },
      }
)
async getTest(@Request() req): Promise<any[]> {
    return [];
}

image