mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[OAS] Support tags (#184320)
This commit is contained in:
parent
c89ee65c70
commit
5fd4795b77
9 changed files with 447 additions and 65 deletions
|
@ -521,7 +521,8 @@
|
|||
"description": "Get Kibana's current status."
|
||||
}
|
||||
},
|
||||
"summary": "Get Kibana's current status."
|
||||
"summary": "Get Kibana's current status.",
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -534,5 +535,6 @@
|
|||
{
|
||||
"url": "http://localhost:5622"
|
||||
}
|
||||
]
|
||||
],
|
||||
"tags": []
|
||||
}
|
|
@ -89,6 +89,7 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "",
|
||||
"tags": Array [],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -102,7 +103,7 @@ Object {
|
|||
"url": "https://test.oas",
|
||||
},
|
||||
],
|
||||
"tags": undefined,
|
||||
"tags": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -213,6 +214,9 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "versioned route",
|
||||
"tags": Array [
|
||||
"versioned",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/foo/{id}/{path*}": Object {
|
||||
|
@ -357,6 +361,154 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "route",
|
||||
"tags": Array [
|
||||
"bar",
|
||||
],
|
||||
},
|
||||
"post": Object {
|
||||
"operationId": "/foo/{id}/{path*}#1",
|
||||
"parameters": Array [
|
||||
Object {
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": Object {
|
||||
"default": "2023-10-31",
|
||||
"enum": Array [
|
||||
"2023-10-31",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "id",
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": Object {
|
||||
"maxLength": 36,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "path",
|
||||
"in": "path",
|
||||
"name": "path",
|
||||
"required": true,
|
||||
"schema": Object {
|
||||
"maxLength": 36,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"description": "page",
|
||||
"in": "query",
|
||||
"name": "page",
|
||||
"required": false,
|
||||
"schema": Object {
|
||||
"default": 1,
|
||||
"maximum": 999,
|
||||
"minimum": 1,
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
],
|
||||
"requestBody": Object {
|
||||
"content": Object {
|
||||
"application/json; Elastic-Api-Version=2023-10-31": Object {
|
||||
"schema": Object {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
"any": Object {},
|
||||
"booleanDefault": Object {
|
||||
"default": true,
|
||||
"description": "defaults to to true",
|
||||
"type": "boolean",
|
||||
},
|
||||
"ipType": Object {
|
||||
"format": "ipv4",
|
||||
"type": "string",
|
||||
},
|
||||
"literalType": Object {
|
||||
"enum": Array [
|
||||
"literallythis",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
"map": Object {
|
||||
"additionalProperties": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"maybeNumber": Object {
|
||||
"maximum": 1000,
|
||||
"minimum": 1,
|
||||
"type": "number",
|
||||
},
|
||||
"record": Object {
|
||||
"additionalProperties": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"string": Object {
|
||||
"maxLength": 10,
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"union": Object {
|
||||
"anyOf": Array [
|
||||
Object {
|
||||
"description": "Union string",
|
||||
"maxLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
Object {
|
||||
"description": "Union number",
|
||||
"minimum": 0,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
},
|
||||
"uri": Object {
|
||||
"default": "prototest://something",
|
||||
"format": "uri",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": Array [
|
||||
"string",
|
||||
"ipType",
|
||||
"literalType",
|
||||
"map",
|
||||
"record",
|
||||
"union",
|
||||
"any",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": Object {
|
||||
"200": Object {
|
||||
"content": Object {
|
||||
"application/json; Elastic-Api-Version=2023-10-31": Object {
|
||||
"schema": Object {
|
||||
"maxLength": 10,
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "route",
|
||||
},
|
||||
},
|
||||
"summary": "route",
|
||||
"tags": Array [
|
||||
"bar",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -370,7 +522,14 @@ Object {
|
|||
"url": "https://test.oas",
|
||||
},
|
||||
],
|
||||
"tags": undefined,
|
||||
"tags": Array [
|
||||
Object {
|
||||
"name": "bar",
|
||||
},
|
||||
Object {
|
||||
"name": "versioned",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -456,6 +615,7 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "",
|
||||
"tags": Array [],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -469,7 +629,7 @@ Object {
|
|||
"url": "https://test.oas",
|
||||
},
|
||||
],
|
||||
"tags": undefined,
|
||||
"tags": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -532,6 +692,7 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "",
|
||||
"tags": Array [],
|
||||
},
|
||||
},
|
||||
"/test": Object {
|
||||
|
@ -568,6 +729,7 @@ Object {
|
|||
},
|
||||
},
|
||||
"summary": "",
|
||||
"tags": Array [],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -581,6 +743,6 @@ Object {
|
|||
"url": "https://test.oas",
|
||||
},
|
||||
],
|
||||
"tags": undefined,
|
||||
"tags": Array [],
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -18,7 +18,10 @@ interface RecursiveType {
|
|||
describe('generateOpenApiDocument', () => {
|
||||
describe('@kbn/config-schema', () => {
|
||||
it('generates the expected OpenAPI document', () => {
|
||||
const [routers, versionedRouters] = createTestRouters();
|
||||
const [routers, versionedRouters] = createTestRouters({
|
||||
routers: { testRouter: { routes: [{ method: 'get' }, { method: 'post' }] } },
|
||||
versionedRouters: { testVersionedRouter: { routes: [{}] } },
|
||||
});
|
||||
expect(
|
||||
generateOpenApiDocument(
|
||||
{
|
||||
|
@ -185,4 +188,58 @@ describe('generateOpenApiDocument', () => {
|
|||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('tags', () => {
|
||||
it('handles tags as expected', () => {
|
||||
const [routers, versionedRouters] = createTestRouters({
|
||||
routers: {
|
||||
testRouter1: {
|
||||
routes: [
|
||||
{ path: '/1-1/{id}/{path*}', options: { tags: ['oas-tag:1', 'oas-tag:2', 'foo'] } },
|
||||
{ path: '/1-2/{id}/{path*}', options: { tags: ['oas-tag:1', 'foo'] } },
|
||||
],
|
||||
},
|
||||
testRouter2: { routes: [{ path: '/2-1/{id}/{path*}', options: { tags: undefined } }] },
|
||||
},
|
||||
versionedRouters: {
|
||||
testVersionedRouter1: {
|
||||
routes: [
|
||||
{ path: '/v1-1', options: { access: 'public', options: { tags: ['oas-tag:v1'] } } },
|
||||
{
|
||||
path: '/v1-2',
|
||||
options: {
|
||||
access: 'public',
|
||||
options: { tags: ['foo', 'bar', 'oas-tag:v2', 'oas-tag:v3'] },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
testVersionedRouter2: {
|
||||
routes: [
|
||||
{ path: '/v2-1', options: { access: 'public', options: { tags: undefined } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = generateOpenApiDocument(
|
||||
{
|
||||
routers,
|
||||
versionedRouters,
|
||||
},
|
||||
{
|
||||
title: 'test',
|
||||
baseUrl: 'https://test.oas',
|
||||
version: '99.99.99',
|
||||
}
|
||||
);
|
||||
// router paths
|
||||
expect(result.paths['/1-1/{id}/{path*}']!.get!.tags).toEqual(['1', '2']);
|
||||
expect(result.paths['/1-2/{id}/{path*}']!.get!.tags).toEqual(['1']);
|
||||
expect(result.paths['/2-1/{id}/{path*}']!.get!.tags).toEqual([]);
|
||||
// versioned router paths
|
||||
expect(result.paths['/v1-1']!.get!.tags).toEqual(['v1']);
|
||||
expect(result.paths['/v1-2']!.get!.tags).toEqual(['v2', 'v3']);
|
||||
expect(result.paths['/v2-1']!.get!.tags).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,26 +35,26 @@ export const testSchema = schema.object({
|
|||
any: schema.any({ meta: { description: 'any type' } }),
|
||||
});
|
||||
|
||||
type RouterMeta = ReturnType<Router['getRoutes']>[number];
|
||||
type VersionedRouterMeta = ReturnType<CoreVersionedRouter['getRoutes']>[number];
|
||||
type RoutesMeta = ReturnType<Router['getRoutes']>[number];
|
||||
type VersionedRoutesMeta = ReturnType<CoreVersionedRouter['getRoutes']>[number];
|
||||
|
||||
export const createRouter = (args: { routes: RouterMeta[] }) => {
|
||||
export const createRouter = (args: { routes: RoutesMeta[] }) => {
|
||||
return {
|
||||
getRoutes: () => args.routes,
|
||||
} as unknown as Router;
|
||||
};
|
||||
export const createVersionedRouter = (args: { routes: VersionedRouterMeta[] }) => {
|
||||
export const createVersionedRouter = (args: { routes: VersionedRoutesMeta[] }) => {
|
||||
return {
|
||||
getRoutes: () => args.routes,
|
||||
} as unknown as CoreVersionedRouter;
|
||||
};
|
||||
|
||||
const getRouterDefaults = () => ({
|
||||
export const getRouterDefaults = () => ({
|
||||
isVersioned: false,
|
||||
path: '/foo/{id}/{path*}',
|
||||
method: 'get',
|
||||
options: {
|
||||
tags: ['foo'],
|
||||
tags: ['foo', 'oas-tag:bar'],
|
||||
description: 'route',
|
||||
},
|
||||
validationSchemas: {
|
||||
|
@ -78,12 +78,15 @@ const getRouterDefaults = () => ({
|
|||
handler: jest.fn(),
|
||||
});
|
||||
|
||||
const getVersionedRouterDefaults = () => ({
|
||||
export const getVersionedRouterDefaults = () => ({
|
||||
method: 'get',
|
||||
path: '/bar',
|
||||
options: {
|
||||
description: 'versioned route',
|
||||
access: 'public',
|
||||
options: {
|
||||
tags: ['ignore-me', 'oas-tag:versioned'],
|
||||
},
|
||||
},
|
||||
handlers: [
|
||||
{
|
||||
|
@ -130,25 +133,29 @@ const getVersionedRouterDefaults = () => ({
|
|||
],
|
||||
});
|
||||
|
||||
interface CreatTestRouterArgs {
|
||||
routers?: { [routerId: string]: { routes: Array<Partial<RoutesMeta>> } };
|
||||
versionedRouters?: {
|
||||
[routerId: string]: { routes: Array<Partial<VersionedRoutesMeta>> };
|
||||
};
|
||||
}
|
||||
|
||||
export const createTestRouters = (
|
||||
{
|
||||
routers = [],
|
||||
versionedRouters = [],
|
||||
}: {
|
||||
routers?: Array<Array<Partial<RouterMeta>>>;
|
||||
versionedRouters?: Array<Array<Partial<VersionedRouterMeta>>>;
|
||||
} = { routers: [[{}]], versionedRouters: [[{}]] }
|
||||
{ routers = {}, versionedRouters = {} }: CreatTestRouterArgs = {
|
||||
routers: { testRouter: { routes: [{}] } },
|
||||
versionedRouters: { testVersionedRouter: { routes: [{}] } },
|
||||
}
|
||||
): [routers: Router[], versionedRouters: CoreVersionedRouter[]] => {
|
||||
return [
|
||||
[
|
||||
...routers.map((rs) =>
|
||||
createRouter({ routes: rs.map((r) => Object.assign(getRouterDefaults(), r)) })
|
||||
...Object.values(routers).map((rs) =>
|
||||
createRouter({ routes: rs.routes.map((r) => Object.assign(getRouterDefaults(), r)) })
|
||||
),
|
||||
],
|
||||
[
|
||||
...versionedRouters.map((rs) =>
|
||||
...Object.values(versionedRouters).map((rs) =>
|
||||
createVersionedRouter({
|
||||
routes: rs.map((r) => Object.assign(getVersionedRouterDefaults(), r)),
|
||||
routes: rs.routes.map((r) => Object.assign(getVersionedRouterDefaults(), r)),
|
||||
})
|
||||
),
|
||||
],
|
||||
|
|
|
@ -12,6 +12,7 @@ import { OasConverter } from './oas_converter';
|
|||
import { createOperationIdCounter } from './operation_id_counter';
|
||||
import { processRouter } from './process_router';
|
||||
import { processVersionedRouter } from './process_versioned_router';
|
||||
import { buildGlobalTags } from './util';
|
||||
|
||||
export const openApiVersion = '3.0.0';
|
||||
|
||||
|
@ -48,6 +49,7 @@ export const generateOpenApiDocument = (
|
|||
const result = processVersionedRouter(router, converter, getOpId, filters);
|
||||
Object.assign(paths, result.paths);
|
||||
}
|
||||
const tags = buildGlobalTags(paths, opts.tags);
|
||||
return {
|
||||
openapi: openApiVersion,
|
||||
info: {
|
||||
|
@ -76,7 +78,7 @@ export const generateOpenApiDocument = (
|
|||
},
|
||||
},
|
||||
security: [{ basicAuth: [] }],
|
||||
tags: opts.tags?.map((tag) => ({ name: tag })),
|
||||
tags,
|
||||
externalDocs: opts.docsUrl ? { url: opts.docsUrl } : undefined,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,8 +12,9 @@ import { ALLOWED_PUBLIC_VERSION as SERVERLESS_VERSION_2023_10_31 } from '@kbn/co
|
|||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import type { OasConverter } from './oas_converter';
|
||||
import {
|
||||
assignToPathsObject,
|
||||
assignToPaths,
|
||||
extractContentType,
|
||||
extractTags,
|
||||
extractValidationSchemaFromRoute,
|
||||
getPathParameters,
|
||||
getVersionedContentTypeString,
|
||||
|
@ -58,24 +59,27 @@ export const processRouter = (
|
|||
];
|
||||
}
|
||||
|
||||
const path: OpenAPIV3.PathItemObject = {
|
||||
[route.method]: {
|
||||
summary: route.options.description ?? '',
|
||||
requestBody: !!validationSchemas?.body
|
||||
? {
|
||||
content: {
|
||||
[getVersionedContentTypeString(SERVERLESS_VERSION_2023_10_31, contentType)]: {
|
||||
schema: converter.convert(validationSchemas.body),
|
||||
},
|
||||
const operation: OpenAPIV3.OperationObject = {
|
||||
summary: route.options.description ?? '',
|
||||
tags: route.options.tags ? extractTags(route.options.tags) : [],
|
||||
requestBody: !!validationSchemas?.body
|
||||
? {
|
||||
content: {
|
||||
[getVersionedContentTypeString(SERVERLESS_VERSION_2023_10_31, contentType)]: {
|
||||
schema: converter.convert(validationSchemas.body),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
responses: extractResponses(route, converter),
|
||||
parameters,
|
||||
operationId: getOpId(route.path),
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
responses: extractResponses(route, converter),
|
||||
parameters,
|
||||
operationId: getOpId(route.path),
|
||||
};
|
||||
assignToPathsObject(paths, route.path, path);
|
||||
|
||||
const path: OpenAPIV3.PathItemObject = {
|
||||
[route.method]: operation,
|
||||
};
|
||||
assignToPaths(paths, route.path, path);
|
||||
} catch (e) {
|
||||
// Enrich the error message with a bit more context
|
||||
e.message = `Error generating OpenAPI for route '${route.path}': ${e.message}`;
|
||||
|
|
|
@ -20,9 +20,10 @@ import {
|
|||
prepareRoutes,
|
||||
getPathParameters,
|
||||
extractContentType,
|
||||
assignToPathsObject,
|
||||
assignToPaths,
|
||||
getVersionedHeaderParam,
|
||||
getVersionedContentTypeString,
|
||||
extractTags,
|
||||
} from './util';
|
||||
|
||||
export const processVersionedRouter = (
|
||||
|
@ -82,25 +83,27 @@ export const processVersionedRouter = (
|
|||
const hasBody = Boolean(extractValidationSchemaFromVersionedHandler(handler)?.request?.body);
|
||||
const contentType = extractContentType(route.options.options?.body);
|
||||
const hasVersionFilter = Boolean(filters?.version);
|
||||
const operation: OpenAPIV3.OperationObject = {
|
||||
summary: route.options.description ?? '',
|
||||
tags: route.options.options?.tags ? extractTags(route.options.options.tags) : [],
|
||||
requestBody: hasBody
|
||||
? {
|
||||
content: hasVersionFilter
|
||||
? extractVersionedRequestBody(handler, converter, contentType)
|
||||
: extractVersionedRequestBodies(route, converter, contentType),
|
||||
}
|
||||
: undefined,
|
||||
responses: hasVersionFilter
|
||||
? extractVersionedResponse(handler, converter, contentType)
|
||||
: extractVersionedResponses(route, converter, contentType),
|
||||
parameters,
|
||||
operationId: getOpId(route.path),
|
||||
};
|
||||
const path: OpenAPIV3.PathItemObject = {
|
||||
[route.method]: {
|
||||
summary: route.options.description ?? '',
|
||||
requestBody: hasBody
|
||||
? {
|
||||
content: hasVersionFilter
|
||||
? extractVersionedRequestBody(handler, converter, contentType)
|
||||
: extractVersionedRequestBodies(route, converter, contentType),
|
||||
}
|
||||
: undefined,
|
||||
responses: hasVersionFilter
|
||||
? extractVersionedResponse(handler, converter, contentType)
|
||||
: extractVersionedResponses(route, converter, contentType),
|
||||
parameters,
|
||||
operationId: getOpId(route.path),
|
||||
},
|
||||
[route.method]: operation,
|
||||
};
|
||||
|
||||
assignToPathsObject(paths, route.path, path);
|
||||
assignToPaths(paths, route.path, path);
|
||||
} catch (e) {
|
||||
// Enrich the error message with a bit more context
|
||||
e.message = `Error generating OpenAPI for route '${route.path}' using newest version '${version}': ${e.message}`;
|
||||
|
|
|
@ -6,12 +6,115 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { prepareRoutes } from './util';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { buildGlobalTags, prepareRoutes } from './util';
|
||||
import { assignToPaths, extractTags } from './util';
|
||||
|
||||
const internal = 'internal' as const;
|
||||
const pub = 'public' as const;
|
||||
describe('extractTags', () => {
|
||||
test.each([
|
||||
[[], []],
|
||||
[['a', 'b', 'c'], []],
|
||||
[
|
||||
['oas-tag:foo', 'b', 'oas-tag:bar'],
|
||||
['foo', 'bar'],
|
||||
],
|
||||
])('given %s returns %s', (input, output) => {
|
||||
expect(extractTags(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildGlobalTags', () => {
|
||||
test.each([
|
||||
{
|
||||
name: 'base case',
|
||||
paths: {},
|
||||
additionalTags: [],
|
||||
output: [],
|
||||
},
|
||||
{
|
||||
name: 'all methods',
|
||||
paths: {
|
||||
'/foo': {
|
||||
get: { tags: ['get'] },
|
||||
put: { tags: ['put'] },
|
||||
post: { tags: ['post'] },
|
||||
patch: { tags: ['patch'] },
|
||||
delete: { tags: ['delete'] },
|
||||
options: { tags: ['options'] },
|
||||
head: { tags: ['head'] },
|
||||
trace: { tags: ['trace'] },
|
||||
},
|
||||
},
|
||||
additionalTags: [],
|
||||
output: [
|
||||
{ name: 'delete' },
|
||||
{ name: 'get' },
|
||||
{ name: 'head' },
|
||||
{ name: 'options' },
|
||||
{ name: 'patch' },
|
||||
{ name: 'post' },
|
||||
{ name: 'put' },
|
||||
{ name: 'trace' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'unknown method',
|
||||
paths: {
|
||||
'/foo': {
|
||||
unknown: { tags: ['not-included'] },
|
||||
},
|
||||
'/bar': {
|
||||
post: { tags: ['bar'] },
|
||||
},
|
||||
},
|
||||
additionalTags: [],
|
||||
output: [{ name: 'bar' }],
|
||||
},
|
||||
{
|
||||
name: 'dedup',
|
||||
paths: {
|
||||
'/foo': {
|
||||
get: { tags: ['foo'] },
|
||||
patch: { tags: ['foo'] },
|
||||
},
|
||||
'/bar': {
|
||||
get: { tags: ['foo'] },
|
||||
post: { tags: ['foo'] },
|
||||
},
|
||||
},
|
||||
additionalTags: [],
|
||||
output: [{ name: 'foo' }],
|
||||
},
|
||||
{
|
||||
name: 'dedups with additional tags',
|
||||
paths: {
|
||||
'/foo': { get: { tags: ['foo'] } },
|
||||
'/baz': { patch: { tags: ['foo'] } },
|
||||
'/bar': { patch: { tags: ['bar'] } },
|
||||
},
|
||||
additionalTags: ['foo', 'bar', 'baz'],
|
||||
output: [{ name: 'bar' }, { name: 'baz' }, { name: 'foo' }],
|
||||
},
|
||||
])('$name', ({ paths, additionalTags, output }) => {
|
||||
expect(buildGlobalTags(paths as OpenAPIV3.PathsObject, additionalTags)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assignToPaths', () => {
|
||||
it('should transform path names', () => {
|
||||
const paths = {};
|
||||
assignToPaths(paths, '/foo', {});
|
||||
assignToPaths(paths, '/bar/{id?}', {});
|
||||
expect(paths).toEqual({
|
||||
'/foo': {},
|
||||
'/bar/{id}': {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('prepareRoutes', () => {
|
||||
const internal = 'internal' as const;
|
||||
const pub = 'public' as const;
|
||||
test.each([
|
||||
{
|
||||
input: [{ path: '/api/foo', options: { access: internal } }],
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import {
|
||||
getRequestValidation,
|
||||
type RouteConfigOptionsBody,
|
||||
|
@ -16,6 +17,47 @@ import {
|
|||
import { KnownParameters } from './type';
|
||||
import type { GenerateOpenApiDocumentOptionsFilters } from './generate_oas';
|
||||
|
||||
const tagPrefix = 'oas-tag:';
|
||||
const extractTag = (tag: string) => {
|
||||
if (tag.startsWith(tagPrefix)) {
|
||||
return tag.slice(tagPrefix.length);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Given an array of tags ([oas-tag:beep, oas-tag:boop]) will return a new array
|
||||
* with the tag prefix removed.
|
||||
*/
|
||||
export const extractTags = (tags?: readonly string[]) => {
|
||||
if (!tags) return [];
|
||||
return tags.flatMap((tag) => {
|
||||
const value = extractTag(tag);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the top-level tags entry based on the paths we extracted. We could
|
||||
* handle this while we are iterating over the routes, but this approach allows
|
||||
* us to keep this as a global document concern at the expense of some extra
|
||||
* processing.
|
||||
*/
|
||||
export const buildGlobalTags = (paths: OpenAPIV3.PathsObject, additionalTags: string[] = []) => {
|
||||
const tags = new Set<string>(additionalTags);
|
||||
for (const path of Object.values(paths)) {
|
||||
for (const method of Object.values(OpenAPIV3.HttpMethods)) {
|
||||
if (path?.[method]?.tags) {
|
||||
path[method]!.tags!.forEach((tag) => tags.add(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(tags)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map<OpenAPIV3.TagObject>((name) => ({ name }));
|
||||
};
|
||||
|
||||
export const getPathParameters = (path: string): KnownParameters => {
|
||||
return Array.from(path.matchAll(/\{(.+?)\}/g)).reduce<KnownParameters>((acc, [_, key]) => {
|
||||
const optional = key.endsWith('?');
|
||||
|
@ -81,7 +123,7 @@ export const prepareRoutes = <
|
|||
});
|
||||
};
|
||||
|
||||
export const assignToPathsObject = (
|
||||
export const assignToPaths = (
|
||||
paths: OpenAPIV3.PathsObject,
|
||||
path: string,
|
||||
pathObject: OpenAPIV3.PathItemObject
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue