Create Swagger Document from a Schema - using swagger-ui-express - swagger

I'm using joi-to-swagger and swagger-ui-express and I'm really struggling to find out how to get this schema to show up correctly. It's getting ignored in all scenarios and I can't find the documentation I need to correctly use the schema.
My file:
import j2s from 'joi-to-swagger'
import joi from 'joi'
import swaggerUi from 'swagger-ui-express'
const myJoiSchema = joi
.object({
text: joi.string().max(100).required(),
})
.required()
const schema = j2s(myJoiSchema).swagger
const swaggerDoc = {
swagger: '2.0',
info: {
title: 'title',
version: '1.0',
},
paths: {
'/my-url': {
post: {
summary: 'my api',
// consumes: 'application/json',
// parameters: mySchema, didn't work
requestBody: {
required: true,
schema: {
$ref: '#/components/schemas/mySchema',
},
},
},
},
},
components: {
schemas: {
mySchema: schema,
},
},
}
router.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc))
the schema that is produced for value schema is:
{
type: 'object',
properties: {
text: { type: 'string', maxLength: 100 },
},
required: [ 'text' ],
additionalProperties: false
}
How is the schema supposed to be properly used in the swagger doc? It might be special with swagger-ui-express but I can't confirm I've tested it correctly.

Related

How can I display multiple ResponseDTOs' schemas in Swagger/NestJS?

I have this route which can return one of these two different DTOs:
#Get()
#ApiQuery({ name: 'legacy', description: "'Y' to get houses legacy" })
async findAllHouses(
#Query('legacy') legacy: string,
): Promise<HousesDto[] | HousesLegacyDto[]> {
...
}
I want to display both of these ResponseDTOs in swagger.
I've tried this decorator:
#ApiOkResponse({
schema: { oneOf: refs(HousesDto, HousesLegacyDto) },
})
// OR
#ApiOkResponse({
schema: {
oneOf: [
{ $ref: getSchemaPath(HousesDto) },
{ $ref: getSchemaPath(HousesLegacyDto) },
],
},
})
with #ApiExtraModels() on top of DTO classes and #ApiProperty() on each properties.
But I still get empty objects in Swagger and I suppose it would not have even taken array types in consideration.
How can I display both of these schemas properly?
Seems to me like a lot of very obscure solutions have been posted here and there, so I will try to clarify what needs to be done.
You have two DTOs:
export class SomeStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Status',
example: 'in_progress',
})
#IsString()
status: string;
}
export class ErrorStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Error',
example: 'Some error string',
})
#IsString()
error: string;
}
Then you have your controller:
#UseGuards(AccountTypesGuard)
#ApiOperation({ summary: 'Get status of...' })
#Get('status')
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
async getPullStatus(
#Request() req,
#Param('id', ParseIntPipe) someId: number,
): Promise<SomeStatusDto | ErrorStatusDto> {
// check if someId belongs to user
const idBelongsToUser = await this.myService.validateSomeId(
req.user.id,
someId,
);
if (!idBelongsToUser) {
throw new ForbiddenException(
`SomeId does not belong to user (someId=${someId}, userId=${req.user.id})`,
);
}
const key = `status-${someId}`;
const response = await this.redisService.getByKey(key);
return response ? response : {};
}
Note the solution below. You need to reference the DTOs as #ApiExtraModels() and then you can add them as anyOf: refs(...) in your schema.
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
Hope this helps somebody :)
so I encountered a similar issue and this is how you could get the output shown in the image above.
Using the #ApiResponse decorator you could set the two responses using the examples property, try the code sample below
#ApiResponse({
status: 200,
description: 'Successful response',
content: {
'application/json': {
examples: {
HousesDto: { value: HousesDto },
HousesLegacyDto: { value: HousesLegacyDto },
},
},
},
})

build fastify-swagger scheme for multiple response data with a single 200 code

I use fastify-swagger (https://github.com/fastify/fastify-swagger) for my fastify server. I also use the JSEND (https://github.com/omniti-labs/jsend) standard, which means that the data returned may be different with the same response code 200.
I do not understand how I can build a scheme for multiple response data with a single 200 code.
The Open Api 3 specification (which fastify-swagger supports) seems to allow you to describe this (https://swagger.io/specification /)
I tried this, but it doesn't work. The response from the server is simply not validated and comes as is.
const chema_1 = {
description: 'description',
type: 'object',
properties: {
status: {
type: 'string',
description: 'string'
},
}
}
const chema_2 = {
description: 'description',
type: 'object',
properties: {
body: {
type: 'string',
description: 'number'
},
}
}
fastify.route({
method: 'GET',
url: '/coursesList',
schema: {
response: {
'200': {
oneOf: [
chema_1,
chema_2,
]
}
}
},
handler: async (request, reply) => {
// handler
}
})
I also tried this. Same behavior:
const chema_1 = {
response: {
'200': {
description: 'description',
type: 'object',
properties: {
status: {
type: 'string',
description: 'string'
},
}
}
}
}
const chema_2 = {
response: {
'200': {
description: 'description',
type: 'object',
properties: {
body: {
type: 'string',
description: 'number'
},
}
}
}
}
fastify.route({
method: 'GET',
url: '/coursesList',
schema: {
oneOf: [
chema_1,
chema_2,
]
},
handler: async (request, reply) => {
// handler
}
})
There are no such examples in the documentation. Maybe someone can help me?

NestJs/Swagger(OpenAPI) defining nested objects in query parameters

I'm trying to correctly define OpenAPI spec for the purposes of generating api client from that spec. I've encoutered a problem where we have a complex query object with nested objects and arrays of objects for get a GET route.
Lets take these classes as an example.
class Person {
#ApiProperty()
name!: string
#ApiProperty()
location!: string
}
class CompanyDto {
#ApiProperty()
name!: string
#ApiProperty({
type: [Person],
})
employees!: Person[]
}
And a get request with #Query decorator.
#Get('test')
async testing(#Query() dto: CompanyDto): Promise<void> {
// ...
}
What I'm getting is.
{
get: {
operationId: 'testing',
parameters: [
{
name: 'name',
required: true,
in: 'query',
schema: {
type: 'string',
},
},
{
name: 'name',
in: 'query',
required: true,
schema: {
type: 'string',
},
},
{
name: 'location',
in: 'query',
required: true,
schema: {
type: 'string',
},
},
],
responses: {
'200': {
description: '',
},
},
tags: ['booking'],
},
}
I've also tries to define Query params by adding #ApiQuery decorator and it almost works.
#ApiQuery({
style: 'deepObject',
type: CompanyDto,
})
--
{
get: {
operationId: 'testing',
parameters: [
{
name: 'name',
required: true,
in: 'query',
schema: {
type: 'string',
},
},
{
name: 'name',
in: 'query',
required: true,
schema: {
type: 'string',
},
},
{
name: 'location',
in: 'query',
required: true,
schema: {
type: 'string',
},
},
{
name: 'name',
in: 'query',
required: true,
schema: {
type: 'string',
},
},
{
name: 'employees',
in: 'query',
required: true,
schema: {
type: 'array',
items: {
$ref: '#/components/schemas/Person',
},
},
},
],
responses: {
'200': {
description: '',
},
},
tags: ['booking'],
},
}
However now I'm getting duplicate query definitions mashed in to one. Is there a way to prevent or overwrite #Query definition? Or just a better way to define complex #Query in general?
Ended up creating a custom decorator to extract query without generating OpenAPI Spec.
export const SilentQuery = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
if (data) {
return request.query[data]
} else {
return request.query
}
},
)
So now you can use #ApiQuery with deepObject style.
Also if your're using ValidationPipes with class-validator for example. Make sure to set validateCustomDecorators to true
#SilentQuery(new ValidationPipe({ validateCustomDecorators: true }))

fastify-http-proxy: Swagger is flooded with prefix URL REST methods

I am trying out to write the swagger using fastify and fastify-swagger in localhost. However my real server is running somewhere else which has the backend logic. I am trying to proxy my localhost API call to the remote upstream using fastify-http-proxy.
So in essence, the swagger I want to serve from localhost and and all the actual API call I want to proxy to remote upstream.
My fastify-http-proxy configuration:
fastify.register(require('fastify-http-proxy'), {
upstream: "https://mystaging.com/notifications",
prefix: '/notifications',
replyOptions: {
rewriteRequestHeaders: (originalRequest, headers) => {
return {
...headers,
};
},
},
async preHandler (request, reply) {
console.log('Request URL: ', request.url);
if (request?.url?.includes('api-docs')) {
console.log('Request contains api-docs. Ignore the request...');
return;
}
},
});
Basically my intention is that any upcoming request coming to http://127.0.0.1:8092/notifications should be proxied to and served by the https://mystaging.com/notifications. E.g. POST http://127.0.0.1:8092/notifications/agent-notifications should be actually forwarded to https://mystaging.com/notifications/agent-notification. That's why I configured the fastify-http-proxy as the above way.
My fastify-swagger configuration:
fastify.register(require('fastify-swagger'), swaggerConfig);
const swaggerConfig = {
openapi: {
info: {
title: `foo bar`,
description: `API forwarded for notifications service`,
version: '1.0.0'
},
servers: [
{ url: `${server}` },
],
tags: [
{ name: 'notifications', description: "Notifications"},
],
components: {
securitySchemes: {
authorization: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'JWT access token to authorize the request, sent on the request header.'
}
}
}
},
exposeRoute: true,
routePrefix: `localhost:8092/notifications/external/api-docs`,
};
Once I opened the swagger in the browser using URL http://localhost:8092/notifications/external/api-docs/static/index.html, I am seeing other than the notifications tag, there are very REST verb of /notifications/ is coming up as the attached picture.
Once I turn of the fastify-http-proxy, everything works fine.
What am I missing/messing up here.
Screenshot of spurious default routes:
Versions used:
"fastify": "^3.21.6",
"fastify-auth0-verify": "^0.5.2",
"fastify-swagger": "^4.8.4",
"fastify-cors": "^6.0.2",
"fastify-http-proxy": "^6.2.1",
Notes added further:
The route specification looks like:
module.exports = async function (fastify, opts) {
fastify.route({
method: 'POST',
url: `${url}/agent-notifications`,
schema: {
description: 'Something',
tags: ['notifications'],
params:{
$ref: 'agent-notifications-proxy-request#',
},
},
handler: async (request, reply) => {
}
});
}
And here is the agent-notifications-proxy-request:
module.exports = function (fastify) {
fastify.addSchema({
$id: 'agent-notifications-proxy-request',
title: "AgentNotificationRequest",
description:'Trying out',
type: 'object',
example: 'Just pass the same stuff as-is',
properties: {
'accountId': {
type: 'string'
},
'templateName': {
type: 'string'
},
"bodyParams": {
type: "object",
},
"includeAdmin": {
type: 'boolean'
},
"serviceName": {
type: 'string'
},
},
});
};

Adding custom CSS to fastify-swagger

I need to add some custom CSS to the fastify-swagger. My fastify-swagger version is: 3.5.0.
Now I did checked the fastify-swagger PR #353. However not able to figure out in the uiConfig .. how to pass the custom css.
Suppose I have a simple css file as MyCSS.scss:
.introduction {
.summary {
text-shadow: 0in;
}
}
.code {
background-color: gray;
color: black;
}
My swagger config looks like:
const swaggerConfig = {
swagger: {
info: {
title: 'My swagger',
description: `something`,
version: '2.0.0'
},
externalDocs: {
url: 'https://swagger.io',
description: 'Find more info here'
},
schemes: [`${schema}`],
consumes: ['application/json'],
produces: ['application/json'],
tags: [
{ name: 'tag1', description: 'desc1' },
{ name: 'tag2', description: 'desc2' },
],
securityDefinitions: {
Authorization_Token: {
type: 'apiKey',
name: 'Authorization',
in: 'header'
},
User_Token: {
type: 'apiKey',
name: 'X-User-token',
in: 'header'
}
}
},
exposeRoute: true,
routePrefix: `${constants.EXTERNAL_PATH}/api-docs`
};
fastify.register(require('fastify-swagger'), swaggerConfig);
The UI config doc says something like this (only literal value):
Any help would be highly appreciated.

Resources