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

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 }))

Related

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

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.

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?

Sequelize.s include where ancestor column

Sorry for my bad English, but I do not know how to better formulate the title.
Anyway I have the following 3 models
const ModelAlpha = sequelize.define('tbl_A', {
id: {
type: Sequelize.INTEGER.UNSIGNED,
field: 'a_id',
autoIncrement: true,
primaryKey: true,
}
});
const ModelBeta = sequelize.define('tbl_B', {
id: {
type: Sequelize.INTEGER.UNSIGNED,
field: 'b_id',
autoIncrement: true,
primaryKey: true,
},
attributeB: {
type: Sequelize.TINYINT.UNSIGNED,
field: 'b_attribute'
}
});
const ModelCharlie = sequelize.define('tbl_C', {
id: {
type: Sequelize.INTEGER.UNSIGNED,
field: 'c_id',
autoIncrement: true,
primaryKey: true,
},
attributeC: {
type: Sequelize.TINYINT.UNSIGNED,
field: 'c_attribute'
}
});
One ModelAlpha has one ModelBeta.
ModelAlpha has a foreign key column referencing the primary key of ModelBeta.
ModelAlpha can have many ModelCharlie
I am to trying to achieve the following
SELECT C.* FROM tbl_C
JOIN tbl_A ON tbl_A.a_id = C.fkey_a_id
JOIN tbl_B ON tbl_B.b_id = tbl_A.fkey_b_id
WHERE C.c_attribute = B.b_attribute
AND tbl_A.a_id = 123
So, in other words, all Charlies of the Alpha whose id is 123
and whose c_attribute is equal to b_attribute of the Beta
So far I have written something like the following,
but I am stuck as to what I am supposed to add to the where
clause.
ModelAlpha.belongsTo(ModelBeta, {foreignKey: 'fkey_b_id', as: 'beta'});
ModelAlpha.hasMany(ModelCharlie,{foreignKey: 'fkey_a_id', as : 'charlies'});
ModelAlpha.find({
where: {id: '123'},
include: [
{
model: ModelBeta,
as: 'beta',
attributes: [],
},
{
model: ModelCharlie,
as: 'charlies',
attributes:[],
where: {
attributeC: ......
}
}
],
limit: 1
}).then(result => {
//...
});
Has anyone come up with this situation before?
Thank you.

Jaydata web api with OData provider -- provider fallback failed error

I'm developing an application with jaydata, OData and web api. Source code is given below:
$(document).ready(function () {
$data.Entity.extend('$org.types.Student', {
Name: { type: 'Edm.String', nullable: false, required: true, maxLength: 40 },
Id: { key: true, type: 'Edm.Int32', nullable: false, computed: false, required: true },
Gender: { type: 'Edm.String', nullable: false, required: true, maxLength: 40 },
Age: { type: 'Edm.Int32', nullable: false, required: true, maxLength: 40 }
});
$data.EntityContext.extend("$org.types.OrgContext", {
Students: { type: $data.EntitySet, elementType: $org.types.Student },
});
var context = new $org.types.OrgContext({ name: 'OData', oDataServiceHost: '/api/students' });
context.onReady(function () {
console.log('context initialized.');
});
});
In above JavaScript code, I defined an entity named Student. In context.onReady() method, I'm getting the following error:
Provider fallback failed! jaydata.min.js:100
Any idea, how I could get rid of this error??
As per suggested solution, I tried to change the key from required to computed. But sadly its still giving the same error. Modified code is given below.
$(document).ready(function () {
$data.Entity.extend('Student', {
Id: { key: true, type: 'int', computed: true },
Name: { type: 'string', required: true}
});
$data.EntityContext.extend("$org.types.OrgContext", {
Students: { type: $data.EntitySet, elementType: Student },
});
var context = new $org.types.OrgContext({
name: 'OData',
oDataServiceHost: '/api/students'
});
context.onReady(function () {
console.log('context initialized.');
});
});
I thinks the issue is with Odata provider because I tried the same code with indexdb provider and its working properly.
The issue is caused by the value oDataServiceHost parameter. You should configure it with the service host, not with a particular collection of the service. I don't know if the provider name is case-sensitive or not, but 'oData' is 100% sure.
For WebAPI + OData endpoints the configuration should look like this:
var context = new $org.types.OrgContext({
name: 'oData',
oDataServiceHost: '/odata'
});

Resources