feat: swagger annotations (#713)
This commit is contained in:
parent
fe357574bd
commit
8eede4b3c4
37
README.md
37
README.md
@ -458,6 +458,43 @@ is resolved to:
|
||||
|
||||
`WHERE ... AND (id = 5 OR id = 7) AND name = 'Milo' AND ...`
|
||||
|
||||
## Swagger
|
||||
|
||||
You can use two default decorators @ApiOkResponsePaginated and @ApiPagination to generate swagger documentation for your endpoints
|
||||
|
||||
`@ApiOkPaginatedResponse` is for response body, return http[](https://) status is 200
|
||||
|
||||
`@ApiPaginationQuery` is for query params
|
||||
|
||||
|
||||
```typescript
|
||||
@Get()
|
||||
@ApiOkPaginatedResponse(
|
||||
UserDto,
|
||||
USER_PAGINATION_CONFIG,
|
||||
)
|
||||
@ApiPaginationQuery(USER_PAGINATION_CONFIG)
|
||||
async findAll(
|
||||
@Paginate()
|
||||
query: PaginateQuery,
|
||||
): Promise<Paginated<UserEntity>> {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
There is also some syntax sugar for this, and you can use only one decorator `@PaginatedSwaggerDocs` for both response body and query params
|
||||
|
||||
```typescript
|
||||
@Get()
|
||||
@PaginatedSwaggerDocs(UserDto, USER_PAGINATION_CONFIG)
|
||||
async findAll(
|
||||
@Paginate()
|
||||
query: PaginateQuery,
|
||||
): Promise<Paginated<UserEntity>> {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The package does not report error reasons in the response bodies. They are instead
|
||||
|
964
package-lock.json
generated
964
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -32,6 +32,8 @@
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/testing": "^10.2.1",
|
||||
"@nestjs/platform-express": "^10.2.1",
|
||||
"@nestjs/common": "^10.2.1",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.4",
|
||||
@ -59,6 +61,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.2.1",
|
||||
"@nestjs/swagger": "^7.1.8",
|
||||
"express": "^4.18.2",
|
||||
"fastify": "^4.21.0",
|
||||
"typeorm": "^0.3.17"
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './decorator'
|
||||
export * from './paginate'
|
||||
export * from './swagger'
|
||||
|
65
src/swagger/api-ok-paginated-response.decorator.ts
Normal file
65
src/swagger/api-ok-paginated-response.decorator.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { applyDecorators, Type } from '@nestjs/common'
|
||||
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'
|
||||
import { ReferenceObject, SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'
|
||||
import { PaginateConfig } from '../paginate'
|
||||
import { PaginatedDocumented } from './paginated-swagger.type'
|
||||
|
||||
export const ApiOkPaginatedResponse = <DTO extends Type<unknown>>(
|
||||
dataDto: DTO,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
paginatedConfig: PaginateConfig<any>
|
||||
) => {
|
||||
const cols = paginatedConfig?.filterableColumns || {}
|
||||
|
||||
return applyDecorators(
|
||||
ApiExtraModels(PaginatedDocumented, dataDto),
|
||||
ApiOkResponse({
|
||||
schema: {
|
||||
allOf: [
|
||||
{ $ref: getSchemaPath(PaginatedDocumented) },
|
||||
{
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: { $ref: getSchemaPath(dataDto) },
|
||||
},
|
||||
meta: {
|
||||
properties: {
|
||||
select: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: paginatedConfig?.select,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
type: 'object',
|
||||
properties: Object.keys(cols).reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, SchemaObject | ReferenceObject>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
158
src/swagger/api-paginated-query.decorator.ts
Normal file
158
src/swagger/api-paginated-query.decorator.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, FilterOperator, FilterSuffix, PaginateConfig } from '../paginate'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { FilterComparator } from '../filter'
|
||||
import { applyDecorators } from '@nestjs/common'
|
||||
|
||||
const DEFAULT_VALUE_KEY = 'Default Value'
|
||||
|
||||
function p(key: string | 'Format' | 'Example' | 'Default Value' | 'Max Value', value: string) {
|
||||
return `<p>
|
||||
<b>${key}: </b> ${value}
|
||||
</p>`
|
||||
}
|
||||
|
||||
function li(key: string | 'Available Fields', values: string[]) {
|
||||
return `<h4>${key}</h4><ul>${values.map((v) => `<li>${v}</li>`).join('\n')}</ul>`
|
||||
}
|
||||
|
||||
export function SortBy(paginationConfig: PaginateConfig<any>) {
|
||||
const defaultSortMessage = paginationConfig.defaultSortBy
|
||||
? paginationConfig.defaultSortBy.map(([col, order]) => `${col}:${order}`).join(',')
|
||||
: 'No default sorting specified, the result order is not guaranteed'
|
||||
|
||||
return ApiQuery({
|
||||
name: 'sortBy',
|
||||
isArray: true,
|
||||
description: `Parameter to sort by.
|
||||
<p>To sort by multiple fields, just provide query param multiple types. The order in url defines an order of sorting</p>
|
||||
${p('Format', 'fieldName:DIRECTION')}
|
||||
${p('Example', 'sortBy=id:DESC&sortBy=createdAt:ASC')}
|
||||
${p('Default Value', defaultSortMessage)}
|
||||
${li('Available Fields', paginationConfig.sortableColumns)}
|
||||
`,
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
}
|
||||
|
||||
function Limit(paginationConfig: PaginateConfig<any>) {
|
||||
return ApiQuery({
|
||||
name: 'limit',
|
||||
description: `Number of records per page.
|
||||
${p('Example', '20')}
|
||||
${p(DEFAULT_VALUE_KEY, paginationConfig?.defaultLimit?.toString() || DEFAULT_LIMIT.toString())}
|
||||
${p('Max Value', paginationConfig.maxLimit?.toString() || DEFAULT_MAX_LIMIT.toString())}
|
||||
|
||||
If provided value is greater than max value, max value will be applied.
|
||||
`,
|
||||
required: false,
|
||||
type: 'number',
|
||||
})
|
||||
}
|
||||
|
||||
function Select(paginationConfig: PaginateConfig<any>) {
|
||||
if (!paginationConfig.select) {
|
||||
return
|
||||
}
|
||||
|
||||
return ApiQuery({
|
||||
name: 'select',
|
||||
description: `List of fields to select.
|
||||
${p('Example', paginationConfig.select.slice(0, Math.min(5, paginationConfig.select.length)).join(','))}
|
||||
${p(
|
||||
DEFAULT_VALUE_KEY,
|
||||
'By default all fields returns. If you want to select only some fields, provide them in query param'
|
||||
)}
|
||||
`,
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
}
|
||||
|
||||
function Where(paginationConfig: PaginateConfig<any>) {
|
||||
if (!paginationConfig.filterableColumns) return
|
||||
|
||||
const allColumnsDecorators = Object.entries(paginationConfig.filterableColumns)
|
||||
.map(([fieldName, filterOperations]) => {
|
||||
const operations =
|
||||
filterOperations === true || filterOperations === undefined
|
||||
? [
|
||||
...Object.values(FilterComparator),
|
||||
...Object.values(FilterSuffix),
|
||||
...Object.values(FilterOperator),
|
||||
]
|
||||
: filterOperations.map((fo) => fo.toString())
|
||||
|
||||
return ApiQuery({
|
||||
name: `filter.${fieldName}`,
|
||||
description: `Filter by ${fieldName} query param.
|
||||
${p('Format', `filter.${fieldName}={$not}:OPERATION:VALUE`)}
|
||||
${p('Example', `filter.${fieldName}=$not:$like:John Doe&filter.${fieldName}=like:John`)}
|
||||
${li('Available Operations', operations)}`,
|
||||
required: false,
|
||||
type: 'string',
|
||||
isArray: true,
|
||||
})
|
||||
})
|
||||
.filter((v) => v !== undefined)
|
||||
|
||||
return applyDecorators(...allColumnsDecorators)
|
||||
}
|
||||
|
||||
function Page() {
|
||||
return ApiQuery({
|
||||
name: 'page',
|
||||
description: `Page number to retrieve.If you provide invalid value the default page number will applied
|
||||
${p('Example', '1')}
|
||||
${p(DEFAULT_VALUE_KEY, '1')}
|
||||
`,
|
||||
required: false,
|
||||
type: 'number',
|
||||
})
|
||||
}
|
||||
|
||||
function Search(paginateConfig: PaginateConfig<any>) {
|
||||
if (!paginateConfig.searchableColumns) return
|
||||
|
||||
return ApiQuery({
|
||||
name: 'search',
|
||||
description: `Search term to filter result values
|
||||
${p('Example', 'John')}
|
||||
${p(DEFAULT_VALUE_KEY, 'No default value')}
|
||||
`,
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
}
|
||||
|
||||
function SearchBy(paginateConfig: PaginateConfig<any>) {
|
||||
if (!paginateConfig.searchableColumns) return
|
||||
|
||||
return ApiQuery({
|
||||
name: 'searchBy',
|
||||
description: `List of fields to search by term to filter result values
|
||||
${p(
|
||||
'Example',
|
||||
paginateConfig.searchableColumns.slice(0, Math.min(5, paginateConfig.searchableColumns.length)).join(',')
|
||||
)}
|
||||
${p(DEFAULT_VALUE_KEY, 'By default all fields mentioned below will be used to search by term')}
|
||||
${li('Available Fields', paginateConfig.searchableColumns)}
|
||||
`,
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
}
|
||||
|
||||
export const ApiPaginationQuery = (paginationConfig: PaginateConfig<any>) => {
|
||||
return applyDecorators(
|
||||
...[
|
||||
Page(),
|
||||
Limit(paginationConfig),
|
||||
Where(paginationConfig),
|
||||
SortBy(paginationConfig),
|
||||
Search(paginationConfig),
|
||||
SearchBy(paginationConfig),
|
||||
Select(paginationConfig),
|
||||
].filter((v): v is MethodDecorator => v !== undefined)
|
||||
)
|
||||
}
|
8
src/swagger/api-paginated-swagger-docs.decorator.ts
Normal file
8
src/swagger/api-paginated-swagger-docs.decorator.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { applyDecorators, Type } from '@nestjs/common'
|
||||
import { PaginateConfig } from '../paginate'
|
||||
import { ApiPaginationQuery } from './api-paginated-query.decorator'
|
||||
import { ApiOkPaginatedResponse } from './api-ok-paginated-response.decorator'
|
||||
|
||||
export function PaginatedSwaggerDocs<DTO extends Type<unknown>>(dto: DTO, paginatedConfig: PaginateConfig<any>) {
|
||||
return applyDecorators(ApiOkPaginatedResponse(dto, paginatedConfig), ApiPaginationQuery(paginatedConfig))
|
||||
}
|
4
src/swagger/index.ts
Normal file
4
src/swagger/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './api-paginated-query.decorator'
|
||||
export * from './api-ok-paginated-response.decorator'
|
||||
export * from './paginated-swagger.type'
|
||||
export * from './api-paginated-swagger-docs.decorator'
|
145
src/swagger/paginated-swagger.type.ts
Normal file
145
src/swagger/paginated-swagger.type.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { Column, SortBy } from '../helper'
|
||||
import { Paginated } from '../paginate'
|
||||
|
||||
class PaginatedLinksDocumented {
|
||||
@ApiProperty({
|
||||
title: 'Link to first page',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
first?: string
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Link to previous page',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
previous?: string
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Link to current page',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
current!: string
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Link to next page',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
next?: string
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Link to last page',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
last?: string
|
||||
}
|
||||
|
||||
export class PaginatedMetaDocumented<T> {
|
||||
@ApiProperty({
|
||||
title: 'Number of items per page',
|
||||
required: true,
|
||||
type: 'number',
|
||||
})
|
||||
itemsPerPage!: number
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Total number of items',
|
||||
required: true,
|
||||
type: 'number',
|
||||
})
|
||||
totalItems!: number
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Current requested page',
|
||||
required: true,
|
||||
type: 'number',
|
||||
})
|
||||
currentPage!: number
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Total number of pages',
|
||||
required: true,
|
||||
type: 'number',
|
||||
})
|
||||
totalPages!: number
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Sorting by columns',
|
||||
required: false,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
enum: ['ASC', 'DESC'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
sortBy!: SortBy<T>
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Search by fields',
|
||||
required: false,
|
||||
isArray: true,
|
||||
type: 'string',
|
||||
})
|
||||
searchBy!: Column<T>[]
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Search term',
|
||||
required: false,
|
||||
type: 'string',
|
||||
})
|
||||
search!: string
|
||||
|
||||
@ApiProperty({
|
||||
title: 'List of selected fields',
|
||||
required: false,
|
||||
isArray: true,
|
||||
type: 'string',
|
||||
})
|
||||
select!: string[]
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Filters that applied to the query',
|
||||
required: false,
|
||||
isArray: false,
|
||||
type: 'object',
|
||||
})
|
||||
filter?: {
|
||||
[p: string]: string | string[]
|
||||
}
|
||||
}
|
||||
|
||||
export class PaginatedDocumented<T> extends Paginated<T> {
|
||||
@ApiProperty({
|
||||
isArray: true,
|
||||
required: true,
|
||||
title: 'Array of entities',
|
||||
})
|
||||
override data!: T[]
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Pagination Metadata',
|
||||
required: true,
|
||||
})
|
||||
override meta!: PaginatedMetaDocumented<T>
|
||||
|
||||
@ApiProperty({
|
||||
title: 'Links to pages',
|
||||
required: true,
|
||||
})
|
||||
override links!: PaginatedLinksDocumented
|
||||
}
|
310
src/swagger/pagination-docs.spec.ts
Normal file
310
src/swagger/pagination-docs.spec.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import { Get, Post, Type } from '@nestjs/common'
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
||||
import { FilterOperator, FilterSuffix, PaginateConfig } from '../paginate'
|
||||
import { Test } from '@nestjs/testing'
|
||||
import { PaginatedSwaggerDocs } from './api-paginated-swagger-docs.decorator'
|
||||
import { ApiPaginationQuery } from './api-paginated-query.decorator'
|
||||
import { ApiOkPaginatedResponse } from './api-ok-paginated-response.decorator'
|
||||
|
||||
const BASE_PAGINATION_CONFIG = {
|
||||
sortableColumns: ['id'],
|
||||
} satisfies PaginateConfig<TestDto>
|
||||
|
||||
const FULL_CONFIG = {
|
||||
...BASE_PAGINATION_CONFIG,
|
||||
defaultSortBy: [['id', 'DESC']],
|
||||
defaultLimit: 20,
|
||||
maxLimit: 100,
|
||||
filterableColumns: {
|
||||
id: true,
|
||||
name: [FilterOperator.EQ, FilterSuffix.NOT],
|
||||
},
|
||||
searchableColumns: ['name'],
|
||||
select: ['id', 'name'],
|
||||
} satisfies PaginateConfig<TestDto>
|
||||
|
||||
class TestDto {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
async function getSwaggerDefinitionForEndpoint<T>(entityType: Type<T>, config: PaginateConfig<T>) {
|
||||
class TestController {
|
||||
@PaginatedSwaggerDocs(entityType, config)
|
||||
@Get('/test')
|
||||
public test(): void {
|
||||
//
|
||||
}
|
||||
|
||||
@ApiPaginationQuery(config)
|
||||
@ApiOkPaginatedResponse(entityType, config)
|
||||
@Post('/test')
|
||||
public testPost(): void {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const fakeAppModule = await Test.createTestingModule({
|
||||
controllers: [TestController],
|
||||
}).compile()
|
||||
const fakeApp = fakeAppModule.createNestApplication()
|
||||
|
||||
return SwaggerModule.createDocument(fakeApp, new DocumentBuilder().build())
|
||||
}
|
||||
|
||||
describe('PaginatedEndpoint decorator', () => {
|
||||
it('post and get definition should be the same', async () => {
|
||||
const openApiDefinition = await getSwaggerDefinitionForEndpoint(TestDto, BASE_PAGINATION_CONFIG)
|
||||
|
||||
expect(openApiDefinition.paths['/test'].get.parameters).toStrictEqual(
|
||||
openApiDefinition.paths['/test'].post.parameters
|
||||
)
|
||||
})
|
||||
|
||||
it('should annotate endpoint with OpenApi documentation with limited config', async () => {
|
||||
const openApiDefinition = await getSwaggerDefinitionForEndpoint(TestDto, BASE_PAGINATION_CONFIG)
|
||||
|
||||
const params = openApiDefinition.paths['/test'].get.parameters
|
||||
expect(params).toStrictEqual([
|
||||
{
|
||||
name: 'page',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Page number to retrieve.If you provide invalid value the default page number will applied\n <p>\n <b>Example: </b> 1\n </p>\n <p>\n <b>Default Value: </b> 1\n </p>\n ',
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Number of records per page.\n <p>\n <b>Example: </b> 20\n </p>\n <p>\n <b>Default Value: </b> 20\n </p>\n <p>\n <b>Max Value: </b> 100\n </p>\n\n If provided value is greater than max value, max value will be applied.\n ',
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sortBy',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Parameter to sort by.\n <p>To sort by multiple fields, just provide query param multiple types. The order in url defines an order of sorting</p>\n <p>\n <b>Format: </b> fieldName:DIRECTION\n </p>\n <p>\n <b>Example: </b> sortBy=id:DESC&sortBy=createdAt:ASC\n </p>\n <p>\n <b>Default Value: </b> No default sorting specified, the result order is not guaranteed\n </p>\n <h4>Available Fields</h4><ul><li>id</li></ul>\n ',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(openApiDefinition.paths['/test'].get.responses).toEqual({
|
||||
'200': {
|
||||
description: '',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/PaginatedDocumented',
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/TestDto',
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
properties: {
|
||||
select: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should annotate endpoint with OpenApi documentation with full config', async () => {
|
||||
const openApiDefinition = await getSwaggerDefinitionForEndpoint(TestDto, FULL_CONFIG)
|
||||
|
||||
const params = openApiDefinition.paths['/test'].get.parameters
|
||||
expect(params).toStrictEqual([
|
||||
{
|
||||
name: 'page',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Page number to retrieve.If you provide invalid value the default page number will applied\n <p>\n <b>Example: </b> 1\n </p>\n <p>\n <b>Default Value: </b> 1\n </p>\n ',
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Number of records per page.\n <p>\n <b>Example: </b> 20\n </p>\n <p>\n <b>Default Value: </b> 20\n </p>\n <p>\n <b>Max Value: </b> 100\n </p>\n\n If provided value is greater than max value, max value will be applied.\n ',
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'filter.id',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Filter by id query param.\n <p>\n <b>Format: </b> filter.id={$not}:OPERATION:VALUE\n </p>\n <p>\n <b>Example: </b> filter.id=$not:$like:John Doe&filter.id=like:John\n </p>\n <h4>Available Operations</h4><ul><li>$and</li>\n<li>$or</li>\n<li>$not</li>\n<li>$eq</li>\n<li>$gt</li>\n<li>$gte</li>\n<li>$in</li>\n<li>$null</li>\n<li>$lt</li>\n<li>$lte</li>\n<li>$btw</li>\n<li>$ilike</li>\n<li>$sw</li>\n<li>$contains</li></ul>',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'filter.name',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Filter by name query param.\n <p>\n <b>Format: </b> filter.name={$not}:OPERATION:VALUE\n </p>\n <p>\n <b>Example: </b> filter.name=$not:$like:John Doe&filter.name=like:John\n </p>\n <h4>Available Operations</h4><ul><li>$eq</li>\n<li>$not</li></ul>',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sortBy',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Parameter to sort by.\n <p>To sort by multiple fields, just provide query param multiple types. The order in url defines an order of sorting</p>\n <p>\n <b>Format: </b> fieldName:DIRECTION\n </p>\n <p>\n <b>Example: </b> sortBy=id:DESC&sortBy=createdAt:ASC\n </p>\n <p>\n <b>Default Value: </b> id:DESC\n </p>\n <h4>Available Fields</h4><ul><li>id</li></ul>\n ',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'Search term to filter result values\n <p>\n <b>Example: </b> John\n </p>\n <p>\n <b>Default Value: </b> No default value\n </p>\n ',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'searchBy',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'List of fields to search by term to filter result values\n <p>\n <b>Example: </b> name\n </p>\n <p>\n <b>Default Value: </b> By default all fields mentioned below will be used to search by term\n </p>\n <h4>Available Fields</h4><ul><li>name</li></ul>\n ',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'select',
|
||||
required: false,
|
||||
in: 'query',
|
||||
description:
|
||||
'List of fields to select.\n <p>\n <b>Example: </b> id,name\n </p>\n <p>\n <b>Default Value: </b> By default all fields returns. If you want to select only some fields, provide them in query param\n </p>\n ',
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(openApiDefinition.paths['/test'].get.responses).toEqual({
|
||||
'200': {
|
||||
description: '',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
allOf: [
|
||||
{
|
||||
$ref: '#/components/schemas/PaginatedDocumented',
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/TestDto',
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
properties: {
|
||||
select: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: ['id', 'name'],
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
name: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user