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
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 },
},
},
},
})
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?
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 }))
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'
},
},
});
};
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.