0
Fork 0
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:
IceHe.xyz 2022-06-15 15:13:04 +08:00 committed by GitHub
parent 9262a6f3be
commit a26299941f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 45 deletions

View file

@ -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;
}

View file

@ -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 () => {

View file

@ -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')],