Skip to content

Commit

Permalink
feat(core): custom status code in swagger.json
Browse files Browse the repository at this point in the history
  • Loading branch information
IceHe committed Jul 4, 2022
1 parent acd1568 commit 37bede6
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 276 deletions.
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"iconv-lite": "0.6.3",
"inquirer": "^8.2.2",
"jose": "^4.0.0",
"js-yaml": "^4.1.0",
"koa": "^2.13.1",
"koa-body": "^5.0.0",
"koa-compose": "^4.1.0",
Expand Down Expand Up @@ -81,6 +82,7 @@
"@types/etag": "^1.8.1",
"@types/inquirer": "^8.2.1",
"@types/jest": "^27.4.1",
"@types/js-yaml": "^4.0.5",
"@types/koa": "^2.13.3",
"@types/koa-compose": "^3.2.5",
"@types/koa-logger": "^3.1.1",
Expand Down
52 changes: 52 additions & 0 deletions packages/core/src/routes/swagger.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { load } from 'js-yaml';
import Koa from 'koa';
import Router from 'koa-router';
import request from 'supertest';
Expand All @@ -9,6 +10,10 @@ import { AnonymousRouter } from '@/routes/types';

import swaggerRoutes, { paginationParameters } from './swagger';

jest.mock('js-yaml', () => ({
load: jest.fn().mockReturnValue({}),
}));

const createSwaggerRequest = (
allRouters: Array<Router<unknown, any>>,
swaggerRouter: AnonymousRouter = new Router()
Expand Down Expand Up @@ -228,4 +233,51 @@ describe('GET /swagger.json', () => {
})
);
});

it('should append custom status code', async () => {
const mockRouter = new Router();
mockRouter.get('/mock', () => ({}));
mockRouter.patch('/mock', () => ({}));
mockRouter.post('/mock', () => ({}));
mockRouter.delete('/mock', () => ({}));
(load as jest.Mock).mockReturnValueOnce({
'/mock': {
get: 204,
patch: 202,
post: 201,
delete: 203,
},
});

const swaggerRequest = createSwaggerRequest([mockRouter]);
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({
responses: {
'204': expect.any(Object),
},
}),
patch: expect.objectContaining({
responses: {
'202': expect.any(Object),
},
}),
post: expect.objectContaining({
responses: {
'201': expect.any(Object),
},
}),
delete: expect.objectContaining({
responses: {
'203': expect.any(Object),
},
}),
},
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
})
);
});
});
36 changes: 28 additions & 8 deletions packages/core/src/routes/swagger.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { readFileSync } from 'fs';

import { toTitle } from '@silverhand/essentials';
import { load } from 'js-yaml';
import Router, { IMiddleware } from 'koa-router';
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 { fallbackDefaultPageSize, isPaginationMiddleware } from '@/middleware/koa-pagination';
import assertThat from '@/utils/assert-that';
import { zodTypeToSwagger } from '@/utils/zod';

Expand All @@ -20,6 +23,10 @@ type MethodMap = {
[key in OpenAPIV3.HttpMethods]?: OpenAPIV3.OperationObject;
};

type MethodStatusCodes = Record<string, number>;

type RouteStatusCodes = Record<string, MethodStatusCodes>;

export const paginationParameters: OpenAPIV3.ParameterObject[] = [
{
name: 'page',
Expand Down Expand Up @@ -62,7 +69,11 @@ const buildParameters = (
}));
};

const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.OperationObject => {
const buildOperation = (
stack: IMiddleware[],
path: string,
status: number
): OpenAPIV3.OperationObject => {
const guard = stack.find((function_): function_ is WithGuardConfig<IMiddleware> =>
isGuardMiddleware(function_)
);
Expand All @@ -89,7 +100,7 @@ const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.Operation
parameters: [...pathParameters, ...queryParameters],
requestBody,
responses: {
'200': {
[String(status)]: {
description: 'OK',
},
},
Expand All @@ -101,16 +112,25 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
allRouters: R[]
) {
router.get('/swagger.json', async (ctx, next) => {
const routeStatusCodes = load(
readFileSync('static/yaml/route-status-codes.yaml', 'utf-8')
) as RouteStatusCodes;

const routes = allRouters.flatMap<RouteObject>((router) =>
router.stack.flatMap<RouteObject>(({ path, stack, methods }) =>
methods
// There is no need to show the HEAD method.
.filter((method) => method !== 'HEAD')
.map((method) => ({
path: `/api${path}`,
method: method.toLowerCase() as OpenAPIV3.HttpMethods,
operation: buildOperation(stack, path),
}))
.map((method) => {
const methodStatusCode = routeStatusCodes[path] ?? {};
const status = methodStatusCode[method.toLowerCase()] ?? 200;

return {
path: `/api${path}`,
method: method.toLowerCase() as OpenAPIV3.HttpMethods,
operation: buildOperation(stack, path, status),
};
})
)
);

Expand Down
22 changes: 22 additions & 0 deletions packages/core/static/yaml/route-status-codes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/applications/:id:
delete: 204
/connectors/:id/test:
delete: 204
/me/password:
patch: 204
/resources/:id:
delete: 204
/session/sign-in/passwordless/sms/send-passcode:
post: 204
/session/sign-in/passwordless/email/send-passcode:
post: 204
/session/register/passwordless/sms/send-passcode:
post: 204
/session/register/passwordless/email/send-passcode:
post: 204
/status:
get: 204
/users/:userId:
delete: 204
/.well-known/sign-in-exp:
get: 304
Loading

0 comments on commit 37bede6

Please sign in to comment.