mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(core): append page and page_size to the query parameters in swagger.json (#1120)
* feat(core): append page and page_size to the query parameters in swagger.json * refactor(core): simplify hasPagination * chore(core): add a comment to note why name the pagination middleware function
This commit is contained in:
parent
9262a6f3be
commit
a26299941f
3 changed files with 108 additions and 45 deletions
|
@ -1,4 +1,5 @@
|
|||
import { MiddlewareType } from 'koa';
|
||||
import { IMiddleware } from 'koa-router';
|
||||
import { number } from 'zod';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
@ -19,11 +20,22 @@ export interface PaginationConfig {
|
|||
maxPageSize?: number;
|
||||
}
|
||||
|
||||
export const isPaginationMiddleware = <Type extends IMiddleware>(
|
||||
function_: Type
|
||||
): function_ is WithPaginationContext<Type> => function_.name === 'paginationMiddleware';
|
||||
|
||||
export const fallbackDefaultPageSize = 20;
|
||||
|
||||
export default function koaPagination<StateT, ContextT, ResponseBodyT>({
|
||||
defaultPageSize = 20,
|
||||
defaultPageSize = fallbackDefaultPageSize,
|
||||
maxPageSize = 100,
|
||||
}: PaginationConfig = {}): MiddlewareType<StateT, WithPaginationContext<ContextT>, ResponseBodyT> {
|
||||
return async (ctx, next) => {
|
||||
// Name this anonymous function for the utility function `isPaginationMiddleware` to identify it
|
||||
const paginationMiddleware: MiddlewareType<
|
||||
StateT,
|
||||
WithPaginationContext<ContextT>,
|
||||
ResponseBodyT
|
||||
> = async (ctx, next) => {
|
||||
try {
|
||||
const {
|
||||
request: {
|
||||
|
@ -67,4 +79,6 @@ export default function koaPagination<StateT, ContextT, ResponseBodyT>({
|
|||
ctx.append('Link', buildLink(ctx.request, page + 1, 'next'));
|
||||
}
|
||||
};
|
||||
|
||||
return paginationMiddleware;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ import request from 'supertest';
|
|||
import { number, object, string } from 'zod';
|
||||
|
||||
import koaGuard from '@/middleware/koa-guard';
|
||||
import koaPagination from '@/middleware/koa-pagination';
|
||||
import { AnonymousRouter } from '@/routes/types';
|
||||
|
||||
import swaggerRoutes from './swagger';
|
||||
import swaggerRoutes, { paginationParameters } from './swagger';
|
||||
|
||||
const createSwaggerRequest = (
|
||||
allRouters: Array<Router<unknown, any>>,
|
||||
|
@ -123,48 +124,68 @@ describe('GET /swagger.json', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should parse the query parameters', async () => {
|
||||
const queryParametersRouter = new Router();
|
||||
queryParametersRouter.get(
|
||||
'/mock',
|
||||
koaGuard({
|
||||
query: object({
|
||||
id: number(),
|
||||
name: string().optional(),
|
||||
}),
|
||||
}),
|
||||
() => ({})
|
||||
);
|
||||
const swaggerRequest = createSwaggerRequest([queryParametersRouter]);
|
||||
const response = await swaggerRequest.get('/swagger.json');
|
||||
expect(response.body.paths).toMatchObject(
|
||||
expect.objectContaining({
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
'/api/mock': {
|
||||
get: expect.objectContaining({
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
describe('parse query parameters', () => {
|
||||
it('should parse the normal query parameters', async () => {
|
||||
const queryParametersRouter = new Router();
|
||||
queryParametersRouter.get(
|
||||
'/mock',
|
||||
koaGuard({
|
||||
query: object({
|
||||
id: number(),
|
||||
name: string().optional(),
|
||||
}),
|
||||
},
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
||||
})
|
||||
);
|
||||
}),
|
||||
() => ({})
|
||||
);
|
||||
const swaggerRequest = createSwaggerRequest([queryParametersRouter]);
|
||||
const response = await swaggerRequest.get('/swagger.json');
|
||||
expect(response.body.paths).toMatchObject(
|
||||
expect.objectContaining({
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
'/api/mock': {
|
||||
get: expect.objectContaining({
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'query',
|
||||
required: true,
|
||||
schema: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should append page and page_size to the query parameters when the route uses pagination', async () => {
|
||||
const queryParametersRouter = new Router();
|
||||
queryParametersRouter.get('/mock', koaPagination(), () => ({}));
|
||||
const swaggerRequest = createSwaggerRequest([queryParametersRouter]);
|
||||
const response = await swaggerRequest.get('/swagger.json');
|
||||
expect(response.body.paths).toMatchObject(
|
||||
expect.objectContaining({
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
'/api/mock': {
|
||||
get: expect.objectContaining({
|
||||
parameters: paginationParameters,
|
||||
}),
|
||||
},
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse the request body', async () => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
import { ZodObject, ZodOptional } from 'zod';
|
||||
|
||||
import { isGuardMiddleware, WithGuardConfig } from '@/middleware/koa-guard';
|
||||
import { isPaginationMiddleware, fallbackDefaultPageSize } from '@/middleware/koa-pagination';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
import { zodTypeToSwagger } from '@/utils/zod';
|
||||
|
||||
|
@ -19,6 +20,29 @@ type MethodMap = {
|
|||
[key in OpenAPIV3.HttpMethods]?: OpenAPIV3.OperationObject;
|
||||
};
|
||||
|
||||
export const paginationParameters: OpenAPIV3.ParameterObject[] = [
|
||||
{
|
||||
name: 'page',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'page_size',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
default: fallbackDefaultPageSize,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Parameter serialization: https://swagger.io/docs/specification/serialization
|
||||
const buildParameters = (
|
||||
zodParameters: unknown,
|
||||
|
@ -42,6 +66,7 @@ const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.Operation
|
|||
const guard = stack.find((function_): function_ is WithGuardConfig<IMiddleware> =>
|
||||
isGuardMiddleware(function_)
|
||||
);
|
||||
const hasPagination = stack.some((function_) => isPaginationMiddleware(function_));
|
||||
|
||||
const body = guard?.config.body;
|
||||
const requestBody = body && {
|
||||
|
@ -54,7 +79,10 @@ const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.Operation
|
|||
};
|
||||
|
||||
const pathParameters = buildParameters(guard?.config.params, 'path');
|
||||
const queryParameters = buildParameters(guard?.config.query, 'query');
|
||||
const queryParameters = [
|
||||
...buildParameters(guard?.config.query, 'query'),
|
||||
...(hasPagination ? paginationParameters : []),
|
||||
];
|
||||
|
||||
return {
|
||||
tags: [toTitle(path.split('/')[1] ?? 'General')],
|
||||
|
|
Loading…
Add table
Reference in a new issue