diff --git a/packages/openapi-2-kong/jest.config.js b/packages/openapi-2-kong/jest.config.js index 0f631282ec..82d81e5799 100644 --- a/packages/openapi-2-kong/jest.config.js +++ b/packages/openapi-2-kong/jest.config.js @@ -1,4 +1,9 @@ /** @type { import('@jest/types').Config.InitialOptions } */ module.exports = { preset: '../../jest-preset.js', + globals: { + 'ts-jest': { + isolatedModules: false, + }, + }, }; diff --git a/packages/openapi-2-kong/src/common.test.ts b/packages/openapi-2-kong/src/common.test.ts index 7378743b2f..0e58470cee 100644 --- a/packages/openapi-2-kong/src/common.test.ts +++ b/packages/openapi-2-kong/src/common.test.ts @@ -14,24 +14,10 @@ import { parseUrl, pathVariablesToRegex, } from './common'; -import { parseSpec } from './generate'; -import { OpenApi3Spec, OA3Operation, OA3Server } from './types/openapi3'; +import { getSpec } from './declarative-config/utils'; +import { OA3Operation, OA3Server, xKongName } from './types/openapi3'; describe('common', () => { - const spec: OpenApi3Spec = { - openapi: '3.0.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, - servers: [ - { - url: 'https://server1.com/path', - }, - ], - paths: {}, - }; - describe('getPaths()', () => { it('should return api paths', () => { const paths = { @@ -39,19 +25,14 @@ describe('common', () => { description: 'test', }, }; - const api = { ...spec, paths }; + const api = getSpec({ paths }); const result = getPaths(api); - expect(result).toBe(paths); + expect(result).toStrictEqual(paths); }); }); describe('getServers()', () => { - const spec = { - openapi: '3.0.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, + const spec = getSpec({ servers: [ { url: 'https://server1.com/path', @@ -66,11 +47,10 @@ describe('common', () => { ], }, }, - }; + }); - it('returns path item servers', async () => { - const s = await parseSpec(spec); - const result = getServers(s.paths['/']); + it('returns path item servers', () => { + const result = getServers(spec.paths['/']); expect(result).toEqual([ { url: 'https://server2.com/path', @@ -78,9 +58,8 @@ describe('common', () => { ]); }); - it('returns api servers', async () => { - const s = await parseSpec(spec); - const result = getServers(s); + it('returns api servers', () => { + const result = getServers(spec); expect(result).toEqual([ { url: 'https://server1.com/path', @@ -97,7 +76,6 @@ describe('common', () => { petstoreAuth: [], }, ], - // @ts-expect-error -- TSCONVERSION not sure, but this appears to be wrong maybe? responses: {}, }; const result = getSecurity(operation); @@ -109,12 +87,7 @@ describe('common', () => { }); it('returns security from api', () => { - const spec: OpenApi3Spec = { - openapi: '3.0.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, + const spec = getSpec({ servers: [ { url: 'https://server1.com/path', @@ -151,7 +124,7 @@ describe('common', () => { }, }, }, - }; + }); const result = getSecurity(spec); expect(result).toEqual([ { @@ -162,48 +135,33 @@ describe('common', () => { }); describe('getName()', () => { - const spec = { - openapi: '3.0.0', - info: { - version: '1.0.0', - title: 'Swagger Petstore', - }, - servers: [ - { - url: 'https://server1.com/path', - }, - ], - paths: {}, - }; - - it('openapi object with x-kong-name', async () => { - const s = await parseSpec({ ...spec, 'x-kong-name': 'override' }); - const result = getName(s); + it('openapi object with x-kong-name', () => { + const spec = getSpec({ [xKongName]: 'override' }); + const result = getName(spec); expect(result).toBe('override'); }); - it('openapi object without x-kong-name', async () => { - const s = await parseSpec(spec); - const result = getName(s); - expect(result).toBe('Swagger_Petstore'); + it('openapi object without x-kong-name', () => { + const spec = getSpec(); + const result = getName(spec); + expect(result).toBe('My_API'); }); - it('openapi object without anything', async () => { - const s = await parseSpec({ - ...spec, + it('openapi object without anything', () => { + const spec = getSpec({ info: { version: '1.0.0', }, }); - const result = getName(s); + const result = getName(spec); expect(result).toBe('openapi'); - const result2 = getName(s, 'Another Default'); + const result2 = getName(spec, 'Another Default'); expect(result2).toBe('Another_Default'); }); it('works with slugify options', () => { - const p = { ...spec, 'x-kong-name': 'This Needs Slugify' }; - const result = getName(p, '', { + const spec = getSpec({ [xKongName]: 'This Needs Slugify' }); + const result = getName(spec, '', { replacement: '?', lower: true, }); @@ -254,7 +212,7 @@ describe('common', () => { const server: OA3Server = { url: 'https://{subdomain}.swagger.io/v1', variables: { - // @ts-expect-error intentionally missing a required value + // @ts-expect-error intentionally invalid - missing a required property, `default` subdomain: { enum: ['petstore'], }, @@ -291,7 +249,7 @@ describe('common', () => { }); it('should be false if key is not prefixed by x-kong-plugin-', () => { - expect(isPluginKey('x-kong-name')).toBe(false); + expect(isPluginKey(xKongName)).toBe(false); }); }); diff --git a/packages/openapi-2-kong/src/common.ts b/packages/openapi-2-kong/src/common.ts index 6bfd30d3eb..121c4ccef0 100644 --- a/packages/openapi-2-kong/src/common.ts +++ b/packages/openapi-2-kong/src/common.ts @@ -5,6 +5,7 @@ import { OA3PathItem, OA3Server, OA3Operation, + xKongName, } from './types/openapi3'; export function getServers(obj: OpenApi3Spec | OA3PathItem) { @@ -46,7 +47,7 @@ export function getName( rawName = isKubernetes ? api.info?.['x-kubernetes-ingress-metadata']?.name : ''; // Get $.x-kong-name - rawName = rawName || api['x-kong-name']; + rawName = rawName || api[xKongName]; // Get $.info.title rawName = rawName || api.info?.title; diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/request-validator-plugin.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/request-validator-plugin.expected.json index a33832cdcf..82a2f73891 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/request-validator-plugin.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/request-validator-plugin.expected.json @@ -13,7 +13,6 @@ "body_schema": "{}", "version": "draft4" }, - "enabled": true, "tags": [ "OAS3_import", "OAS3file_request-validator-plugin.yaml" @@ -40,7 +39,6 @@ "body_schema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"}}}", "version": "draft4" }, - "enabled": true, "tags": [ "OAS3_import", "OAS3file_request-validator-plugin.yaml" @@ -71,7 +69,6 @@ "body_schema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"}}}", "version": "draft4" }, - "enabled": true, "tags": [ "OAS3_import", "OAS3file_request-validator-plugin.yaml" diff --git a/packages/openapi-2-kong/src/declarative-config/names.test.ts b/packages/openapi-2-kong/src/declarative-config/names.test.ts index c3a40e4d12..3f70897ee2 100644 --- a/packages/openapi-2-kong/src/declarative-config/names.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/names.test.ts @@ -1,4 +1,4 @@ -import { OA3PathItem, OpenApi3Spec } from '../types/openapi3'; +import { OA3PathItem, OpenApi3Spec, xKongName } from '../types/openapi3'; import { generateRouteName } from './services'; const api: OpenApi3Spec = { @@ -35,9 +35,9 @@ const compare = (expected: string, pathItem: OA3PathItem) => { describe('names', () => { it("api.paths[path][method]['x-kong-name'] is highest priority", () => { compare('Nebulo_9-method_smash', { - 'x-kong-name': 'pathItem-smash', + [xKongName]: 'pathItem-smash', post: { - 'x-kong-name': 'method-smash', + [xKongName]: 'method-smash', operationId: 'operationId-smash', }, }); @@ -45,7 +45,7 @@ describe('names', () => { it('api.paths[path][method].operationId is second priority (and not slugified)', () => { compare('Nebulo_9-operationId_smash', { - 'x-kong-name': 'pathItem-smash', + [xKongName]: 'pathItem-smash', post: { operationId: 'operationId-smash', }, @@ -54,7 +54,7 @@ describe('names', () => { it("api.paths[path]['x-kong-name'] is third priority", () => { compare('Nebulo_9-pathItem_smash-post', { - 'x-kong-name': 'pathItem-smash', + [xKongName]: 'pathItem-smash', post: {}, }); }); diff --git a/packages/openapi-2-kong/src/declarative-config/plugins.test.ts b/packages/openapi-2-kong/src/declarative-config/plugins.test.ts index 8ab02d2eca..03981212e2 100644 --- a/packages/openapi-2-kong/src/declarative-config/plugins.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/plugins.test.ts @@ -1,20 +1,15 @@ import { generateGlobalPlugins, generateRequestValidatorPlugin } from './plugins'; -import { OpenApi3Spec, OA3Operation, OA3Parameter } from '../types/openapi3'; +import { OA3Operation, OA3Parameter, xKongPluginKeyAuth, xKongPluginRequestTermination, xKongPluginRequestValidator } from '../types/openapi3'; import { DCPlugin, DCPluginConfig } from '../types/declarative-config'; +import { getSpec } from './utils'; const tags = ['Tag']; describe('plugins', () => { describe('generateGlobalPlugins()', () => { it('generates plugin given a spec with a plugin attached', async () => { - const api: OpenApi3Spec = { - openapi: '3.0.2', - info: { - title: 'something', - version: '12', - }, - paths: {}, - 'x-kong-plugin-request-validator': { + const api = getSpec({ + [xKongPluginRequestValidator]: { enabled: false, config: { verbose_response: true, @@ -26,26 +21,27 @@ describe('plugins', () => { some_config: ['something'], }, }, - 'x-kong-plugin-key-auth': { + [xKongPluginKeyAuth]: { name: 'key-auth', config: { key_names: ['x-api-key'], }, }, - }; - const result = generateGlobalPlugins(api, ['Tag']); + }); + + const result = generateGlobalPlugins(api, tags); + expect(result.plugins).toEqual([ { name: 'abcd', - // name from plugin tag - tags: ['Tag'], + tags, config: { some_config: ['something'], }, }, { name: 'key-auth', - tags: ['Tag'], + tags, config: { key_names: ['x-api-key'], }, @@ -56,7 +52,7 @@ describe('plugins', () => { verbose_response: true, version: 'draft4', }, - tags: ['Tag'], + tags, enabled: false, name: 'request-validator', }, @@ -68,7 +64,37 @@ describe('plugins', () => { enabled: false, }); }); + + it('does not add extra things to the plugin', () => { + const spec = getSpec({ + [xKongPluginRequestTermination]: { + name: 'request-termination', + mad: 'max', + config: { + max: 'is mad', + status_code: 403, + message: 'So long and thanks for all the fish!', + }, + }, + }); + + const result = generateGlobalPlugins(spec, tags); + + expect(result.plugins).toEqual([ + { + name: 'request-termination', + mad: 'max', + tags, + config: { + max: 'is mad', + status_code: 403, + message: 'So long and thanks for all the fish!', + }, + }, + ]); + }); }); + describe('generateRequestValidatorPlugin()', () => { const parameterSchema = [ { @@ -95,7 +121,7 @@ describe('plugins', () => { expect(generated).toStrictEqual({ name: 'request-validator', enabled: plugin.enabled, - tags: ['Tag'], + tags, config: { version: 'draft4', ...plugin.config, @@ -103,12 +129,13 @@ describe('plugins', () => { }); }); - it('should not add properties if they are not defined', () => { + it('should not add config properties if they are not defined', () => { const plugin = { enabled: true, config: { parameter_schema: [parameterSchema], - body_schema: '[{"name":{"type": "string", "required": true}}]', // The following properties are missing + body_schema: '[{"name":{"type": "string", "required": true}}]', + // The following properties are missing // verbose_response: true, // allowed_content_types: ['application/json'], }, @@ -117,7 +144,7 @@ describe('plugins', () => { expect(generated).toStrictEqual({ name: 'request-validator', enabled: plugin.enabled, - tags: ['Tag'], + tags, config: { version: 'draft4', ...plugin.config, @@ -261,6 +288,7 @@ describe('plugins', () => { }); }); }); + describe('body_schema and allowed_content_types', () => { it('should not add body_schema or allowed_content_types if no body present', () => { const plugin = { diff --git a/packages/openapi-2-kong/src/declarative-config/plugins.ts b/packages/openapi-2-kong/src/declarative-config/plugins.ts index 989b646308..bcbf6d648c 100644 --- a/packages/openapi-2-kong/src/declarative-config/plugins.ts +++ b/packages/openapi-2-kong/src/declarative-config/plugins.ts @@ -3,7 +3,7 @@ import { distinctByProperty, getPluginNameFromKey, isPluginKey } from '../common import { DCPlugin, DCPluginConfig } from '../types/declarative-config'; import { OA3Operation, OpenApi3Spec, OA3RequestBody, OA3Parameter } from '../types/openapi3'; -export function isRequestValidatorPluginKey(key: string): boolean { +export function isRequestValidatorPluginKey(key: string) { return key.match(/-request-validator$/) != null; } @@ -20,8 +20,8 @@ export function generatePlugins(item: PluginItem, tags: string[]) { } const generatePlugin = (tags: string[]) => ([key, value]: Entry): DCPlugin => ({ + ...(value ?? {}), name: value.name || getPluginNameFromKey(key), - ...(value.config ? { config: value.config } : {}), tags: [ // Add tags to plugins while appending defaults tags ...tags, @@ -147,10 +147,16 @@ export function generateRequestValidatorPlugin({ plugin, tags, operation }: { config.verbose_response = Boolean(pluginConfig.verbose_response); } + const isEnabledSpecified = Object.prototype.hasOwnProperty.call(plugin, 'enabled'); + const enabled = isEnabledSpecified ? { enabled: Boolean(plugin.enabled ?? true) } : {}; + const dcPlugin: DCPlugin = { config, - tags: [...(tags ?? []), ...(plugin.tags ?? [])], - enabled: Boolean(plugin.enabled ?? true), + tags: [ + ...(tags ?? []), + ...(plugin.tags ?? []), + ], + ...enabled, name: 'request-validator', }; return dcPlugin; diff --git a/packages/openapi-2-kong/src/declarative-config/security-plugins.test.ts b/packages/openapi-2-kong/src/declarative-config/security-plugins.test.ts index d4b111ebe3..7b7a897cbe 100644 --- a/packages/openapi-2-kong/src/declarative-config/security-plugins.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/security-plugins.test.ts @@ -22,7 +22,7 @@ describe('security-plugins', () => { it('generates apikey plugin with funny casing', async () => { const scheme: OA3SecurityScheme = { - // @ts-expect-error -- this is intentionally using strange casing. Yes, technically speaking the type of OA3SecurityScheme['type'] should be `string`, but we really only support strange casing like this for backwards compat. We want the types to reflect the API we're aiming for, even though we're technically a bit more permissive. + // @ts-expect-error intentionally invalid -- using strange casing. Yes, technically speaking the type of OA3SecurityScheme['type'] should be `string`, but we really only support strange casing like this for backwards compat. We want the types to reflect the API we're aiming for, even though we're technically a bit more permissive. type: 'ApIKeY', in: 'header', name: 'x-api-key', diff --git a/packages/openapi-2-kong/src/declarative-config/services.test.ts b/packages/openapi-2-kong/src/declarative-config/services.test.ts index 48b54ae5b2..878979768a 100644 --- a/packages/openapi-2-kong/src/declarative-config/services.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/services.test.ts @@ -2,9 +2,7 @@ import { generateServices } from './services'; import { parseSpec } from '../generate'; import { getSpec } from './utils'; import { DCRoute, DCService } from '../types/declarative-config'; - -const xKongPluginRequestValidator = 'x-kong-plugin-request-validator'; -const xKongRouteDefaults = 'x-kong-route-defaults'; +import { xKongPluginKeyAuth, xKongPluginRequestValidator, xKongRouteDefaults } from '../types'; /** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ const getSpecResult = (): DCService => @@ -63,8 +61,10 @@ describe('services', () => { }); it('throws for a root level x-kong-route-default', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = 'foo'; + const spec = getSpec({ + // @ts-expect-error intentionally invalid + [xKongRouteDefaults]: 'foo', + }); const api = await parseSpec(spec); const fn = () => generateServices(api, ['Tag']); @@ -73,15 +73,18 @@ describe('services', () => { }); it('ignores null for a root level x-kong-route-default', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = null; + const spec = getSpec({ + // @ts-expect-error intentionally invalid + [xKongRouteDefaults]: null, + }); const specResult = getSpecResult(); const api = await parseSpec(spec); expect(generateServices(api, ['Tag'])).toEqual([specResult]); }); it('throws for a paths level x-kong-route-default', async () => { - const spec = getSpec(); + const spec = getSpec({}); + // @ts-expect-error intentionally invalid spec.paths['/cats'][xKongRouteDefaults] = 'foo'; const api = await parseSpec(spec); @@ -92,6 +95,7 @@ describe('services', () => { it('ignores null for a paths level x-kong-route-default', async () => { const spec = getSpec(); + // @ts-expect-error intentionally invalid spec.paths['/cats'][xKongRouteDefaults] = null; const specResult = getSpecResult(); const api = await parseSpec(spec); @@ -100,6 +104,7 @@ describe('services', () => { it('throws for an operation level x-kong-route-default', async () => { const spec = getSpec(); + // @ts-expect-error intentionally invalid spec.paths['/cats'].post[xKongRouteDefaults] = 'foo'; const api = await parseSpec(spec); @@ -112,6 +117,7 @@ describe('services', () => { it('ignores null for an operation level x-kong-route-default', async () => { const spec = getSpec(); + // @ts-expect-error intentionally invalid spec.paths['/cats'].post[xKongRouteDefaults] = null; const specResult = getSpecResult(); const api = await parseSpec(spec); @@ -127,13 +133,14 @@ describe('services', () => { }); it('generates routes with request validator plugin from operation over path over global', async () => { - const spec = getSpec(); - // global req validator plugin - spec[xKongPluginRequestValidator] = { - config: { - parameter_schema: 'global', + const spec = getSpec({ + // global req validator plugin + [xKongPluginRequestValidator]: { + config: { + parameter_schema: 'global', + }, }, - }; + }); // path req validator plugin spec.paths['/cats'][xKongPluginRequestValidator] = { config: { @@ -142,17 +149,21 @@ describe('services', () => { }; spec.paths['/cats'].get = {}; // operation req validator plugin - spec.paths['/cats'].post[xKongPluginRequestValidator] = { - config: { - parameter_schema: 'operation', - }, - }; + if (spec.paths['/cats'].post) { + spec.paths['/cats'].post[xKongPluginRequestValidator] = { + config: { + parameter_schema: 'operation', + }, + }; + } // operation req validator plugin - spec.paths['/dogs'].post[xKongPluginRequestValidator] = { - config: { - parameter_schema: 'operation', - }, - }; + if (spec.paths['/dogs'].post) { + spec.paths['/dogs'].post[xKongPluginRequestValidator] = { + config: { + parameter_schema: 'operation', + }, + }; + } const specResult = getSpecResult(); specResult.plugins = [ { @@ -160,7 +171,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'global', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -179,7 +189,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'operation', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -198,7 +207,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'path', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -217,7 +225,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'global', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -236,7 +243,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'operation', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -255,7 +261,6 @@ describe('services', () => { version: 'draft4', parameter_schema: 'global', }, - enabled: true, tags: ['Tag'], name: 'request-validator', }, @@ -271,14 +276,15 @@ describe('services', () => { spec.paths = { '/dogs': { summary: 'Dog stuff', - 'x-kong-plugin-key-auth': { + [xKongPluginKeyAuth]: { config: { key_names: ['path'], }, }, get: {}, post: { - 'x-kong-plugin-key-auth': { + [xKongPluginKeyAuth]: { + name: 'key-auth', config: { key_names: ['operation'], }, @@ -351,22 +357,24 @@ describe('services', () => { }); describe('x-kong-route-defaults and strip_path', () => { + // Note: although these are technically not valid DC Routes configs, we use them because they make it crystal-clear what the tests are checking for const rootLevel = { level: 'root', rootLevel: true, - }; + } as unknown as DCRoute; const pathLevel = { level: 'path', pathLevel: true, - }; + } as unknown as DCRoute; const operationLevel = { level: 'operation', operationLevel: true, - }; + } as unknown as DCRoute; it('root level', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = rootLevel; + const spec = getSpec({ + [xKongRouteDefaults]: rootLevel, + }); const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, ...rootLevel })); const api = await parseSpec(spec); @@ -385,7 +393,9 @@ describe('services', () => { it('operation level', async () => { const spec = getSpec(); - spec.paths['/dogs'].get[xKongRouteDefaults] = operationLevel; + if (spec.paths['/dogs'].get) { + spec.paths['/dogs'].get[xKongRouteDefaults] = operationLevel; + } const specResult = getSpecResult(); specResult.routes[1] = { ...specResult.routes[1], ...operationLevel }; const api = await parseSpec(spec); @@ -393,9 +403,12 @@ describe('services', () => { }); it('will select (but not merge) the operation level over the root level', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = rootLevel; - spec.paths['/cats'].post[xKongRouteDefaults] = operationLevel; + const spec = getSpec({ + [xKongRouteDefaults]: rootLevel, + }); + if (spec.paths['/cats'].post) { + spec.paths['/cats'].post[xKongRouteDefaults] = operationLevel; + } const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, @@ -408,7 +421,9 @@ describe('services', () => { it('will select (but not merge) the operation level over the path level', async () => { const spec = getSpec(); spec.paths['/dogs'][xKongRouteDefaults] = pathLevel; - spec.paths['/dogs'].post[xKongRouteDefaults] = operationLevel; + if (spec.paths['/dogs'].post) { + spec.paths['/dogs'].post[xKongRouteDefaults] = operationLevel; + } const specResult = getSpecResult(); specResult.routes[1] = { ...specResult.routes[1], ...pathLevel }; specResult.routes[2] = { ...specResult.routes[2], ...operationLevel }; @@ -417,8 +432,9 @@ describe('services', () => { }); it('will select (but not merge) the path level over the root level', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = rootLevel; + const spec = getSpec({ + [xKongRouteDefaults]: rootLevel, + }); spec.paths['/cats'][xKongRouteDefaults] = pathLevel; const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ @@ -442,10 +458,11 @@ describe('services', () => { }); it('allows overriding `strip_path` from `x-kong-route-defaults` at the root', async () => { - const spec = getSpec(); - spec[xKongRouteDefaults] = { - strip_path: true, - }; + const spec = getSpec({ + [xKongRouteDefaults]: { + strip_path: true, + }, + }); const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, strip_path: true })); const api = await parseSpec(spec); diff --git a/packages/openapi-2-kong/src/declarative-config/services.ts b/packages/openapi-2-kong/src/declarative-config/services.ts index 4b5d53a383..ca0f2876f6 100644 --- a/packages/openapi-2-kong/src/declarative-config/services.ts +++ b/packages/openapi-2-kong/src/declarative-config/services.ts @@ -15,7 +15,7 @@ import { generatePlugins, } from './plugins'; import { DCService, DCRoute } from '../types/declarative-config'; -import { OpenApi3Spec, OA3Server, OA3PathItem } from '../types/openapi3'; +import { OpenApi3Spec, OA3Server, OA3PathItem, xKongServiceDefaults, xKongName } from '../types/openapi3'; export function generateServices(api: OpenApi3Spec, tags: string[]) { const servers = getAllServers(api); @@ -35,10 +35,10 @@ export function generateService(server: OA3Server, api: OpenApi3Spec, tags: stri const parsedUrl = parseUrl(serverUrl); // Service plugins const globalPlugins = generateGlobalPlugins(api, tags); - const serviceDefaults = api['x-kong-service-defaults'] || {}; + const serviceDefaults = api[xKongServiceDefaults] || {}; if (typeof serviceDefaults !== 'object') { - throw new Error('expected \'x-kong-service-defaults\' to be an object'); + throw new Error(`expected '${xKongServiceDefaults}' to be an object`); } const service: DCService = { @@ -147,8 +147,8 @@ export function generateRouteName( const name = getName(api); const pathItem = api.paths[routePath]; - if (typeof pathItem?.[method]?.['x-kong-name'] === 'string') { - const opsName = generateSlug(pathItem?.[method]?.['x-kong-name'] as string); + if (typeof pathItem?.[method]?.[xKongName] === 'string') { + const opsName = generateSlug(pathItem?.[method]?.[xKongName] as string); return `${name}-${opsName}`; } @@ -159,6 +159,6 @@ export function generateRouteName( // replace all `/` with `-` except the ones at the beginning or end of a string const replacedRoute = routePath.replace(/(?!^)\/(?!$)/g, '-'); - const pathSlug = generateSlug(pathItem['x-kong-name'] || replacedRoute); + const pathSlug = generateSlug(pathItem[xKongName] || replacedRoute); return `${name}${pathSlug ? `-${pathSlug}` : ''}-${method}`; } diff --git a/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts b/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts index 4523c07574..2ec2eadfba 100644 --- a/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts @@ -1,9 +1,8 @@ import { parseSpec } from '../generate'; +import { xKongUpstreamDefaults } from '../types'; import { generateUpstreams } from './upstreams'; import { getSpec } from './utils'; -const xKongUpstreamDefaults = 'x-kong-upstream-defaults'; - /** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ const getSpecResult = () => JSON.parse( @@ -28,20 +27,22 @@ describe('upstreams', () => { }); it('throws for a root level x-kong-route-default', async () => { - const spec = getSpec(); - // @ts-expect-error intentionall invalid - spec[xKongUpstreamDefaults] = 'foo'; + const spec = getSpec({ + // @ts-expect-error intentionall invalid + [xKongUpstreamDefaults]: 'foo', + }); const api = await parseSpec(spec); const fn = () => generateUpstreams(api, ['Tag']); - expect(fn).toThrowError('expected \'x-kong-upstream-defaults\' to be an object'); + expect(fn).toThrowError(`expected '${xKongUpstreamDefaults}' to be an object`); }); it('ignores null for a root level x-kong-route-default', async () => { - const spec = getSpec(); - // @ts-expect-error intentionall invalid - spec[xKongUpstreamDefaults] = null; + const spec = getSpec({ + // @ts-expect-error intentionall invalid + [xKongUpstreamDefaults]: null, + }); const specResult = getSpecResult(); const api = await parseSpec(spec); expect(generateUpstreams(api, ['Tag'])).toEqual([specResult]); diff --git a/packages/openapi-2-kong/src/declarative-config/upstreams.ts b/packages/openapi-2-kong/src/declarative-config/upstreams.ts index ce8a7c22e6..bceb5384b8 100644 --- a/packages/openapi-2-kong/src/declarative-config/upstreams.ts +++ b/packages/openapi-2-kong/src/declarative-config/upstreams.ts @@ -1,6 +1,6 @@ import { getName, parseUrl, fillServerVariables } from '../common'; import { DCUpstream } from '../types/declarative-config'; -import { OpenApi3Spec } from '../types/openapi3'; +import { OpenApi3Spec, xKongUpstreamDefaults } from '../types/openapi3'; export function generateUpstreams(api: OpenApi3Spec, tags: string[]) { const servers = api.servers || []; @@ -11,10 +11,10 @@ export function generateUpstreams(api: OpenApi3Spec, tags: string[]) { // x-kong-upstream-defaults is free format so we do not want type checking. // If added, it would tightly couple these objects to Kong, and that would make future maintenance a lot harder. - const upstreamDefaults = api['x-kong-upstream-defaults'] || {}; + const upstreamDefaults = api[xKongUpstreamDefaults] || {}; if (typeof upstreamDefaults !== 'object') { - throw new Error("expected 'x-kong-upstream-defaults' to be an object"); + throw new Error(`expected '${xKongUpstreamDefaults}' to be an object`); } const upstream: DCUpstream = { diff --git a/packages/openapi-2-kong/src/declarative-config/utils.ts b/packages/openapi-2-kong/src/declarative-config/utils.ts index b83e927aef..67036839a0 100644 --- a/packages/openapi-2-kong/src/declarative-config/utils.ts +++ b/packages/openapi-2-kong/src/declarative-config/utils.ts @@ -1,7 +1,7 @@ import { OpenApi3Spec } from '../types/openapi3'; /** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ -export const getSpec = (): OpenApi3Spec => +export const getSpec = (overrides: Partial = {}): OpenApi3Spec => JSON.parse( JSON.stringify({ openapi: '3.0', @@ -31,5 +31,6 @@ export const getSpec = (): OpenApi3Spec => get: {}, }, }, + ...overrides, }), ); diff --git a/packages/openapi-2-kong/src/generate.test.ts b/packages/openapi-2-kong/src/generate.test.ts index e09b770680..29475165b6 100644 --- a/packages/openapi-2-kong/src/generate.test.ts +++ b/packages/openapi-2-kong/src/generate.test.ts @@ -87,7 +87,7 @@ describe('top-level API exports', () => { describe('generateFromSpec()', () => { it('generates DC from spec', async () => { - const parsedSpec = YAML.parse(dcFixtureFileString, 'utf8'); + const parsedSpec = YAML.parse(dcFixtureFileString); const { documents: [dc], } = await generateFromSpec(parsedSpec, 'kong-declarative-config') as DeclarativeConfigResult; @@ -97,7 +97,7 @@ describe('top-level API exports', () => { }); it('generates kubernetes from spec', async () => { - const parsedSpec = YAML.parse(k8sFixtureFileString, 'utf8'); + const parsedSpec = YAML.parse(k8sFixtureFileString); const { type, label, diff --git a/packages/openapi-2-kong/src/generate.ts b/packages/openapi-2-kong/src/generate.ts index 30f5efe786..1d6cb357d7 100644 --- a/packages/openapi-2-kong/src/generate.ts +++ b/packages/openapi-2-kong/src/generate.ts @@ -81,6 +81,6 @@ export const parseSpec = (spec: string | Record) => { api.openapi = '3.0.0'; } - // @ts-expect-error -- TSCONVERSION + // @ts-expect-error until we make our OpenAPI type extend from the cannonical one (i.e. from `openapi-types`, we'll need to shim this here) return SwaggerParser.dereference(api) as Promise; }; diff --git a/packages/openapi-2-kong/src/kubernetes/fixtures.test.ts b/packages/openapi-2-kong/src/kubernetes/fixtures.test.ts index 9acf4a9792..57791d0592 100644 --- a/packages/openapi-2-kong/src/kubernetes/fixtures.test.ts +++ b/packages/openapi-2-kong/src/kubernetes/fixtures.test.ts @@ -19,7 +19,7 @@ describe('kubernetes fixtures', () => { const expectedBase = `${path.parse(fileBase).name}.expected.yaml`; const expectedPath = path.join(root, expectedBase); const expected = await readFile(expectedPath, 'utf8'); - const parsedExpected = YAML.parse(expected); + const parsedExpected = YAML.parse(expected); expect(result).toEqual(parsedExpected); }); diff --git a/packages/openapi-2-kong/src/kubernetes/generate.test.ts b/packages/openapi-2-kong/src/kubernetes/generate.test.ts index 0fb314ebba..edfe3fec20 100644 --- a/packages/openapi-2-kong/src/kubernetes/generate.test.ts +++ b/packages/openapi-2-kong/src/kubernetes/generate.test.ts @@ -1,6 +1,6 @@ import { parseSpec } from '../generate'; import { K8sAnnotations, K8sIngressRule, K8sIngressTLS, K8sManifest } from '../types/kubernetes-config'; -import { OA3Server, OpenApi3Spec } from '../types/openapi3'; +import { OA3Server, OpenApi3Spec, xKongName } from '../types/openapi3'; import { generateKongForKubernetesConfigFromSpec, generateMetadataAnnotations, @@ -50,14 +50,14 @@ describe('index', () => { }); it('with x-kong-name', () => { - const api: OpenApi3Spec = { ...spec, 'x-kong-name': 'kong-name' }; + const api: OpenApi3Spec = { ...spec, [xKongName]: 'kong-name' }; expect(getSpecName(api)).toBe('kong-name'); }); it('with x-kubernetes-ingress-metadata.name', async () => { const api = await parseSpec({ ...spec, - 'x-kong-name': 'Kong Name', + [xKongName]: 'Kong Name', info: { 'x-kubernetes-ingress-metadata': { name: 'K8s name', diff --git a/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts b/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts index e26b1f0cf6..13f83aa34a 100644 --- a/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts +++ b/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts @@ -1,9 +1,9 @@ import { HttpMethodType } from '../common'; import { K8sIngress, K8sKongIngress, K8sKongPlugin } from '../types/kubernetes-config'; -import { XKongPluginKeyAuth, XKongPluginUnknown } from '../types/openapi3'; +import { xKongPluginKeyAuth, XKongPluginKeyAuth, XKongPluginUnknown } from '../types/openapi3'; export const pluginKeyAuth: XKongPluginKeyAuth = { - 'x-kong-plugin-key-auth': { + [xKongPluginKeyAuth]: { name: 'key-auth', config: { key_names: ['api_key', 'apikey'], diff --git a/packages/openapi-2-kong/src/types/declarative-config.ts b/packages/openapi-2-kong/src/types/declarative-config.ts index 8e3aca2d42..7da022acff 100644 --- a/packages/openapi-2-kong/src/types/declarative-config.ts +++ b/packages/openapi-2-kong/src/types/declarative-config.ts @@ -6,7 +6,7 @@ export interface DCPluginConfig { body_schema?: string; issuer?: string; key_names?: string[]; - parameter_schema?: OA3Parameter[]; + parameter_schema?: OA3Parameter[] | 'global' | 'path' | 'operation'; scopes_required?: string[]; verbose_response?: boolean; version?: 'draft4'; diff --git a/packages/openapi-2-kong/src/types/openapi3.ts b/packages/openapi-2-kong/src/types/openapi3.ts index de492a8130..133c7f711a 100644 --- a/packages/openapi-2-kong/src/types/openapi3.ts +++ b/packages/openapi-2-kong/src/types/openapi3.ts @@ -1,39 +1,40 @@ import { DCRoute, DCUpstream } from './declarative-config'; import { K8sIngressTLS } from './kubernetes-config'; +export const xKongName = 'x-kong-name'; export interface XKongName { - 'x-kong-name'?: string; + [xKongName]?: string; } +export const xKongRouteDefaults = 'x-kong-route-defaults'; export interface XKongRouteDefaults { - 'x-kong-route-defaults'?: DCRoute; + [xKongRouteDefaults]?: Partial; } +export const xKongUpstreamDefaults = 'x-kong-upstream-defaults'; export interface XKongUpstreamDefaults { - 'x-kong-upstream-defaults'?: DCUpstream; + [xKongUpstreamDefaults]?: Partial; } +export const xKongServiceDefaults = 'x-kong-service-defaults'; export interface XKongServiceDefaults { - 'x-kong-service-defaults'?: Record; + [xKongServiceDefaults]?: Record; } +export const xKongPluginRequestValidator = 'x-kong-plugin-request-validator'; export interface XKongPluginRequestValidator { - 'x-kong-plugin-request-validator'?: { - enabled: boolean; + [xKongPluginRequestValidator]?: { + enabled?: boolean; config: { - verbose_response: boolean, - } - } + verbose_response?: boolean; + parameter_schema?: 'global' | 'path' | 'operation'; + }; + }; } -export type XKongPluginUnknown = Record<`x-kong-plugin-${string}`, { - enabled?: boolean; - name?: string; - config: Record; -}> - +export const xKongPluginKeyAuth = 'x-kong-plugin-key-auth'; export interface XKongPluginKeyAuth { - 'x-kong-plugin-key-auth'?: { + [xKongPluginKeyAuth]?: { name: 'key-auth'; config: { key_names: string[]; @@ -43,6 +44,25 @@ export interface XKongPluginKeyAuth { }; } +export const xKongPluginRequestTermination = 'x-kong-plugin-request-termination'; +export interface XKongPluginRequestTermination { + [xKongPluginRequestTermination]?: { + name: 'request-termination'; + config: { + status_code: number; + message: string; + [key: string]: string | number; + } + [key: string]: string | number | Record; + } +} + +export type XKongPluginUnknown = Record<`x-kong-plugin-${string}`, { + enabled?: boolean; + name?: string; + config: Record; +}> + export interface StripPath { // eslint-disable-next-line camelcase -- this is defined by a spec that is out of our control strip_path?: boolean; @@ -153,6 +173,7 @@ export type OA3Operation = { } & XKongName & XKongRouteDefaults & XKongPluginKeyAuth + & XKongPluginRequestValidator ; export type OA3PathItem = { @@ -171,6 +192,7 @@ export type OA3PathItem = { trace?: OA3Operation; } & XKongName & XKongRouteDefaults + & XKongPluginRequestValidator ; export type OA3Paths = Record @@ -254,11 +276,13 @@ export type OpenApi3Spec = { security?: OA3SecurityRequirement[]; tags?: string[]; externalDocs?: OA3ExternalDocs; -} & XKongName +} & XKongPluginKeyAuth + & XKongPluginRequestTermination & XKongPluginRequestValidator & XKongPluginUnknown + & XKongRouteDefaults & XKongServiceDefaults & XKongUpstreamDefaults - & XKongRouteDefaults + & XKongName ;