use operationId where possible in o2k generation (#3072)

* use `operationId` if it exists for route name
* adds precedence for x-kong-name usages
This commit is contained in:
Dimitri Mitropoulos 2021-02-16 16:34:34 -05:00 committed by GitHub
parent 4064e85781
commit eb4ae99f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 160 additions and 79 deletions

View File

@ -9,14 +9,14 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_api-with-examples.yaml"],
"name": "Simple_API_overview-path-get",
"name": "x_kong_name_override_at_method",
"paths": ["/$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_api-with-examples.yaml"],
"name": "Simple_API_overview-path_1-get",
"name": "getVersionDetailsv2",
"paths": ["/v2$"]
}
],

View File

@ -6,7 +6,9 @@ servers:
- url: http://backend.com/path
paths:
/:
x-kong-name: x-kong-name-override-at-path-item
get:
x-kong-name: x-kong-name-override-at-method
operationId: listVersionsv2
summary: List API versions
responses:
@ -78,6 +80,7 @@ paths:
]
}
/v2:
x-kong-name: x-kong-name-override-at-path-item
get:
operationId: getVersionDetailsv2
summary: Show API version details

View File

@ -9,7 +9,7 @@
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_callback-example.yaml"],
"name": "Callback_Example-path-post",
"name": "Callback_Example-streams-post",
"paths": ["/streams$"]
}
],

View File

@ -9,14 +9,14 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_3-get",
"name": "httpbin-get-get",
"paths": ["/get$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_10-get",
"name": "httpbin-basic_auth_user_password-get",
"plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth"}],
"paths": ["\/basic-auth\/(?<user>\\S+)\/(?<password>\\S+)$"]
},
@ -24,70 +24,70 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_20-get",
"name": "httpbin-image_format-get",
"paths": ["\/image\/(?<format>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_9-get",
"name": "httpbin-delay_n-get",
"paths": ["\/delay\/(?<n>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_19-get",
"name": "httpbin-html-get",
"paths": ["/html$"]
},
{
"methods": ["DELETE"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-Returns_the_specified_HTTP_status_code_or_a_random_status_code_if_more_than_one_are_given-delete",
"name": "httpbin-status_statusCode-delete",
"paths": ["\/status\/(?<statusCode>\\S+)$"]
},
{
"methods": ["PUT"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-Returns_the_specified_HTTP_status_code_or_a_random_status_code_if_more_than_one_are_given-put",
"name": "httpbin-status_statusCode-put",
"paths": ["\/status\/(?<statusCode>\\S+)$"]
},
{
"methods": ["PATCH"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-Returns_the_specified_HTTP_status_code_or_a_random_status_code_if_more_than_one_are_given-patch",
"name": "httpbin-status_statusCode-patch",
"paths": ["\/status\/(?<statusCode>\\S+)$"]
},
{
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-Returns_the_specified_HTTP_status_code_or_a_random_status_code_if_more_than_one_are_given-post",
"name": "httpbin-status_statusCode-post",
"paths": ["\/status\/(?<statusCode>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-Returns_the_specified_HTTP_status_code_or_a_random_status_code_if_more_than_one_are_given-get",
"name": "httpbin-status_statusCode-get",
"paths": ["\/status\/(?<statusCode>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_7-get",
"name": "httpbin-user_agent-get",
"paths": ["/user-agent$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_11-get",
"name": "httpbin-hidden_basic_auth_user_password-get",
"plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth"}],
"paths": ["\/hidden-basic-auth\/(?<user>\\S+)\/(?<password>\\S+)$"]
},
@ -95,14 +95,14 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_8-get",
"name": "httpbin-headers-get",
"paths": ["/headers$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_23-get",
"name": "httpbin-cookies-get",
"plugins": [{"name": "correlation-id"}],
"paths": ["/cookies$"]
},
@ -110,63 +110,63 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_6-get",
"name": "httpbin-ip-get",
"paths": ["/ip$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_18-get",
"name": "httpbin-xml-get",
"paths": ["/xml$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_25-get",
"name": "httpbin-cookies_delete-get",
"paths": ["/cookies\/delete$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_21-get",
"name": "httpbin-image-get",
"paths": ["/image$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_2-get",
"name": "httpbin-parse_machine_timestamp-get",
"paths": ["\/parse\/(?<machine_timestamp>\\S+)$"]
},
{
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_5-post",
"name": "httpbin-post-post",
"paths": ["/post$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_22-get",
"name": "httpbin-cache-get",
"paths": ["/cache$"]
},
{
"methods": ["DELETE"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_4-delete",
"name": "httpbin-delete-delete",
"paths": ["/delete$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_1-get",
"name": "httpbin-when_human_timestamp-get",
"plugins": [
{
"name": "request-validator",
@ -192,7 +192,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_12-get",
"name": "httpbin-bearer-get",
"plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth"}],
"paths": ["/bearer$"]
},
@ -200,14 +200,14 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path-get",
"name": "httpbin-get",
"paths": ["/$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_httpbin.yaml"],
"name": "httpbin-path_24-get",
"name": "httpbin-cookies_set-get",
"paths": ["/cookies/set$"]
}
],

View File

@ -9,35 +9,35 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path-get",
"name": "getUserByName",
"paths": ["\/2.0\/users\/(?<username>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path_1-get",
"name": "getRepositoriesByOwner",
"paths": ["\/2.0\/repositories\/(?<username>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path_2-get",
"name": "getRepository",
"paths": ["\/2.0\/repositories\/(?<username>\\S+)\/(?<slug>\\S+)$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path_3-get",
"name": "getPullRequestsByRepository",
"paths": ["\/2.0\/repositories\/(?<username>\\S+)\/(?<slug>\\S+)\/pullrequests$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path_4-get",
"name": "getPullRequestsById",
"paths": [
"\/2.0\/repositories\/(?<username>\\S+)\/(?<slug>\\S+)\/pullrequests\/(?<pid>\\S+)$"
]
@ -46,7 +46,7 @@
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_link-example.yaml"],
"name": "Link_Example-path_5-post",
"name": "mergePullRequest",
"paths": [
"\/2.0\/repositories\/(?<username>\\S+)\/(?<slug>\\S+)\/pullrequests\/(?<pid>\\S+)\/merge$"
]

View File

@ -9,28 +9,28 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"],
"name": "Swagger_Petstore-path-get",
"name": "findPets",
"paths": ["/pets$"]
},
{
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"],
"name": "Swagger_Petstore-path_1-post",
"name": "addPet",
"paths": ["/pets$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"],
"name": "Swagger_Petstore-path_2-get",
"name": "find pet by id",
"paths": ["\/pets\/(?<id>\\S+)$"]
},
{
"methods": ["DELETE"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"],
"name": "Swagger_Petstore-path_3-delete",
"name": "deletePet",
"paths": ["\/pets\/(?<id>\\S+)$"]
}
],

View File

@ -9,21 +9,21 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore.yaml"],
"name": "Swagger_Petstore-path-get",
"name": "listPets",
"paths": ["/pets$"]
},
{
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore.yaml"],
"name": "Swagger_Petstore-path_1-post",
"name": "createPets",
"paths": ["/pets$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_petstore.yaml"],
"name": "Swagger_Petstore-path_2-get",
"name": "showPetById",
"paths": ["\/pets\/(?<petId>\\S+)$"]
}
],

View File

@ -9,7 +9,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path_2-get",
"name": "Security_Example-only_oath2-get",
"plugins": [
{"config": {"auth_methods": ["client_credentials"]}, "name": "openid-connect"}
],
@ -19,7 +19,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path_1-get",
"name": "Security_Example-only_key-get",
"plugins": [{"config": {"key_names": ["api_key_by_me"]}, "name": "key-auth"}],
"paths": ["/only/key$"]
},
@ -27,7 +27,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path_4-get",
"name": "Security_Example-and_based_auth-get",
"plugins": [
{"config": {"auth_methods": ["client_credentials"]}, "name": "openid-connect"}
],
@ -37,7 +37,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path_5-get",
"name": "Security_Example-or_based_auth-get",
"plugins": [{"config": {"key_names": ["api_key_by_me"]}, "name": "key-auth"}],
"paths": ["/or/based/auth$"]
},
@ -45,7 +45,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path_3-get",
"name": "Security_Example-only_oidc-get",
"plugins": [
{
"config": {
@ -61,7 +61,7 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_security.yaml"],
"name": "Security_Example-path-get",
"name": "Security_Example-only_basic-get",
"plugins": [{"name": "basic-auth"}],
"paths": ["/only/basic$"]
}

View File

@ -9,21 +9,21 @@
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_uspto.yaml"],
"name": "USPTO_Data_Set_API-path-get",
"name": "list-data-sets",
"paths": ["/$"]
},
{
"methods": ["GET"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_uspto.yaml"],
"name": "USPTO_Data_Set_API-path_1-get",
"name": "list-searchable-fields",
"paths": ["\/(?<dataset>\\S+)\/(?<version>\\S+)\/fields$"]
},
{
"methods": ["POST"],
"strip_path": false,
"tags": ["OAS3_import", "OAS3file_uspto.yaml"],
"name": "USPTO_Data_Set_API-path_2-post",
"name": "perform-search",
"paths": ["\/(?<dataset>\\S+)\/(?<version>\\S+)\/records$"]
}
],

View File

@ -5,17 +5,16 @@ import { generate } from '../index';
describe('fixtures', () => {
const root = path.join(__dirname, '../__fixtures__/');
for (const name of fs.readdirSync(root)) {
if (name.includes('.expected')) {
continue;
}
const fileBases = fs.readdirSync(root).filter(name => !name.includes('.expected'));
const inputPath = path.join(root, name);
const expectedPath = inputPath + '.expected';
for (const fileBase of fileBases) {
const inputPath = path.join(root, fileBase);
const expectedBase = `${path.parse(fileBase).name}.expected.json`;
const expectedPath = path.join(root, expectedBase);
const expected = fs.readFileSync(expectedPath, 'utf8');
it(`converts ${name}`, async () => {
it(`converts ${fileBase}`, async () => {
const result = await generate(inputPath, 'kong-declarative-config');
expect(result.documents.length).toBe(1);

View File

@ -0,0 +1,77 @@
// @flow
import { generateRouteName } from '../services';
const api: OpenApi3Spec = {
openapi: '3.0',
info: { version: '1.0', title: 'Nebulo 9' },
servers: [{ url: 'https://example.com/api' }],
paths: {},
};
const compare = (expected: string, pathItem: OA3PathItem) => {
const name = generateRouteName(
{
...api,
paths: {
'/planet-smasher': {
summary: 'smashes planets',
...pathItem,
},
},
},
'/planet-smasher',
'post',
);
expect(name).toEqual(expected);
};
describe('names', () => {
it(`api.paths[path][method]['x-kong-name'] is highest priority`, () => {
compare('method_smash', {
'x-kong-name': 'pathItem-smash',
post: {
'x-kong-name': 'method-smash',
operationId: 'operationId-smash',
},
});
});
it('api.paths[path][method].operationId is second priority (and not slugified)', () => {
compare('operationId-smash', {
'x-kong-name': 'pathItem-smash',
post: {
operationId: 'operationId-smash',
},
});
});
it(`api.paths[path]['x-kong-name'] is third priority`, () => {
compare('Nebulo_9-pathItem_smash-post', {
'x-kong-name': 'pathItem-smash',
post: {},
});
});
it('purely generated is fourth priority', () => {
compare('Nebulo_9-planet_smasher-post', {
post: {},
});
});
it('handles root paths', () => {
const name = generateRouteName(
{
...api,
paths: {
'/': {
summary: 'smashes planets',
get: {},
},
},
},
'/',
'get',
);
expect(name).toEqual('Nebulo_9-get');
});
});

View File

@ -42,21 +42,21 @@ describe('services', () => {
tags: ['Tag'],
},
{
name: 'My_API-Dog_stuff-get',
name: 'My_API-dogs-get',
strip_path: false,
methods: ['GET'],
paths: ['/dogs$'],
tags: ['Tag'],
},
{
name: 'My_API-Dog_stuff-post',
name: 'My_API-dogs-post',
strip_path: false,
methods: ['POST'],
paths: ['/dogs$'],
tags: ['Tag'],
},
{
name: 'My_API-path_3-get',
name: 'My_API-birds_id-get',
strip_path: false,
methods: ['GET'],
paths: ['/birds/(?<id>\\S+)$'],

View File

@ -6,6 +6,7 @@ import {
getAllServers,
getName,
pathVariablesToRegex,
HttpMethod,
} from '../common';
import { generateSecurityPlugins } from './security-plugins';
@ -66,7 +67,7 @@ export function generateService(
const fullPathRegex = pathVariablesToRegex(routePath);
const route: DCRoute = {
tags,
name: generateRouteName(api, pathItem, method, service.routes.length),
name: generateRouteName(api, routePath, method),
methods: [method.toUpperCase()],
paths: [fullPathRegex],
strip_path: false,
@ -91,24 +92,22 @@ export function generateService(
export function generateRouteName(
api: OpenApi3Spec,
pathItem: OA3PathItem,
method: string,
numRoutes: number,
routePath: string,
method: $Keys<typeof HttpMethod>,
): string {
const n = numRoutes;
const name = getName(api);
const pathItem = api.paths[routePath];
if (typeof (pathItem: Object)['x-kong-name'] === 'string') {
const pathSlug = generateSlug((pathItem: Object)['x-kong-name']);
return `${name}-${pathSlug}-${method}`;
if (pathItem[method] && typeof pathItem[method]['x-kong-name'] === 'string') {
return generateSlug(pathItem[method]['x-kong-name']);
}
// If a summary key exists, use that to generate the name
if (typeof pathItem.summary === 'string') {
const pathSlug = generateSlug(pathItem.summary);
return `${name}-${pathSlug}-${method}`;
if (pathItem[method] && pathItem[method].operationId) {
return pathItem[method].operationId;
}
// otherwise, use a unique integer to prevent collisions
return `${generateSlug(name)}-path${n ? '_' + n : ''}-${method}`;
// replace all `/` with `-` except the ones at the beginng or end of a string
const replacedRoute = routePath.replace(/(?!^)\/(?!$)/g, '-');
const pathSlug = generateSlug(pathItem['x-kong-name'] || replacedRoute);
return `${name}${pathSlug ? `-${pathSlug}` : ''}-${method}`;
}

View File

@ -1,5 +1,9 @@
// @flow
declare type XKongName = {
'x-kong-name'?: string,
};
declare type OA3Info = {|
title: string,
version: string,
@ -53,7 +57,7 @@ declare type OA3Operation = {
deprecated?: boolean,
security?: Array<OA3SecurityRequirement>,
servers?: Array<OA3Server>,
};
} & XKongName;
declare type OA3Reference = {|
$ref: string,
@ -106,7 +110,7 @@ declare type OA3PathItem = {
head?: OA3Operation,
patch?: OA3Operation,
trace?: OA3Operation,
};
} & XKongName;
declare type OA3Paths = {
[string]: OA3PathItem,
@ -204,8 +208,7 @@ declare type OpenApi3Spec = {
security?: Array<OA3SecurityRequirement>,
tags?: Array<string>,
externalDocs?: OA3ExternalDocs,
'x-kong-name'?: string,
};
} & XKongName;
const HttpMethod = {
get: 'GET',